From 0d14f99484ac038729b67454d0b17b927e905ab7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 13 Nov 2018 12:00:29 +0100 Subject: [PATCH 01/24] feat(parity-clib asynchronous rpc queries) --- Cargo.lock | 3 + logger/src/lib.rs | 1 + parity-clib/Cargo.toml | 7 +- parity-clib/examples/cpp/main.cpp | 134 ++++++++++++++----- parity-clib/parity.h | 21 +-- parity-clib/src/java.rs | 91 +++++++++++++ parity-clib/src/lib.rs | 206 +++++++++++++----------------- parity/lib.rs | 1 + parity/run.rs | 16 ++- rpc/src/lib.rs | 1 + 10 files changed, 309 insertions(+), 172 deletions(-) create mode 100644 parity-clib/src/java.rs diff --git a/Cargo.lock b/Cargo.lock index 47763e75121..0f5c445e289 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2328,9 +2328,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "parity-clib" version = "1.12.0" dependencies = [ + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "jni 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "panic_hook 0.1.0", "parity-ethereum 2.3.0", + "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-current-thread 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/logger/src/lib.rs b/logger/src/lib.rs index c78c24b29ea..6068fa39678 100644 --- a/logger/src/lib.rs +++ b/logger/src/lib.rs @@ -71,6 +71,7 @@ pub fn setup_log(config: &Config) -> Result, String> { builder.filter(Some("ws"), LevelFilter::Warn); builder.filter(Some("hyper"), LevelFilter::Warn); builder.filter(Some("rustls"), LevelFilter::Error); + builder.filter(Some("rpc"), LevelFilter::Trace); // Enable info for others. builder.filter(None, LevelFilter::Info); diff --git a/parity-clib/Cargo.toml b/parity-clib/Cargo.toml index 5397225eab2..4072494e4a6 100644 --- a/parity-clib/Cargo.toml +++ b/parity-clib/Cargo.toml @@ -10,9 +10,12 @@ name = "parity" crate-type = ["cdylib", "staticlib"] [dependencies] -panic_hook = { path = "../util/panic-hook" } -parity-ethereum = { path = "../", default-features = false } +futures = "0.1.6" jni = { version = "0.10.1", optional = true } +panic_hook = { path = "../util/panic_hook" } +parity-ethereum = { path = "../", default-features = false } +tokio = "0.1.11" +tokio-current-thread = "0.1.3" [features] default = [] diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index c5e83d06492..9402e7e967d 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +// Note, `nullptr` requires a C++ compiler with C+11 support + #include #include #include @@ -21,37 +23,111 @@ #include #include +int parity_light(); +int parity_full(); + +// global variable to keep track of the received rpc responses +static int g_num_queries = 0; + +// list of rpc queries +static const char* rpc_queries[] = { + "{\"method\":\"parity_versionInfo\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_getTransactionReceipt\",\"params\":[\"0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913fff\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\"}],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_getBalance\",\"params\":[\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\"],\"id\":1,\"jsonrpc\":\"2.0\"}" +}; + +// callback that gets invoked when the client restarts void on_restart(void*, const char*, size_t) {} -int main() { - ParityParams cfg = { 0 }; - cfg.on_client_restart_cb = on_restart; - - const char* args[] = {"--no-ipc"}; - size_t str_lens[] = {8}; - if (parity_config_from_cli(args, str_lens, 1, &cfg.configuration) != 0) { - return 1; - } - - void* parity; - if (parity_start(&cfg, &parity) != 0) { - return 1; - } - - const char* rpc = "{\"method\":\"parity_versionInfo\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}"; - size_t out_len = 256; - char* out = (char*)malloc(out_len + 1); - if (parity_rpc(parity, rpc, strlen(rpc), out, &out_len)) { - return 1; - } - out[out_len] = '\0'; - printf("RPC output: %s", out); - free(out); - - sleep(5); - if (parity != NULL) { - parity_destroy(parity); - } +// callback that is invoked on rpc responses +void rpc_response(void *, const char* response, size_t len) { + printf("rpc_callback: %s \r\n", response); + g_num_queries -= 1; +} +int main() { + // run the list of queries in the light client + if (parity_light() != 0) { + printf("parity light client failed\r\n"); + } + // run the list of queries in the full client + if (parity_full() != 0) { + printf("parity client failed\r\n"); + } return 0; } + +int parity_light() { + int num_queries = sizeof(rpc_queries) / sizeof(rpc_queries[0]); + g_num_queries = num_queries; + + ParityParams cfg = { + .configuration = nullptr, + .on_client_restart_cb = on_restart, + .on_client_restart_cb_custom = nullptr + }; + + const char* args[] = {"--light", "--no-ipc"}; + size_t str_lens[] = {strlen(args[0]), strlen(args[1])}; + + if (parity_config_from_cli(args, str_lens, sizeof(str_lens)/sizeof(str_lens[0]), &cfg.configuration) != 0) { + return 1; + } + + void *parity = nullptr; + if (parity_start(&cfg, &parity) != 0) { + return 1; + } + + for (int i = 0; i < num_queries; i++) { + if (parity_rpc(parity, rpc_queries[i], strlen(rpc_queries[i]), rpc_response) != 0) { + return 1; + } + } + + // wait until all queries have been answered + while(g_num_queries != 0); + + if (parity != NULL) { + parity_destroy(parity); + } + return 0; +} + +int parity_full() { + int num_queries = sizeof(rpc_queries) / sizeof(rpc_queries[0]); + g_num_queries = num_queries; + + ParityParams cfg = { + .configuration = nullptr, + .on_client_restart_cb = on_restart, + .on_client_restart_cb_custom = nullptr + }; + + const char* args[] = {"--no-ipc"}; + size_t str_lens[] = {strlen(args[0])}; + + if (parity_config_from_cli(args, str_lens, sizeof(str_lens)/sizeof(str_lens[0]), &cfg.configuration) != 0) { + return 1; + } + + void *parity = nullptr; + if (parity_start(&cfg, &parity) != 0) { + return 1; + } + + for (int i = 0; i < num_queries; i++) { + if (parity_rpc(parity, rpc_queries[i], strlen(rpc_queries[i]), rpc_response) != 0) { + return 1; + } + } + + // wait until all queries have been answered + while(g_num_queries != 0); + + if (parity != NULL) { + parity_destroy(parity); + } + return 0; +} diff --git a/parity-clib/parity.h b/parity-clib/parity.h index 9be077b4d30..310b8410c5e 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -19,6 +19,8 @@ #include +typedef void (subscribe)(void*, const char*, size_t); + /// Parameters to pass to `parity_start`. struct ParityParams { /// Configuration object, as handled by the `parity_config_*` functions. @@ -31,7 +33,7 @@ struct ParityParams { /// /// The first parameter of the callback is the value of `on_client_restart_cb_custom`. /// The second and third parameters of the callback are the string pointer and length. - void (*on_client_restart_cb)(void* custom, const char* new_chain, size_t new_chain_len); + subscribe *on_client_restart_cb; /// Custom parameter passed to the `on_client_restart_cb` callback as first parameter. void *on_client_restart_cb_custom; @@ -86,21 +88,12 @@ int parity_start(const ParityParams* params, void** out); /// must not call this function. void parity_destroy(void* parity); -/// Performs an RPC request. -/// -/// Blocks the current thread until the request is finished. You are therefore encouraged to spawn -/// a new thread for each RPC request that requires accessing the blockchain. +/// Performs an asynchronous RPC request. /// /// - `rpc` and `len` must contain the JSON string representing the RPC request. -/// - `out_str` and `out_len` point to a buffer where the output JSON result will be stored. If the -/// buffer is not large enough, the function fails. -/// - `out_len` will receive the final length of the string. -/// - On success, the function returns 0. On failure, it returns 1. -/// -/// **Important**: Keep in mind that this function doesn't write any null terminator on the output -/// string. +/// - On success, the function returns a callback with the result in `null` terminated `cstring` /// -int parity_rpc(void* parity, const char* rpc, size_t len, char* out_str, size_t* out_len); +int parity_rpc(void* parity, const char* rpc_query, size_t len, subscribe rpc_response); /// Sets a callback to call when a panic happens in the Rust code. /// @@ -117,7 +110,7 @@ int parity_rpc(void* parity, const char* rpc, size_t len, char* out_str, size_t* /// The callback can be called from any thread and multiple times simultaneously. Make sure that /// your code is thread safe. /// -int parity_set_panic_hook(void (*cb)(void* param, const char* msg, size_t msg_len), void* param); +int parity_set_panic_hook(subscribe panic, void* param); #ifdef __cplusplus } diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs new file mode 100644 index 00000000000..2f57c29913c --- /dev/null +++ b/parity-clib/src/java.rs @@ -0,0 +1,91 @@ +use std::{mem, ptr}; +use std::os::raw::c_void; + +use {Callback, parity_config_from_cli, parity_destroy, parity_start, parity_rpc, ParityParams}; +use jni::{JNIEnv, objects::JClass, objects::JString, sys::jlong, sys::jobjectArray}; + +#[no_mangle] +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_configFromCli(env: JNIEnv, _: JClass, cli: jobjectArray) -> jlong { + let cli_len = env.get_array_length(cli).expect("invalid Java bindings"); + + let mut jni_strings = Vec::with_capacity(cli_len as usize); + let mut opts = Vec::with_capacity(cli_len as usize); + let mut opts_lens = Vec::with_capacity(cli_len as usize); + + for n in 0 .. cli_len { + let elem = env.get_object_array_element(cli, n).expect("invalid Java bindings"); + let elem_str: JString = elem.into(); + match env.get_string(elem_str) { + Ok(s) => { + opts.push(s.as_ptr()); + opts_lens.push(s.to_bytes().len()); + jni_strings.push(s); + }, + Err(err) => { + let _ = env.throw_new("java/lang/Exception", err.to_string()); + return 0 + } + }; + } + + let mut out = ptr::null_mut(); + match parity_config_from_cli(opts.as_ptr(), opts_lens.as_ptr(), cli_len as usize, &mut out) { + 0 => out as usize as jlong, + _ => { + let _ = env.throw_new("java/lang/Exception", "failed to create config object"); + 0 + }, + } +} + +#[no_mangle] +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_build(env: JNIEnv, _: JClass, config: jlong) -> jlong { + let params = ParityParams { + configuration: config as usize as *mut c_void, + .. mem::zeroed() + }; + + let mut out = ptr::null_mut(); + match parity_start(¶ms, &mut out) { + 0 => out as usize as jlong, + _ => { + let _ = env.throw_new("java/lang/Exception", "failed to start Parity"); + 0 + }, + } +} + +#[no_mangle] +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_destroy(_env: JNIEnv, _: JClass, parity: jlong) { + let parity = parity as usize as *mut c_void; + parity_destroy(parity); +} + +#[no_mangle] +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative<'a>( + env: JNIEnv<'a>, + _: JClass, + parity:jlong, + rpc: JString, + callback: Callback, +) +{ + let parity = parity as usize as *mut c_void; + + let rpc = match env.get_string(rpc) { + Ok(s) => s, + Err(err) => { + let _ = env.throw_new("java/lang/Exception", err.to_string()); + return; + }, + }; + + //FIXME: provide "callback" in java fashion + match parity_rpc(parity, rpc.as_ptr(), rpc.to_bytes().len(), callback) { + 0 => (), + _ => { + let _ = env.throw_new("java/lang/Exception", "failed to perform RPC query"); + return; + }, + } +} diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index 7791f97bd5e..7993b2adbf1 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -17,31 +17,50 @@ //! Note that all the structs and functions here are documented in `parity.h`, to avoid //! duplicating documentation. +extern crate futures; +extern crate panic_hook; +extern crate parity_ethereum; +extern crate tokio; +extern crate tokio_current_thread; + #[cfg(feature = "jni")] extern crate jni; -extern crate parity_ethereum; -extern crate panic_hook; +#[cfg(feature = "jni")] +mod java; + +use std::ffi::CString; use std::os::raw::{c_char, c_void, c_int}; -use std::panic; -use std::ptr; -use std::slice; -use std::str; +use std::{panic, ptr, slice, str, thread}; +use std::sync::Arc; +use std::time::Duration; -#[cfg(feature = "jni")] -use std::mem; -#[cfg(feature = "jni")] -use jni::{JNIEnv, objects::JClass, objects::JString, sys::jlong, sys::jobjectArray}; +use futures::{Future, Stream}; +use futures::sync::mpsc; +use futures::future; +use tokio::prelude::Async::Ready; +use futures::Async; +use tokio_current_thread::CurrentThread; +use parity_ethereum::PubSubSession; + +type Callback = Option; + +const QUERY_TIMEOUT: Duration = Duration::from_secs(5*60); #[repr(C)] pub struct ParityParams { pub configuration: *mut c_void, - pub on_client_restart_cb: Option, + pub on_client_restart_cb: Callback, pub on_client_restart_cb_custom: *mut c_void, } #[no_mangle] -pub unsafe extern fn parity_config_from_cli(args: *const *const c_char, args_lens: *const usize, len: usize, output: *mut *mut c_void) -> c_int { +pub unsafe extern fn parity_config_from_cli( + args: *const *const c_char, + args_lens: *const usize, + len: usize, + output: *mut *mut c_void +) -> c_int { panic::catch_unwind(|| { *output = ptr::null_mut(); @@ -124,8 +143,15 @@ pub unsafe extern fn parity_destroy(client: *mut c_void) { } #[no_mangle] -pub unsafe extern fn parity_rpc(client: *mut c_void, query: *const c_char, len: usize, out_str: *mut c_char, out_len: *mut usize) -> c_int { +pub unsafe extern fn parity_rpc( + client: *mut c_void, + query: *const c_char, + len: usize, + callback: Callback, +) -> c_int { + panic::catch_unwind(|| { + let client: &mut parity_ethereum::RunningClient = &mut *(client as *mut parity_ethereum::RunningClient); let query_str = { @@ -136,31 +162,60 @@ pub unsafe extern fn parity_rpc(client: *mut c_void, query: *const c_char, len: } }; - if let Some(output) = client.rpc_query_sync(query_str) { - let q_out_len = output.as_bytes().len(); - if *out_len < q_out_len { - return 1; - } + let callback = match callback { + Some(callback) => Arc::new(callback), + None => return 1, + }; - ptr::copy_nonoverlapping(output.as_bytes().as_ptr(), out_str as *mut u8, q_out_len); - *out_len = q_out_len; - 0 - } else { - 1 - } + let cb = callback.clone(); + + let (tx, mut rx) = mpsc::channel(1); + let session = Some(Arc::new(PubSubSession::new(tx))); + + // FIXME: provide session object here, if we want to support the `PubSub` + let mut future = client.rpc_query(query_str, None).map(move |response| { + let (cstring, len) = match response { + Some(response) => to_cstring(response.into()), + _ => to_cstring("empty response".into()), + }; + cb(ptr::null_mut(), cstring, len); + () + }); + + let _handle = thread::Builder::new() + .name("rpc-subscriber".into()) + .spawn(move || { + let mut current_thread = CurrentThread::new(); + let _ = current_thread.run_timeout(QUERY_TIMEOUT).map_err(|_e| { + let (cstring, len) = to_cstring("timeout".into()); + callback(ptr::null_mut(), cstring, len); + }); + loop { + // let _e = rx.by_ref().for_each(|r| { + // println!("{:?}", r); + // future::ok(()) + // }); + + // if let Ok(Ready(_)) = future.poll() { + // break; + // } + } + }) + .expect("rpc-subscriber thread shouldn't fail; qed"); + 0 }).unwrap_or(1) } #[no_mangle] -pub unsafe extern fn parity_set_panic_hook(callback: extern "C" fn(*mut c_void, *const c_char, usize), param: *mut c_void) { - let cb = CallbackStr(Some(callback), param); +pub unsafe extern fn parity_set_panic_hook(callback: Callback, param: *mut c_void) { + let cb = CallbackStr(callback, param); panic_hook::set_with(move |panic_msg| { cb.call(panic_msg); }); } // Internal structure for handling callbacks that get passed a string. -struct CallbackStr(Option, *mut c_void); +struct CallbackStr(Callback, *mut c_void); unsafe impl Send for CallbackStr {} unsafe impl Sync for CallbackStr {} impl CallbackStr { @@ -171,97 +226,8 @@ impl CallbackStr { } } -#[cfg(feature = "jni")] -#[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_configFromCli(env: JNIEnv, _: JClass, cli: jobjectArray) -> jlong { - let cli_len = env.get_array_length(cli).expect("invalid Java bindings"); - - let mut jni_strings = Vec::with_capacity(cli_len as usize); - let mut opts = Vec::with_capacity(cli_len as usize); - let mut opts_lens = Vec::with_capacity(cli_len as usize); - - for n in 0 .. cli_len { - let elem = env.get_object_array_element(cli, n).expect("invalid Java bindings"); - let elem_str: JString = elem.into(); - match env.get_string(elem_str) { - Ok(s) => { - opts.push(s.as_ptr()); - opts_lens.push(s.to_bytes().len()); - jni_strings.push(s); - }, - Err(err) => { - let _ = env.throw_new("java/lang/Exception", err.to_string()); - return 0 - } - }; - } - - let mut out = ptr::null_mut(); - match parity_config_from_cli(opts.as_ptr(), opts_lens.as_ptr(), cli_len as usize, &mut out) { - 0 => out as usize as jlong, - _ => { - let _ = env.throw_new("java/lang/Exception", "failed to create config object"); - 0 - }, - } -} - -#[cfg(feature = "jni")] -#[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_build(env: JNIEnv, _: JClass, config: jlong) -> jlong { - let params = ParityParams { - configuration: config as usize as *mut c_void, - .. mem::zeroed() - }; - - let mut out = ptr::null_mut(); - match parity_start(¶ms, &mut out) { - 0 => out as usize as jlong, - _ => { - let _ = env.throw_new("java/lang/Exception", "failed to start Parity"); - 0 - }, - } -} - -#[cfg(feature = "jni")] -#[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_destroy(_env: JNIEnv, _: JClass, parity: jlong) { - let parity = parity as usize as *mut c_void; - parity_destroy(parity); -} - -#[cfg(feature = "jni")] -#[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative<'a>(env: JNIEnv<'a>, _: JClass, parity: jlong, rpc: JString) -> JString<'a> { - let parity = parity as usize as *mut c_void; - - let rpc = match env.get_string(rpc) { - Ok(s) => s, - Err(err) => { - let _ = env.throw_new("java/lang/Exception", err.to_string()); - return env.new_string("").expect("Creating an empty string never fails"); - }, - }; - - let mut out_len = 255; - let mut out = [0u8; 256]; - - match parity_rpc(parity, rpc.as_ptr(), rpc.to_bytes().len(), out.as_mut_ptr() as *mut c_char, &mut out_len) { - 0 => (), - _ => { - let _ = env.throw_new("java/lang/Exception", "failed to perform RPC query"); - return env.new_string("").expect("Creating an empty string never fails"); - }, - } - - let out = str::from_utf8(&out[..out_len]) - .expect("parity always generates an UTF-8 RPC response"); - match env.new_string(out) { - Ok(s) => s, - Err(err) => { - let _ = env.throw_new("java/lang/Exception", err.to_string()); - return env.new_string("").expect("Creating an empty string never fails"); - } - } +fn to_cstring(response: Vec) -> (*mut c_char, usize) { + let len = response.len(); + let cstr = CString::new(response).expect("valid string with no null bytes in the middle; qed").into_raw(); + (cstr, len) } diff --git a/parity/lib.rs b/parity/lib.rs index b5a614a7bf4..88330f1ac09 100644 --- a/parity/lib.rs +++ b/parity/lib.rs @@ -121,6 +121,7 @@ use std::alloc::System; pub use self::configuration::Configuration; pub use self::run::RunningClient; +pub use parity_rpc::PubSubSession; #[cfg(feature = "memory_profiling")] #[global_allocator] diff --git a/parity/run.rs b/parity/run.rs index 28536ea6a45..1f2b9ffcfd7 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -41,7 +41,8 @@ use light::Cache as LightDataCache; use miner::external::ExternalMiner; use node_filter::NodeFilter; use parity_runtime::Runtime; -use parity_rpc::{Origin, Metadata, NetworkSettings, informant, is_major_importing}; +use parity_rpc::{Origin, Metadata, NetworkSettings, informant, is_major_importing, PubSubSession, FutureResult, + FutureResponse, FutureOutput}; use updater::{UpdatePolicy, Updater}; use parity_version::version; use ethcore_private_tx::{ProviderConfig, EncryptorConfig, SecretStoreEncryptor}; @@ -875,20 +876,21 @@ enum RunningClientInner { } impl RunningClient { - /// Performs a synchronous RPC query. - /// Blocks execution until the result is ready. - pub fn rpc_query_sync(&self, request: &str) -> Option { + /// Performs an asynchronous RPC query. + pub fn rpc_query(&self, request: &str, session: Option>) + -> FutureResult + { let metadata = Metadata { origin: Origin::CApi, - session: None, + session, }; match self.inner { RunningClientInner::Light { ref rpc, .. } => { - rpc.handle_request_sync(request, metadata) + rpc.handle_request(request, metadata) }, RunningClientInner::Full { ref rpc, .. } => { - rpc.handle_request_sync(request, metadata) + rpc.handle_request(request, metadata) }, } } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index ac0d3dd6f7e..ca5ed28bae3 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -108,6 +108,7 @@ pub mod v1; pub mod tests; +pub use jsonrpc_core::{FutureOutput, FutureResult, FutureResponse, FutureRpcResult}; pub use jsonrpc_pubsub::Session as PubSubSession; pub use ipc::{Server as IpcServer, MetaExtractor as IpcMetaExtractor, RequestContext as IpcRequestContext}; pub use http::{ From 6cd0c136846f24a9cfba2bf26567baa23d998e26 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 14 Nov 2018 20:22:42 +0100 Subject: [PATCH 02/24] feat(seperate bindings for ws and rpc) * Subscribing to websockets for the full-client works --- ethcore/light/src/client/service.rs | 1 + ethcore/src/client/client.rs | 1 + logger/src/lib.rs | 1 + parity-clib/examples/cpp/main.cpp | 133 +++++++++++++++++----------- parity-clib/parity.h | 32 ++++++- parity-clib/src/lib.rs | 98 ++++++++++++++------ 6 files changed, 184 insertions(+), 82 deletions(-) diff --git a/ethcore/light/src/client/service.rs b/ethcore/light/src/client/service.rs index 4b199d6baab..80e65c2f6d8 100644 --- a/ethcore/light/src/client/service.rs +++ b/ethcore/light/src/client/service.rs @@ -86,6 +86,7 @@ impl Service { /// Set the actor to be notified on certain chain events pub fn add_notify(&self, notify: Arc) { + trace!(target: "pubsub", "add lightchain notify"); self.client.add_listener(Arc::downgrade(¬ify)); } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 1b49d009225..aa2408ef53d 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -852,6 +852,7 @@ impl Client { /// Adds an actor to be notified on certain events pub fn add_notify(&self, target: Arc) { + trace!(target: "pubsub", "adding chain notify handler"); self.notify.write().push(Arc::downgrade(&target)); } diff --git a/logger/src/lib.rs b/logger/src/lib.rs index 6068fa39678..ff04395ac83 100644 --- a/logger/src/lib.rs +++ b/logger/src/lib.rs @@ -71,6 +71,7 @@ pub fn setup_log(config: &Config) -> Result, String> { builder.filter(Some("ws"), LevelFilter::Warn); builder.filter(Some("hyper"), LevelFilter::Warn); builder.filter(Some("rustls"), LevelFilter::Error); + builder.filter(Some("pubsub"), LevelFilter::Debug); builder.filter(Some("rpc"), LevelFilter::Trace); // Enable info for others. builder.filter(None, LevelFilter::Info); diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index 9402e7e967d..f31910668d0 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -23,11 +23,19 @@ #include #include -int parity_light(); -int parity_full(); +void* parity_light(); +void* parity_full_run(); +int parity_subscribe_to_websocket(void*); +int parity_rpc_queries(void*); // global variable to keep track of the received rpc responses -static int g_num_queries = 0; +static int g_rpc_counter = 0; + +// list of websocket queries +static const char* ws_queries[] = { + "{\"method\":\"parity_subscribe\",\"params\":[\"eth_getBalance\",[\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\",\"latest\"]],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}" +}; // list of rpc queries static const char* rpc_queries[] = { @@ -40,94 +48,115 @@ static const char* rpc_queries[] = { // callback that gets invoked when the client restarts void on_restart(void*, const char*, size_t) {} -// callback that is invoked on rpc responses +// callback that is invoked on ws responses +void ws_response(void *, const char* response, size_t len) { + printf("ws_callback: %s \r\n", response); +} + +// callback that is invoked on ws responses void rpc_response(void *, const char* response, size_t len) { printf("rpc_callback: %s \r\n", response); - g_num_queries -= 1; + g_rpc_counter -= 1; } int main() { - // run the list of queries in the light client - if (parity_light() != 0) { - printf("parity light client failed\r\n"); + void* parity = parity_full_run(); + + if (parity_rpc_queries(parity)) { + printf("rpc_queries failed\r\n"); + return 1; } - // run the list of queries in the full client - if (parity_full() != 0) { - printf("parity client failed\r\n"); + + if (parity_subscribe_to_websocket(parity)) { + printf("ws_queries failed\r\n"); + return 1; } - return 0; + + if (parity != NULL) { + printf("parity client was err couldn't shutdown\r\n"); + parity_destroy(parity); + } + + return 0; } -int parity_light() { - int num_queries = sizeof(rpc_queries) / sizeof(rpc_queries[0]); - g_num_queries = num_queries; +int parity_rpc_queries(void* parity) { + if (!parity) { + return 1; + } - ParityParams cfg = { - .configuration = nullptr, - .on_client_restart_cb = on_restart, - .on_client_restart_cb_custom = nullptr - }; + size_t num_queries = sizeof(rpc_queries) / sizeof(rpc_queries[0]); + size_t timeout = 1000; + g_rpc_counter = num_queries; - const char* args[] = {"--light", "--no-ipc"}; - size_t str_lens[] = {strlen(args[0]), strlen(args[1])}; - if (parity_config_from_cli(args, str_lens, sizeof(str_lens)/sizeof(str_lens[0]), &cfg.configuration) != 0) { - return 1; + for (int i = 0; i < num_queries; i++) { + if (parity_rpc(parity, rpc_queries[i], strlen(rpc_queries[i]), timeout, rpc_response) != 0) { + return 1; + } } - void *parity = nullptr; - if (parity_start(&cfg, &parity) != 0) { + while(g_rpc_counter != 0); + return 0; +} + + +int parity_subscribe_to_websocket(void* parity) { + if (!parity) { return 1; } + int num_queries = sizeof(ws_queries) / sizeof(ws_queries[0]); + for (int i = 0; i < num_queries; i++) { - if (parity_rpc(parity, rpc_queries[i], strlen(rpc_queries[i]), rpc_response) != 0) { + if (parity_subscribe_ws(parity, ws_queries[i], strlen(ws_queries[i]), ws_response) != 0) { return 1; } } - // wait until all queries have been answered - while(g_num_queries != 0); - - if (parity != NULL) { - parity_destroy(parity); - } - return 0; + // wait forever + while(1); } -int parity_full() { - int num_queries = sizeof(rpc_queries) / sizeof(rpc_queries[0]); - g_num_queries = num_queries; - +void* parity_full_run() { ParityParams cfg = { .configuration = nullptr, .on_client_restart_cb = on_restart, .on_client_restart_cb_custom = nullptr }; - const char* args[] = {"--no-ipc"}; - size_t str_lens[] = {strlen(args[0])}; + const char* args[] = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; + size_t str_lens[] = {strlen(args[0]), strlen(args[1]), strlen(args[2]), strlen(args[3])}; if (parity_config_from_cli(args, str_lens, sizeof(str_lens)/sizeof(str_lens[0]), &cfg.configuration) != 0) { - return 1; + return nullptr; } void *parity = nullptr; if (parity_start(&cfg, &parity) != 0) { - return 1; + return nullptr; } - for (int i = 0; i < num_queries; i++) { - if (parity_rpc(parity, rpc_queries[i], strlen(rpc_queries[i]), rpc_response) != 0) { - return 1; - } - } + return parity; +} - // wait until all queries have been answered - while(g_num_queries != 0); +void* parity_light_run() { + ParityParams cfg = { + .configuration = nullptr, + .on_client_restart_cb = on_restart, + .on_client_restart_cb_custom = nullptr + }; - if (parity != NULL) { - parity_destroy(parity); + const char* args[] = {"--light", "--no-ipc","--chain", "kovan", "--jsonrpc-apis=all"}; + size_t str_lens[] = {strlen(args[0]), strlen(args[1]), strlen(args[2]), strlen(args[3]), strlen(args[4])}; + + if (parity_config_from_cli(args, str_lens, sizeof(str_lens)/sizeof(str_lens[0]), &cfg.configuration) != 0) { + return nullptr; } - return 0; + + void *parity = nullptr; + if (parity_start(&cfg, &parity) != 0) { + return nullptr; + } + return parity; } diff --git a/parity-clib/parity.h b/parity-clib/parity.h index 310b8410c5e..def097c0f9e 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -88,12 +88,36 @@ int parity_start(const ParityParams* params, void** out); /// must not call this function. void parity_destroy(void* parity); -/// Performs an asynchronous RPC request. +/// Performs an asynchronous RPC request running in a background thread for at most X milliseconds /// -/// - `rpc` and `len` must contain the JSON string representing the RPC request. -/// - On success, the function returns a callback with the result in `null` terminated `cstring` +/// - parity : Reference to the running parity client +/// - rpc_query : JSON encoded string representing the RPC request. +/// - len : Length of the RPC query +/// - timeout_ms : Maximum time that request is waiting for a response +/// - response : Callback to invoke when the query gets answered. It will respond with /// -int parity_rpc(void* parity, const char* rpc_query, size_t len, subscribe rpc_response); +/// 1) A JSON encoded string with the result +/// 2) A string "empty", (got an empty response) +// 3) A string "timeout", (the query timed-out) +/// +/// - On success : The parity client reference and the query string were valid +/// - On error : The parity client reference and the query string were not valid +/// +int parity_rpc(void* parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, subscribe response); + + +/// Subscribes to a specific websocket event +/// FIXME: provide functionality to cancel a "subscription" +/// +/// - parity : Reference to the running parity client +/// - ws_query : JSON encoded string representing the websocket and which event to subscribe to +/// - len : Length of the queury +/// - response : Callback to invoke when a websocket event occured +/// +/// - On success : The function returns a callback with a JSON encoded string +/// - On error : The function returns a callback with the error (empty or timeout) +/// +int parity_subscribe_ws(void* parity, const char* ws_query, size_t len, subscribe response); /// Sets a callback to call when a panic happens in the Rust code. /// diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index 7993b2adbf1..76752a9b460 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -37,16 +37,11 @@ use std::time::Duration; use futures::{Future, Stream}; use futures::sync::mpsc; -use futures::future; -use tokio::prelude::Async::Ready; -use futures::Async; -use tokio_current_thread::CurrentThread; use parity_ethereum::PubSubSession; +use tokio_current_thread::CurrentThread; type Callback = Option; -const QUERY_TIMEOUT: Duration = Duration::from_secs(5*60); - #[repr(C)] pub struct ParityParams { pub configuration: *mut c_void, @@ -147,6 +142,7 @@ pub unsafe extern fn parity_rpc( client: *mut c_void, query: *const c_char, len: usize, + timeout_ms: usize, callback: Callback, ) -> c_int { @@ -169,11 +165,7 @@ pub unsafe extern fn parity_rpc( let cb = callback.clone(); - let (tx, mut rx) = mpsc::channel(1); - let session = Some(Arc::new(PubSubSession::new(tx))); - - // FIXME: provide session object here, if we want to support the `PubSub` - let mut future = client.rpc_query(query_str, None).map(move |response| { + let query = client.rpc_query(query_str, None).map(move |response| { let (cstring, len) = match response { Some(response) => to_cstring(response.into()), _ => to_cstring("empty response".into()), @@ -183,29 +175,83 @@ pub unsafe extern fn parity_rpc( }); let _handle = thread::Builder::new() - .name("rpc-subscriber".into()) + .name("rpc-query".into()) .spawn(move || { let mut current_thread = CurrentThread::new(); - let _ = current_thread.run_timeout(QUERY_TIMEOUT).map_err(|_e| { - let (cstring, len) = to_cstring("timeout".into()); - callback(ptr::null_mut(), cstring, len); - }); - loop { - // let _e = rx.by_ref().for_each(|r| { - // println!("{:?}", r); - // future::ok(()) - // }); - - // if let Ok(Ready(_)) = future.poll() { - // break; - // } - } + current_thread.spawn(query); + let _ = current_thread.run_timeout(Duration::from_millis(timeout_ms as u64)) + .map_err(|_e| { + let (cstring, len) = to_cstring("timeout".into()); + callback(ptr::null_mut(), cstring, len); + }); }) .expect("rpc-subscriber thread shouldn't fail; qed"); 0 }).unwrap_or(1) } + +#[no_mangle] +pub unsafe extern fn parity_subscribe_ws( + client: *mut c_void, + query: *const c_char, + len: usize, + callback: Callback, +) -> c_int { + + panic::catch_unwind(|| { + let client: &mut parity_ethereum::RunningClient = &mut *(client as *mut parity_ethereum::RunningClient); + + let query_str = { + let string = slice::from_raw_parts(query as *const u8, len); + match str::from_utf8(string) { + Ok(a) => a, + Err(_) => return 1, + } + }; + + let callback = match callback { + Some(callback) => Arc::new(callback), + None => return 1, + }; + + let cb = callback.clone(); + let (tx, mut rx) = mpsc::channel(1); + let session = Arc::new(PubSubSession::new(tx)); + + // spawn the query into a threadpool + let _ = tokio::run( + client.rpc_query(query_str, Some(session.clone())).map(move |response| { + let (cstring, len) = match response { + Some(response) => to_cstring(response.into()), + _ => to_cstring("empty response".into()), + }; + cb(ptr::null_mut(), cstring, len); + () + }) + ); + + //TODO: figure out how to cancel thread + // This will run forever + let _handle = thread::Builder::new() + .name("ws-subscriber".into()) + .spawn(move || { + Arc::downgrade(&session); + loop { + for response in rx.by_ref().wait() { + if let Ok(r) = response { + let (cstring, len) = to_cstring(r.into()); + callback(ptr::null_mut(), cstring, len); + } + } + } + }) + .expect("rpc-subscriber thread shouldn't fail; qed"); + 0 + }) + .unwrap_or(0) +} + #[no_mangle] pub unsafe extern fn parity_set_panic_hook(callback: Callback, param: *mut c_void) { let cb = CallbackStr(callback, param); From 78664a02e568d0765deb901e036bf71f422dd513 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 17 Nov 2018 01:09:08 +0100 Subject: [PATCH 03/24] feat(c binding unsubscribe_from_websocket) --- parity-clib/examples/cpp/main.cpp | 95 +++++++++---- parity-clib/parity.h | 36 +++-- parity-clib/src/lib.rs | 215 +++++++++++++++++------------- 3 files changed, 215 insertions(+), 131 deletions(-) diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index f31910668d0..8b56c094dfe 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -22,20 +22,20 @@ #include #include #include +#include -void* parity_light(); +void* parity_light_run(); void* parity_full_run(); int parity_subscribe_to_websocket(void*); int parity_rpc_queries(void*); +const int SUBSCRIPTION_ID_LEN = 18; + // global variable to keep track of the received rpc responses static int g_rpc_counter = 0; -// list of websocket queries -static const char* ws_queries[] = { - "{\"method\":\"parity_subscribe\",\"params\":[\"eth_getBalance\",[\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\",\"latest\"]],\"id\":1,\"jsonrpc\":\"2.0\"}", - "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}" -}; +// global string for callbacks +static char g_str[60]; // list of rpc queries static const char* rpc_queries[] = { @@ -49,32 +49,56 @@ static const char* rpc_queries[] = { void on_restart(void*, const char*, size_t) {} // callback that is invoked on ws responses -void ws_response(void *, const char* response, size_t len) { - printf("ws_callback: %s \r\n", response); +void ws_response(void* _unused, const char* response, size_t len) { + printf("ws_response: %s\r\n", response); + std::regex is_subscription ("\\{\"jsonrpc\":\"2.0\",\"result\":\"0[xX][a-fA-F0-9]{16}\",\"id\":1\\}"); + if (std::regex_match(response, is_subscription) == true) { + strncpy(g_str, response, 55); + } } // callback that is invoked on ws responses -void rpc_response(void *, const char* response, size_t len) { - printf("rpc_callback: %s \r\n", response); +void rpc_response(void* _unused, const char* response, size_t len) { + printf("rpc_response: %s\r\n", response); g_rpc_counter -= 1; } int main() { - void* parity = parity_full_run(); + // run full-client + { + void* parity = parity_full_run(); + if (parity_rpc_queries(parity)) { + printf("rpc_queries failed\r\n"); + return 1; + } - if (parity_rpc_queries(parity)) { - printf("rpc_queries failed\r\n"); - return 1; - } + if (parity_subscribe_to_websocket(parity)) { + printf("ws_queries failed\r\n"); + return 1; + } - if (parity_subscribe_to_websocket(parity)) { - printf("ws_queries failed\r\n"); - return 1; + if (parity != NULL) { + parity_destroy(parity); + } } - if (parity != NULL) { - printf("parity client was err couldn't shutdown\r\n"); - parity_destroy(parity); + // run light-client + { + void* parity = parity_light_run(); + + if (parity_rpc_queries(parity)) { + printf("rpc_queries failed\r\n"); + return 1; + } + + if (parity_subscribe_to_websocket(parity)) { + printf("ws_queries failed\r\n"); + return 1; + } + + if (parity != NULL) { + parity_destroy(parity); + } } return 0; @@ -106,16 +130,29 @@ int parity_subscribe_to_websocket(void* parity) { return 1; } - int num_queries = sizeof(ws_queries) / sizeof(ws_queries[0]); + size_t timeout = 1000; + int num_queries = 1; - for (int i = 0; i < num_queries; i++) { - if (parity_subscribe_ws(parity, ws_queries[i], strlen(ws_queries[i]), ws_response) != 0) { + char subscribe[] = "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; + char unsubscribe[] = "{\"method\":\"eth_unsubscribe\",\"params\":[\"0x1234567891234567\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; + + const void *const handle = parity_subscribe_ws(parity, subscribe, strlen(subscribe), ws_response); + + if (!handle) { + return 1; + } + + while(g_str[0] == 0); + sleep(60); + + // Replace subscription_id with the id we got in the callback + // (this is not a good practice use your favorite JSON parser) + strncpy(&unsubscribe[39], &g_str[27], SUBSCRIPTION_ID_LEN); + if (parity_unsubscribe_ws(parity, handle, unsubscribe, strlen(unsubscribe), timeout, ws_response) != 0) { return 1; - } } - // wait forever - while(1); + return 0; } void* parity_full_run() { @@ -128,7 +165,7 @@ void* parity_full_run() { const char* args[] = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; size_t str_lens[] = {strlen(args[0]), strlen(args[1]), strlen(args[2]), strlen(args[3])}; - if (parity_config_from_cli(args, str_lens, sizeof(str_lens)/sizeof(str_lens[0]), &cfg.configuration) != 0) { + if (parity_config_from_cli(args, str_lens, sizeof(str_lens) / sizeof(str_lens[0]), &cfg.configuration) != 0) { return nullptr; } @@ -150,7 +187,7 @@ void* parity_light_run() { const char* args[] = {"--light", "--no-ipc","--chain", "kovan", "--jsonrpc-apis=all"}; size_t str_lens[] = {strlen(args[0]), strlen(args[1]), strlen(args[2]), strlen(args[3]), strlen(args[4])}; - if (parity_config_from_cli(args, str_lens, sizeof(str_lens)/sizeof(str_lens[0]), &cfg.configuration) != 0) { + if (parity_config_from_cli(args, str_lens, sizeof(str_lens) / sizeof(str_lens[0]), &cfg.configuration) != 0) { return nullptr; } diff --git a/parity-clib/parity.h b/parity-clib/parity.h index def097c0f9e..a6a9465564e 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -19,7 +19,7 @@ #include -typedef void (subscribe)(void*, const char*, size_t); +typedef void (callback)(void*, const char*, size_t); /// Parameters to pass to `parity_start`. struct ParityParams { @@ -33,7 +33,7 @@ struct ParityParams { /// /// The first parameter of the callback is the value of `on_client_restart_cb_custom`. /// The second and third parameters of the callback are the string pointer and length. - subscribe *on_client_restart_cb; + callback *on_client_restart_cb; /// Custom parameter passed to the `on_client_restart_cb` callback as first parameter. void *on_client_restart_cb_custom; @@ -59,7 +59,7 @@ extern "C" { /// const char *args[] = {"--light", "--can-restart"}; /// size_t str_lens[] = {7, 13}; /// if (parity_config_from_cli(args, str_lens, 2, &cfg) != 0) { -/// return 1; +/// return 1; /// } /// ``` /// @@ -103,21 +103,37 @@ void parity_destroy(void* parity); /// - On success : The parity client reference and the query string were valid /// - On error : The parity client reference and the query string were not valid /// -int parity_rpc(void* parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, subscribe response); +int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, callback response); -/// Subscribes to a specific websocket event -/// FIXME: provide functionality to cancel a "subscription" +/// Subscribes to the specified websocket event /// /// - parity : Reference to the running parity client /// - ws_query : JSON encoded string representing the websocket and which event to subscribe to /// - len : Length of the queury /// - response : Callback to invoke when a websocket event occured /// -/// - On success : The function returns a callback with a JSON encoded string -/// - On error : The function returns a callback with the error (empty or timeout) +/// - On success : The function returns the underlying pointer to a atomic reference counter of the session object +// which should later be used cancel the subscription +/// - On error : The function returns a null pointer /// -int parity_subscribe_ws(void* parity, const char* ws_query, size_t len, subscribe response); +const void *const parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, callback response); + +/// Unsubscribes from a specific websocket event. Caution this function consumes the session object and must only be +/// exactly per session. +/// +/// - parity : Reference to the running parity client +/// - session : Underlying pointer to an atomic reference counter +/// - ws_query : JSON encoded string representing the websocket event to unsubscribe from +/// - len : Length of the query +/// - timeout : Maximum time in milliseconds to wait for a response +/// - response : Callback to invoke when a websocket event is received +/// +/// - On success : The function return 0 +/// - On error : The function returns non-zero +// +int parity_unsubscribe_ws(const void *const parity, const void *const session, const char* ws_query, + size_t len, size_t timeout, callback response); /// Sets a callback to call when a panic happens in the Rust code. /// @@ -134,7 +150,7 @@ int parity_subscribe_ws(void* parity, const char* ws_query, size_t len, subscrib /// The callback can be called from any thread and multiple times simultaneously. Make sure that /// your code is thread safe. /// -int parity_set_panic_hook(subscribe panic, void* param); +int parity_set_panic_hook(callback panic, void* param); #ifdef __cplusplus } diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index 76752a9b460..98629591a21 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -37,10 +37,19 @@ use std::time::Duration; use futures::{Future, Stream}; use futures::sync::mpsc; -use parity_ethereum::PubSubSession; +use parity_ethereum::{PubSubSession, RunningClient}; use tokio_current_thread::CurrentThread; type Callback = Option; +type CheckedCallback = Arc; +type CheckedQuery<'a> = (&'a RunningClient, &'static str, CheckedCallback); + +pub mod error { + pub const EMPTY: &str = r#"{"jsonrpc":"2.0","result":"null","id":1}"#; + pub const TIMEOUT: &str = r#"{"jsonrpc":"2.0","result":"timeout","id":1}"#; + pub const SUBSCRIBE: &str = r#"{"jsonrpc":"2.0","result":"subcribe_fail","id":1}"#; +} + #[repr(C)] pub struct ParityParams { @@ -73,7 +82,6 @@ pub unsafe extern fn parity_config_from_cli( Err(_) => return 1, }; } - args }; @@ -132,124 +140,147 @@ pub unsafe extern fn parity_start(cfg: *const ParityParams, output: *mut *mut c_ #[no_mangle] pub unsafe extern fn parity_destroy(client: *mut c_void) { let _ = panic::catch_unwind(|| { - let client = Box::from_raw(client as *mut parity_ethereum::RunningClient); + let client = Box::from_raw(client as *mut RunningClient); client.shutdown(); }); } + +unsafe fn parity_rpc_query_checker<'a>(client: *const c_void, query: *const c_char, len: usize, callback: Callback) + -> Option> +{ + let callback = callback?; + let query_str = { + let string = slice::from_raw_parts(query as *const u8, len); + str::from_utf8(string).ok()? + }; + let client: &RunningClient = &*(client as *const RunningClient); + + Some((client, query_str, Arc::new(callback))) +} + +fn parity_rpc_dispatcher( + client: &RunningClient, + query: &str, + session: Option>, + callback: CheckedCallback, + timeout_ms: usize, + name: &str) { + + let cb = callback.clone(); + let client = client as &RunningClient; + let query = client.rpc_query(query, session).map(move |response| { + let (cstr, len) = match response { + Some(response) => to_cstring(response.as_bytes()), + None => to_cstring(error::EMPTY.as_bytes()), + }; + cb(ptr::null_mut(), cstr, len); + () + }); + + let _handle = thread::Builder::new() + .name(name.into()) + .spawn(move || { + let mut current_thread = CurrentThread::new(); + current_thread.spawn(query); + let _ = current_thread.run_timeout(Duration::from_millis(timeout_ms as u64)) + .map_err(|_e| { + let (cstr, len) = to_cstring(error::TIMEOUT.as_bytes()); + callback(ptr::null_mut(), cstr, len); + }); + }) + .expect("rpc-query thread shouldn't fail; qed"); +} + #[no_mangle] pub unsafe extern fn parity_rpc( - client: *mut c_void, + client: *const c_void, query: *const c_char, len: usize, timeout_ms: usize, callback: Callback, ) -> c_int { - panic::catch_unwind(|| { - - let client: &mut parity_ethereum::RunningClient = &mut *(client as *mut parity_ethereum::RunningClient); - - let query_str = { - let string = slice::from_raw_parts(query as *const u8, len); - match str::from_utf8(string) { - Ok(a) => a, - Err(_) => return 1, - } - }; - - let callback = match callback { - Some(callback) => Arc::new(callback), - None => return 1, - }; - - let cb = callback.clone(); - - let query = client.rpc_query(query_str, None).map(move |response| { - let (cstring, len) = match response { - Some(response) => to_cstring(response.into()), - _ => to_cstring("empty response".into()), - }; - cb(ptr::null_mut(), cstring, len); - () - }); - - let _handle = thread::Builder::new() - .name("rpc-query".into()) - .spawn(move || { - let mut current_thread = CurrentThread::new(); - current_thread.spawn(query); - let _ = current_thread.run_timeout(Duration::from_millis(timeout_ms as u64)) - .map_err(|_e| { - let (cstring, len) = to_cstring("timeout".into()); - callback(ptr::null_mut(), cstring, len); - }); - }) - .expect("rpc-subscriber thread shouldn't fail; qed"); - 0 + if let Some((client, query, callback)) = parity_rpc_query_checker(client, query, len, callback) { + parity_rpc_dispatcher(client, query, None, callback, timeout_ms, "rpc-query"); + 0 + } else { + 1 + } }).unwrap_or(1) } #[no_mangle] pub unsafe extern fn parity_subscribe_ws( - client: *mut c_void, + client: *const c_void, query: *const c_char, len: usize, callback: Callback, -) -> c_int { - - panic::catch_unwind(|| { - let client: &mut parity_ethereum::RunningClient = &mut *(client as *mut parity_ethereum::RunningClient); - - let query_str = { - let string = slice::from_raw_parts(query as *const u8, len); - match str::from_utf8(string) { - Ok(a) => a, - Err(_) => return 1, - } - }; - - let callback = match callback { - Some(callback) => Arc::new(callback), - None => return 1, - }; +) -> *const c_void { - let cb = callback.clone(); - let (tx, mut rx) = mpsc::channel(1); - let session = Arc::new(PubSubSession::new(tx)); + panic::catch_unwind(|| { + if let Some((client, query, callback)) = parity_rpc_query_checker(client, query, len, callback) { + let (tx, mut rx) = mpsc::channel(1); + let session = Arc::new(PubSubSession::new(tx)); + let ffi_session = session.clone(); + let query = client.rpc_query(query, Some(session.clone())); + + let _handle = thread::Builder::new() + .name("ws-subscriber".into()) + .spawn(move || { + + // wait for subscription ID + match query.wait() { + Ok(Some(response)) => { + let (cstr, len) = to_cstring(response.as_bytes()); + callback(ptr::null_mut(), cstr, len); + } + // subscription failed return error and exit + _ => { + let (cstr, len) = to_cstring(error::SUBSCRIBE.as_bytes()); + callback(ptr::null_mut(), cstr, len); + return; + } + }; - // spawn the query into a threadpool - let _ = tokio::run( - client.rpc_query(query_str, Some(session.clone())).map(move |response| { - let (cstring, len) = match response { - Some(response) => to_cstring(response.into()), - _ => to_cstring("empty response".into()), - }; - cb(ptr::null_mut(), cstring, len); - () - }) - ); - - //TODO: figure out how to cancel thread - // This will run forever - let _handle = thread::Builder::new() - .name("ws-subscriber".into()) - .spawn(move || { - Arc::downgrade(&session); - loop { - for response in rx.by_ref().wait() { - if let Ok(r) = response { - let (cstring, len) = to_cstring(r.into()); - callback(ptr::null_mut(), cstring, len); + while Arc::strong_count(&session) > 1 { + for response in rx.by_ref().wait() { + if let Ok(r) = response { + let (cstring, len) = to_cstring(r.as_bytes()); + callback(ptr::null_mut(), cstring, len); } } } }) .expect("rpc-subscriber thread shouldn't fail; qed"); - 0 + + Arc::into_raw(ffi_session) as *const c_void + } else { + ptr::null() + } }) - .unwrap_or(0) + .unwrap_or(ptr::null()) +} + +#[no_mangle] +pub unsafe extern fn parity_unsubscribe_ws( + client: *const c_void, + session: *const c_void, + query: *const c_char, + len: usize, + timeout_ms: usize, + callback: Callback, +) -> c_int { + panic::catch_unwind(|| { + if let Some((client, query, callback)) = parity_rpc_query_checker(client, query, len, callback) { + let session = Some(Arc::from_raw(session as *const PubSubSession)); + parity_rpc_dispatcher(client, query, session, callback, timeout_ms, "ws-unsubscribe"); + 0 + } else { + 1 + } + }).unwrap_or(1) } #[no_mangle] @@ -272,7 +303,7 @@ impl CallbackStr { } } -fn to_cstring(response: Vec) -> (*mut c_char, usize) { +fn to_cstring(response: &[u8]) -> (*const c_char, usize) { let len = response.len(); let cstr = CString::new(response).expect("valid string with no null bytes in the middle; qed").into_raw(); (cstr, len) From bf450a30ee06838df7e4c1566ee5ed09a1d3624d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 17 Nov 2018 01:40:32 +0100 Subject: [PATCH 04/24] fix(tests): tweak CMake build config * Enforce C+11 --- ethcore/light/src/client/service.rs | 1 - ethcore/src/client/client.rs | 1 - parity-clib/examples/cpp/CMakeLists.txt | 5 ++--- parity-clib/examples/cpp/main.cpp | 7 +++---- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/ethcore/light/src/client/service.rs b/ethcore/light/src/client/service.rs index 80e65c2f6d8..4b199d6baab 100644 --- a/ethcore/light/src/client/service.rs +++ b/ethcore/light/src/client/service.rs @@ -86,7 +86,6 @@ impl Service { /// Set the actor to be notified on certain chain events pub fn add_notify(&self, notify: Arc) { - trace!(target: "pubsub", "add lightchain notify"); self.client.add_listener(Arc::downgrade(¬ify)); } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index aa2408ef53d..1b49d009225 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -852,7 +852,6 @@ impl Client { /// Adds an actor to be notified on certain events pub fn add_notify(&self, target: Arc) { - trace!(target: "pubsub", "adding chain notify handler"); self.notify.write().push(Arc::downgrade(&target)); } diff --git a/parity-clib/examples/cpp/CMakeLists.txt b/parity-clib/examples/cpp/CMakeLists.txt index 69b58c211a1..ce68915884b 100644 --- a/parity-clib/examples/cpp/CMakeLists.txt +++ b/parity-clib/examples/cpp/CMakeLists.txt @@ -1,8 +1,7 @@ cmake_minimum_required(VERSION 3.5) include(ExternalProject) - -include_directories("${CMAKE_SOURCE_DIR}/../../../parity-clib") - +include_directories("${CMAKE_SOURCE_DIR}/../../parity-clib") +set (CMAKE_CXX_STANDARD 11) # Enfore C++11 add_executable(parity-example main.cpp) ExternalProject_Add( diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index 8b56c094dfe..c04b3e4ac71 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -// Note, `nullptr` requires a C++ compiler with C+11 support - #include #include #include @@ -77,7 +75,7 @@ int main() { return 1; } - if (parity != NULL) { + if (parity != nullptr) { parity_destroy(parity); } } @@ -96,7 +94,7 @@ int main() { return 1; } - if (parity != NULL) { + if (parity != nullptr) { parity_destroy(parity); } } @@ -132,6 +130,7 @@ int parity_subscribe_to_websocket(void* parity) { size_t timeout = 1000; int num_queries = 1; + g_str[0] = 0; char subscribe[] = "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; char unsubscribe[] = "{\"method\":\"eth_unsubscribe\",\"params\":[\"0x1234567891234567\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; From 1cca51447e97b3a7138429cf95f8096cf19827e9 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Nov 2018 15:38:00 +0100 Subject: [PATCH 05/24] refactor(parity-cpp-example) : `cpp:ify` --- parity-clib/examples/cpp/main.cpp | 60 +++++++++++++++++-------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index c04b3e4ac71..ca7ed97da43 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -14,13 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -#include -#include -#include -#include -#include +#include #include #include +#include +#include void* parity_light_run(); void* parity_full_run(); @@ -33,10 +31,10 @@ const int SUBSCRIPTION_ID_LEN = 18; static int g_rpc_counter = 0; // global string for callbacks -static char g_str[60]; +static std::string g_str; // list of rpc queries -static const char* rpc_queries[] = { +static std::vector rpc_queries { "{\"method\":\"parity_versionInfo\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}", "{\"method\":\"eth_getTransactionReceipt\",\"params\":[\"0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913fff\"],\"id\":1,\"jsonrpc\":\"2.0\"}", "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\"}],\"id\":1,\"jsonrpc\":\"2.0\"}", @@ -50,8 +48,9 @@ void on_restart(void*, const char*, size_t) {} void ws_response(void* _unused, const char* response, size_t len) { printf("ws_response: %s\r\n", response); std::regex is_subscription ("\\{\"jsonrpc\":\"2.0\",\"result\":\"0[xX][a-fA-F0-9]{16}\",\"id\":1\\}"); - if (std::regex_match(response, is_subscription) == true) { - strncpy(g_str, response, 55); + // assume only one subscription is used + if (std::regex_match(response, is_subscription) == true && g_str.empty()) { + g_str = response; } } @@ -107,13 +106,12 @@ int parity_rpc_queries(void* parity) { return 1; } - size_t num_queries = sizeof(rpc_queries) / sizeof(rpc_queries[0]); + size_t num_queries = rpc_queries.size(); size_t timeout = 1000; g_rpc_counter = num_queries; - - for (int i = 0; i < num_queries; i++) { - if (parity_rpc(parity, rpc_queries[i], strlen(rpc_queries[i]), timeout, rpc_response) != 0) { + for (auto query : rpc_queries) { + if (parity_rpc(parity, query.c_str(), query.length(), timeout, rpc_response) != 0) { return 1; } } @@ -130,24 +128,24 @@ int parity_subscribe_to_websocket(void* parity) { size_t timeout = 1000; int num_queries = 1; - g_str[0] = 0; + g_str.clear(); - char subscribe[] = "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; - char unsubscribe[] = "{\"method\":\"eth_unsubscribe\",\"params\":[\"0x1234567891234567\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; + std::string subscribe = "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; + std::string unsubscribe = "{\"method\":\"eth_unsubscribe\",\"params\":[\"0x1234567891234567\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; - const void *const handle = parity_subscribe_ws(parity, subscribe, strlen(subscribe), ws_response); + const void *const handle = parity_subscribe_ws(parity, subscribe.c_str(), subscribe.length(), ws_response); if (!handle) { return 1; } - while(g_str[0] == 0); - sleep(60); + while(g_str.empty()); + std::this_thread::sleep_for(std::chrono::seconds(60)); // Replace subscription_id with the id we got in the callback // (this is not a good practice use your favorite JSON parser) - strncpy(&unsubscribe[39], &g_str[27], SUBSCRIPTION_ID_LEN); - if (parity_unsubscribe_ws(parity, handle, unsubscribe, strlen(unsubscribe), timeout, ws_response) != 0) { + unsubscribe.replace(39, SUBSCRIPTION_ID_LEN, g_str, 27, SUBSCRIPTION_ID_LEN); + if (parity_unsubscribe_ws(parity, handle, unsubscribe.c_str(), unsubscribe.length(), timeout, ws_response) != 0) { return 1; } @@ -161,10 +159,14 @@ void* parity_full_run() { .on_client_restart_cb_custom = nullptr }; - const char* args[] = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; - size_t str_lens[] = {strlen(args[0]), strlen(args[1]), strlen(args[2]), strlen(args[3])}; + std::vector args = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; + std::vector strs_len; + + for (auto arg: args) { + strs_len.push_back(std::strlen(arg)); + } - if (parity_config_from_cli(args, str_lens, sizeof(str_lens) / sizeof(str_lens[0]), &cfg.configuration) != 0) { + if (parity_config_from_cli(&args[0], &strs_len[0], args.size(), &cfg.configuration) != 0) { return nullptr; } @@ -183,10 +185,14 @@ void* parity_light_run() { .on_client_restart_cb_custom = nullptr }; - const char* args[] = {"--light", "--no-ipc","--chain", "kovan", "--jsonrpc-apis=all"}; - size_t str_lens[] = {strlen(args[0]), strlen(args[1]), strlen(args[2]), strlen(args[3]), strlen(args[4])}; + std::vector args = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; + std::vector str_lens; + + for (auto arg: args) { + str_lens.push_back(std::strlen(arg)); + } - if (parity_config_from_cli(args, str_lens, sizeof(str_lens) / sizeof(str_lens[0]), &cfg.configuration) != 0) { + if (parity_config_from_cli(&args[0], &str_lens[0], str_lens.size(), &cfg.configuration) != 0) { return nullptr; } From 79c0576402f7d8d324d8c6b87ed24577e4fe533a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Nov 2018 16:38:37 +0100 Subject: [PATCH 06/24] fix(typedefs) : revert typedefs parity-clib --- parity-clib/parity.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/parity-clib/parity.h b/parity-clib/parity.h index a6a9465564e..775a443572c 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -19,8 +19,6 @@ #include -typedef void (callback)(void*, const char*, size_t); - /// Parameters to pass to `parity_start`. struct ParityParams { /// Configuration object, as handled by the `parity_config_*` functions. @@ -33,7 +31,7 @@ struct ParityParams { /// /// The first parameter of the callback is the value of `on_client_restart_cb_custom`. /// The second and third parameters of the callback are the string pointer and length. - callback *on_client_restart_cb; + void (*on_client_restart_cb)(void* custom, const char* new_chain, size_t new_chain_len); /// Custom parameter passed to the `on_client_restart_cb` callback as first parameter. void *on_client_restart_cb_custom; @@ -103,7 +101,8 @@ void parity_destroy(void* parity); /// - On success : The parity client reference and the query string were valid /// - On error : The parity client reference and the query string were not valid /// -int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, callback response); +int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, + void (*subscribe)(void* custom, const char* response, size_t len)); /// Subscribes to the specified websocket event @@ -117,7 +116,8 @@ int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, // which should later be used cancel the subscription /// - On error : The function returns a null pointer /// -const void *const parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, callback response); +const void *const parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, + void (*subscribe)(void* custom, const char* response, size_t len)); /// Unsubscribes from a specific websocket event. Caution this function consumes the session object and must only be /// exactly per session. @@ -133,7 +133,7 @@ const void *const parity_subscribe_ws(const void *const parity, const char* ws_q /// - On error : The function returns non-zero // int parity_unsubscribe_ws(const void *const parity, const void *const session, const char* ws_query, - size_t len, size_t timeout, callback response); + size_t len, size_t timeout, void (*unsubscribe)(void* custom, const char* response, size_t len)); /// Sets a callback to call when a panic happens in the Rust code. /// @@ -150,7 +150,7 @@ int parity_unsubscribe_ws(const void *const parity, const void *const session, c /// The callback can be called from any thread and multiple times simultaneously. Make sure that /// your code is thread safe. /// -int parity_set_panic_hook(callback panic, void* param); +int parity_set_panic_hook(void (*cb)(void* param, const char* msg, size_t msg_len), void* param); #ifdef __cplusplus } From ac159607e5ab7bd825a57fdd7f0437dde3404556 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 19 Nov 2018 17:52:58 +0100 Subject: [PATCH 07/24] docs(nits) --- parity-clib/parity.h | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/parity-clib/parity.h b/parity-clib/parity.h index 775a443572c..ba8e0960eb5 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -92,45 +92,42 @@ void parity_destroy(void* parity); /// - rpc_query : JSON encoded string representing the RPC request. /// - len : Length of the RPC query /// - timeout_ms : Maximum time that request is waiting for a response -/// - response : Callback to invoke when the query gets answered. It will respond with +/// - response : Callback to invoke when the query gets answered. It will respond with a JSON encoded the string +// with the result /// -/// 1) A JSON encoded string with the result -/// 2) A string "empty", (got an empty response) -// 3) A string "timeout", (the query timed-out) -/// -/// - On success : The parity client reference and the query string were valid -/// - On error : The parity client reference and the query string were not valid +/// - On success : The parity client reference and the query string were valid +/// - On error : The parity client reference and the query string were not valid /// int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, void (*subscribe)(void* custom, const char* response, size_t len)); -/// Subscribes to the specified websocket event +/// Subscribes to a specific websocket event that will run until it is canceled /// /// - parity : Reference to the running parity client -/// - ws_query : JSON encoded string representing the websocket and which event to subscribe to -/// - len : Length of the queury -/// - response : Callback to invoke when a websocket event occured +/// - ws_query : JSON encoded string representing the websocket event to subscribe to +/// - len : Length of the query +/// - response : Callback to invoke when a websocket event occurs /// -/// - On success : The function returns the underlying pointer to a atomic reference counter of the session object -// which should later be used cancel the subscription +/// - On success : The function returns an object to the current session +/// which can be used cancel the subscription /// - On error : The function returns a null pointer /// const void *const parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, void (*subscribe)(void* custom, const char* response, size_t len)); /// Unsubscribes from a specific websocket event. Caution this function consumes the session object and must only be -/// exactly per session. +/// used exactly once per session. /// /// - parity : Reference to the running parity client /// - session : Underlying pointer to an atomic reference counter /// - ws_query : JSON encoded string representing the websocket event to unsubscribe from /// - len : Length of the query /// - timeout : Maximum time in milliseconds to wait for a response -/// - response : Callback to invoke when a websocket event is received +/// - response : Callback to invoke when the current session has been terminated /// -/// - On success : The function return 0 -/// - On error : The function returns non-zero +/// - On success : The function returns 0 +/// - On error : The function returns non-zero // int parity_unsubscribe_ws(const void *const parity, const void *const session, const char* ws_query, size_t len, size_t timeout, void (*unsubscribe)(void* custom, const char* response, size_t len)); From 9c2d49eb09e142fe786402bb15cbf6d60dd4767e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Nov 2018 13:06:57 +0100 Subject: [PATCH 08/24] fix(simplify websocket_unsubscribe) --- parity-clib/examples/cpp/main.cpp | 22 +++++------------ parity-clib/parity.h | 15 +++-------- parity-clib/src/lib.rs | 41 +++++++++++++------------------ 3 files changed, 26 insertions(+), 52 deletions(-) diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index ca7ed97da43..5bc790b1c7f 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -30,9 +30,6 @@ const int SUBSCRIPTION_ID_LEN = 18; // global variable to keep track of the received rpc responses static int g_rpc_counter = 0; -// global string for callbacks -static std::string g_str; - // list of rpc queries static std::vector rpc_queries { "{\"method\":\"parity_versionInfo\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}", @@ -49,8 +46,8 @@ void ws_response(void* _unused, const char* response, size_t len) { printf("ws_response: %s\r\n", response); std::regex is_subscription ("\\{\"jsonrpc\":\"2.0\",\"result\":\"0[xX][a-fA-F0-9]{16}\",\"id\":1\\}"); // assume only one subscription is used - if (std::regex_match(response, is_subscription) == true && g_str.empty()) { - g_str = response; + if (std::regex_match(response, is_subscription) == true) { + g_rpc_counter -= 1; } } @@ -128,10 +125,9 @@ int parity_subscribe_to_websocket(void* parity) { size_t timeout = 1000; int num_queries = 1; - g_str.clear(); + g_rpc_counter = 1; std::string subscribe = "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; - std::string unsubscribe = "{\"method\":\"eth_unsubscribe\",\"params\":[\"0x1234567891234567\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; const void *const handle = parity_subscribe_ws(parity, subscribe.c_str(), subscribe.length(), ws_response); @@ -139,16 +135,10 @@ int parity_subscribe_to_websocket(void* parity) { return 1; } - while(g_str.empty()); + while(g_rpc_counter != 0); std::this_thread::sleep_for(std::chrono::seconds(60)); - // Replace subscription_id with the id we got in the callback - // (this is not a good practice use your favorite JSON parser) - unsubscribe.replace(39, SUBSCRIPTION_ID_LEN, g_str, 27, SUBSCRIPTION_ID_LEN); - if (parity_unsubscribe_ws(parity, handle, unsubscribe.c_str(), unsubscribe.length(), timeout, ws_response) != 0) { - return 1; - } - + parity_unsubscribe_ws(handle); return 0; } @@ -185,7 +175,7 @@ void* parity_light_run() { .on_client_restart_cb_custom = nullptr }; - std::vector args = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; + std::vector args = {"--no-ipc" , "--light", "--jsonrpc-apis=all", "--chain", "kovan"}; std::vector str_lens; for (auto arg: args) { diff --git a/parity-clib/parity.h b/parity-clib/parity.h index ba8e0960eb5..333ff69d2f1 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -116,21 +116,12 @@ int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, const void *const parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, void (*subscribe)(void* custom, const char* response, size_t len)); -/// Unsubscribes from a specific websocket event. Caution this function consumes the session object and must only be +/// Unsubscribes from a websocket subscription. Caution this function consumes the session object and must only be /// used exactly once per session. /// -/// - parity : Reference to the running parity client /// - session : Underlying pointer to an atomic reference counter -/// - ws_query : JSON encoded string representing the websocket event to unsubscribe from -/// - len : Length of the query -/// - timeout : Maximum time in milliseconds to wait for a response -/// - response : Callback to invoke when the current session has been terminated -/// -/// - On success : The function returns 0 -/// - On error : The function returns non-zero -// -int parity_unsubscribe_ws(const void *const parity, const void *const session, const char* ws_query, - size_t len, size_t timeout, void (*unsubscribe)(void* custom, const char* response, size_t len)); +/// +int parity_unsubscribe_ws(const void *const session); /// Sets a callback to call when a panic happens in the Rust code. /// diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index 98629591a21..1526905a083 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -175,7 +175,6 @@ fn parity_rpc_dispatcher( None => to_cstring(error::EMPTY.as_bytes()), }; cb(ptr::null_mut(), cstr, len); - () }); let _handle = thread::Builder::new() @@ -223,14 +222,15 @@ pub unsafe extern fn parity_subscribe_ws( if let Some((client, query, callback)) = parity_rpc_query_checker(client, query, len, callback) { let (tx, mut rx) = mpsc::channel(1); let session = Arc::new(PubSubSession::new(tx)); - let ffi_session = session.clone(); let query = client.rpc_query(query, Some(session.clone())); + let weak_session = Arc::downgrade(&session); let _handle = thread::Builder::new() .name("ws-subscriber".into()) .spawn(move || { - - // wait for subscription ID + // Wait for subscription ID + // Note this may block forever and be can't destroyed using the session object) + // FIXME: add timeout match query.wait() { Ok(Some(response)) => { let (cstr, len) = to_cstring(response.as_bytes()); @@ -244,18 +244,24 @@ pub unsafe extern fn parity_subscribe_ws( } }; - while Arc::strong_count(&session) > 1 { + loop { for response in rx.by_ref().wait() { if let Ok(r) = response { let (cstring, len) = to_cstring(r.as_bytes()); callback(ptr::null_mut(), cstring, len); + } + } + + let rc = weak_session.upgrade().map_or(0,|session| Arc::strong_count(&session)); + // No subscription left, then terminate + if rc <= 1 { + break; } } - } }) .expect("rpc-subscriber thread shouldn't fail; qed"); - Arc::into_raw(ffi_session) as *const c_void + Arc::into_raw(session) as *const c_void } else { ptr::null() } @@ -264,23 +270,10 @@ pub unsafe extern fn parity_subscribe_ws( } #[no_mangle] -pub unsafe extern fn parity_unsubscribe_ws( - client: *const c_void, - session: *const c_void, - query: *const c_char, - len: usize, - timeout_ms: usize, - callback: Callback, -) -> c_int { - panic::catch_unwind(|| { - if let Some((client, query, callback)) = parity_rpc_query_checker(client, query, len, callback) { - let session = Some(Arc::from_raw(session as *const PubSubSession)); - parity_rpc_dispatcher(client, query, session, callback, timeout_ms, "ws-unsubscribe"); - 0 - } else { - 1 - } - }).unwrap_or(1) +pub unsafe extern fn parity_unsubscribe_ws(session: *const c_void) { + let _ = panic::catch_unwind(|| { + let _session = Arc::from_raw(session as *const PubSubSession); + }); } #[no_mangle] From 1f93ffa116255ee059ac7b5bf9cc2bda77ef7b83 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Nov 2018 16:57:34 +0100 Subject: [PATCH 09/24] refactor(cpp example) : more subscriptions --- parity-clib/examples/cpp/main.cpp | 87 +++++++++++++------------------ parity-clib/src/lib.rs | 2 +- 2 files changed, 38 insertions(+), 51 deletions(-) diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index 5bc790b1c7f..9a3a6226af3 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -20,24 +20,31 @@ #include #include -void* parity_light_run(); -void* parity_full_run(); +void* parity_run(std::vector); int parity_subscribe_to_websocket(void*); int parity_rpc_queries(void*); const int SUBSCRIPTION_ID_LEN = 18; +const size_t TIMEOUT_ONE_MIN_AS_MILLIS = 60 * 1000; // global variable to keep track of the received rpc responses static int g_rpc_counter = 0; // list of rpc queries -static std::vector rpc_queries { +const std::vector rpc_queries { "{\"method\":\"parity_versionInfo\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}", "{\"method\":\"eth_getTransactionReceipt\",\"params\":[\"0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913fff\"],\"id\":1,\"jsonrpc\":\"2.0\"}", "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\"}],\"id\":1,\"jsonrpc\":\"2.0\"}", "{\"method\":\"eth_getBalance\",\"params\":[\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\"],\"id\":1,\"jsonrpc\":\"2.0\"}" }; +// list of subscriptions +const std::vector ws_subscriptions { + "{\"method\":\"parity_subscribe\",\"params\":[\"eth_getBalance\",[\"0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826\",\"latest\"]],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"parity_subscribe\",\"params\":[\"parity_netPeers\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}" +}; + // callback that gets invoked when the client restarts void on_restart(void*, const char*, size_t) {} @@ -45,7 +52,6 @@ void on_restart(void*, const char*, size_t) {} void ws_response(void* _unused, const char* response, size_t len) { printf("ws_response: %s\r\n", response); std::regex is_subscription ("\\{\"jsonrpc\":\"2.0\",\"result\":\"0[xX][a-fA-F0-9]{16}\",\"id\":1\\}"); - // assume only one subscription is used if (std::regex_match(response, is_subscription) == true) { g_rpc_counter -= 1; } @@ -60,7 +66,8 @@ void rpc_response(void* _unused, const char* response, size_t len) { int main() { // run full-client { - void* parity = parity_full_run(); + std::vector config = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; + void* parity = parity_run(config); if (parity_rpc_queries(parity)) { printf("rpc_queries failed\r\n"); return 1; @@ -78,7 +85,8 @@ int main() { // run light-client { - void* parity = parity_light_run(); + std::vector light_config = {"--no-ipc", "--light", "--jsonrpc-apis=all", "--chain", "kovan"}; + void* parity = parity_run(light_config); if (parity_rpc_queries(parity)) { printf("rpc_queries failed\r\n"); @@ -94,7 +102,6 @@ int main() { parity_destroy(parity); } } - return 0; } @@ -104,11 +111,10 @@ int parity_rpc_queries(void* parity) { } size_t num_queries = rpc_queries.size(); - size_t timeout = 1000; g_rpc_counter = num_queries; for (auto query : rpc_queries) { - if (parity_rpc(parity, query.c_str(), query.length(), timeout, rpc_response) != 0) { + if (parity_rpc(parity, query.c_str(), query.length(), TIMEOUT_ONE_MIN_AS_MILLIS, rpc_response) != 0) { return 1; } } @@ -123,72 +129,53 @@ int parity_subscribe_to_websocket(void* parity) { return 1; } - size_t timeout = 1000; - int num_queries = 1; - g_rpc_counter = 1; - - std::string subscribe = "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}"; - - const void *const handle = parity_subscribe_ws(parity, subscribe.c_str(), subscribe.length(), ws_response); + std::vector sessions; + g_rpc_counter = ws_subscriptions.size(); - if (!handle) { - return 1; + for (auto sub : ws_subscriptions) { + const void *const session = parity_subscribe_ws(parity, sub.c_str(), sub.length(), ws_response); + if (!session) { + return 1; + } + sessions.push_back(session); } while(g_rpc_counter != 0); std::this_thread::sleep_for(std::chrono::seconds(60)); - - parity_unsubscribe_ws(handle); - return 0; -} - -void* parity_full_run() { - ParityParams cfg = { - .configuration = nullptr, - .on_client_restart_cb = on_restart, - .on_client_restart_cb_custom = nullptr - }; - - std::vector args = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; - std::vector strs_len; - - for (auto arg: args) { - strs_len.push_back(std::strlen(arg)); - } - - if (parity_config_from_cli(&args[0], &strs_len[0], args.size(), &cfg.configuration) != 0) { - return nullptr; + for (auto session : sessions) { + parity_unsubscribe_ws(session); } - - void *parity = nullptr; - if (parity_start(&cfg, &parity) != 0) { - return nullptr; - } - - return parity; + return 0; } -void* parity_light_run() { +void* parity_run(std::vector args) { ParityParams cfg = { .configuration = nullptr, .on_client_restart_cb = on_restart, .on_client_restart_cb_custom = nullptr }; - std::vector args = {"--no-ipc" , "--light", "--jsonrpc-apis=all", "--chain", "kovan"}; std::vector str_lens; for (auto arg: args) { str_lens.push_back(std::strlen(arg)); } - if (parity_config_from_cli(&args[0], &str_lens[0], str_lens.size(), &cfg.configuration) != 0) { - return nullptr; + // make sure no out-of-range access happens here + if (args.empty()) { + if (parity_config_from_cli(nullptr, nullptr, 0, &cfg.configuration) != 0) { + return nullptr; + } + } else { + if (parity_config_from_cli(&args[0], &str_lens[0], args.size(), &cfg.configuration) != 0) { + return nullptr; + } } void *parity = nullptr; if (parity_start(&cfg, &parity) != 0) { return nullptr; } + return parity; } diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index 1526905a083..c16986dd482 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -187,7 +187,7 @@ fn parity_rpc_dispatcher( let (cstr, len) = to_cstring(error::TIMEOUT.as_bytes()); callback(ptr::null_mut(), cstr, len); }); - }) + }) .expect("rpc-query thread shouldn't fail; qed"); } From ee38cf17bd8336b9cf26d948ff7592cfb8e91a05 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Nov 2018 21:52:40 +0100 Subject: [PATCH 10/24] fix(callback type) : address grumbles on callback * Use it the example to avoid using global variables --- logger/src/lib.rs | 1 - parity-clib/examples/cpp/main.cpp | 51 ++++++------ parity-clib/parity.h | 22 ++++-- parity-clib/src/lib.rs | 126 ++++++++++++++---------------- 4 files changed, 101 insertions(+), 99 deletions(-) diff --git a/logger/src/lib.rs b/logger/src/lib.rs index ff04395ac83..6068fa39678 100644 --- a/logger/src/lib.rs +++ b/logger/src/lib.rs @@ -71,7 +71,6 @@ pub fn setup_log(config: &Config) -> Result, String> { builder.filter(Some("ws"), LevelFilter::Warn); builder.filter(Some("hyper"), LevelFilter::Warn); builder.filter(Some("rustls"), LevelFilter::Error); - builder.filter(Some("pubsub"), LevelFilter::Debug); builder.filter(Some("rpc"), LevelFilter::Trace); // Enable info for others. builder.filter(None, LevelFilter::Info); diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index 9a3a6226af3..ae643e6bcb1 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -27,9 +27,6 @@ int parity_rpc_queries(void*); const int SUBSCRIPTION_ID_LEN = 18; const size_t TIMEOUT_ONE_MIN_AS_MILLIS = 60 * 1000; -// global variable to keep track of the received rpc responses -static int g_rpc_counter = 0; - // list of rpc queries const std::vector rpc_queries { "{\"method\":\"parity_versionInfo\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}", @@ -45,22 +42,27 @@ const std::vector ws_subscriptions { "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}" }; -// callback that gets invoked when the client restarts -void on_restart(void*, const char*, size_t) {} - -// callback that is invoked on ws responses -void ws_response(void* _unused, const char* response, size_t len) { - printf("ws_response: %s\r\n", response); - std::regex is_subscription ("\\{\"jsonrpc\":\"2.0\",\"result\":\"0[xX][a-fA-F0-9]{16}\",\"id\":1\\}"); - if (std::regex_match(response, is_subscription) == true) { - g_rpc_counter -= 1; +// callback that gets invoked upon an event +void callback(size_t type, void* ud, const char* response, size_t _len) { + if (type == PARITY_CALLBACK_RESTART) { + printf("restart\r\n"); + } else if (type == PARITY_CALLBACK_RPC) { + printf("rpc response: %s\r\n", response); + size_t* counter = static_cast(ud); + *counter -= 1; + } else if (type == PARITY_CALLBACK_WEBSOCKET) { + printf("websocket response: %s\r\n", response); + std::regex is_subscription ("\\{\"jsonrpc\":\"2.0\",\"result\":\"0[xX][a-fA-F0-9]{16}\",\"id\":1\\}"); + if (std::regex_match(response, is_subscription) == true) { + size_t* counter = static_cast(ud); + *counter -= 1; + } + } + else if (type == PARITY_CALLBACK_PANIC_HOOK) { + printf("panic hook\r\n"); + } else { + printf("Not supported callback id\r\n"); } -} - -// callback that is invoked on ws responses -void rpc_response(void* _unused, const char* response, size_t len) { - printf("rpc_response: %s\r\n", response); - g_rpc_counter -= 1; } int main() { @@ -111,15 +113,14 @@ int parity_rpc_queries(void* parity) { } size_t num_queries = rpc_queries.size(); - g_rpc_counter = num_queries; for (auto query : rpc_queries) { - if (parity_rpc(parity, query.c_str(), query.length(), TIMEOUT_ONE_MIN_AS_MILLIS, rpc_response) != 0) { + if (parity_rpc(parity, query.c_str(), query.length(), TIMEOUT_ONE_MIN_AS_MILLIS, callback, &num_queries) != 0) { return 1; } } - while(g_rpc_counter != 0); + while(num_queries != 0); return 0; } @@ -130,17 +131,17 @@ int parity_subscribe_to_websocket(void* parity) { } std::vector sessions; - g_rpc_counter = ws_subscriptions.size(); + size_t num_subs = ws_subscriptions.size(); for (auto sub : ws_subscriptions) { - const void *const session = parity_subscribe_ws(parity, sub.c_str(), sub.length(), ws_response); + void *const session = parity_subscribe_ws(parity, sub.c_str(), sub.length(), callback, &num_subs); if (!session) { return 1; } sessions.push_back(session); } - while(g_rpc_counter != 0); + while(num_subs != 0); std::this_thread::sleep_for(std::chrono::seconds(60)); for (auto session : sessions) { parity_unsubscribe_ws(session); @@ -151,7 +152,7 @@ int parity_subscribe_to_websocket(void* parity) { void* parity_run(std::vector args) { ParityParams cfg = { .configuration = nullptr, - .on_client_restart_cb = on_restart, + .on_client_restart_cb = callback, .on_client_restart_cb_custom = nullptr }; diff --git a/parity-clib/parity.h b/parity-clib/parity.h index 333ff69d2f1..56a2736ac5a 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -17,6 +17,11 @@ #ifndef _PARITY_H_INCLUDED_ #define _PARITY_H_INCLUDED_ +#define PARITY_CALLBACK_RESTART 0 +#define PARITY_CALLBACK_RPC 1 +#define PARITY_CALLBACK_WEBSOCKET 2 +#define PARITY_CALLBACK_PANIC_HOOK 3 + #include /// Parameters to pass to `parity_start`. @@ -31,7 +36,7 @@ struct ParityParams { /// /// The first parameter of the callback is the value of `on_client_restart_cb_custom`. /// The second and third parameters of the callback are the string pointer and length. - void (*on_client_restart_cb)(void* custom, const char* new_chain, size_t new_chain_len); + void (*on_client_restart_cb)(size_t callback_kind, void* custom, const char* new_chain, size_t new_chain_len); /// Custom parameter passed to the `on_client_restart_cb` callback as first parameter. void *on_client_restart_cb_custom; @@ -93,13 +98,15 @@ void parity_destroy(void* parity); /// - len : Length of the RPC query /// - timeout_ms : Maximum time that request is waiting for a response /// - response : Callback to invoke when the query gets answered. It will respond with a JSON encoded the string -// with the result -/// +/// with the result +/// - ud : Specific user defined data that can used in the callback ("response) +// +// /// - On success : The parity client reference and the query string were valid /// - On error : The parity client reference and the query string were not valid /// int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, - void (*subscribe)(void* custom, const char* response, size_t len)); + void (*subscribe)(size_t callback_kind, void* ud, const char* response, size_t len), void* ud); /// Subscribes to a specific websocket event that will run until it is canceled @@ -108,13 +115,14 @@ int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, /// - ws_query : JSON encoded string representing the websocket event to subscribe to /// - len : Length of the query /// - response : Callback to invoke when a websocket event occurs +/// - ud : Specific user defined data that can used in the callback /// /// - On success : The function returns an object to the current session /// which can be used cancel the subscription /// - On error : The function returns a null pointer /// -const void *const parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, - void (*subscribe)(void* custom, const char* response, size_t len)); +void* parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, + void (*subscribe)(size_t callback_kind, void* ud, const char* response, size_t len), void* ud); /// Unsubscribes from a websocket subscription. Caution this function consumes the session object and must only be /// used exactly once per session. @@ -138,7 +146,7 @@ int parity_unsubscribe_ws(const void *const session); /// The callback can be called from any thread and multiple times simultaneously. Make sure that /// your code is thread safe. /// -int parity_set_panic_hook(void (*cb)(void* param, const char* msg, size_t msg_len), void* param); +int parity_set_panic_hook(void (*cb)(size_t callback_kind, void* param, const char* msg, size_t msg_len), void* param); #ifdef __cplusplus } diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index c16986dd482..1fbc1f06724 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -40,9 +40,8 @@ use futures::sync::mpsc; use parity_ethereum::{PubSubSession, RunningClient}; use tokio_current_thread::CurrentThread; -type Callback = Option; -type CheckedCallback = Arc; -type CheckedQuery<'a> = (&'a RunningClient, &'static str, CheckedCallback); +type Callback = Option; +type CheckedQuery<'a> = (&'a RunningClient, &'static str); pub mod error { pub const EMPTY: &str = r#"{"jsonrpc":"2.0","result":"null","id":1}"#; @@ -50,6 +49,14 @@ pub mod error { pub const SUBSCRIBE: &str = r#"{"jsonrpc":"2.0","result":"subcribe_fail","id":1}"#; } +#[derive(Debug, Copy, Clone)] +#[repr(usize)] +enum CallbackKind { + Restart = 0, + Rpc = 1, + WebSocket = 2, + PanicHook = 3 +} #[repr(C)] pub struct ParityParams { @@ -117,8 +124,12 @@ pub unsafe extern fn parity_start(cfg: *const ParityParams, output: *mut *mut c_ let config = Box::from_raw(cfg.configuration as *mut parity_ethereum::Configuration); let on_client_restart_cb = { - let cb = CallbackStr(cfg.on_client_restart_cb, cfg.on_client_restart_cb_custom); - move |new_chain: String| { cb.call(&new_chain); } + let cb = CallbackStr { + kind: CallbackKind::Restart, + user_data: cfg.on_client_restart_cb_custom, + f: cfg.on_client_restart_cb, + }; + move |new_chain: String| { cb.call(new_chain.as_bytes()); } }; let action = match parity_ethereum::start(*config, on_client_restart_cb, || {}) { @@ -146,49 +157,15 @@ pub unsafe extern fn parity_destroy(client: *mut c_void) { } -unsafe fn parity_rpc_query_checker<'a>(client: *const c_void, query: *const c_char, len: usize, callback: Callback) +unsafe fn parity_rpc_query_checker<'a>(client: *const c_void, query: *const c_char, len: usize) -> Option> { - let callback = callback?; let query_str = { let string = slice::from_raw_parts(query as *const u8, len); str::from_utf8(string).ok()? }; let client: &RunningClient = &*(client as *const RunningClient); - - Some((client, query_str, Arc::new(callback))) -} - -fn parity_rpc_dispatcher( - client: &RunningClient, - query: &str, - session: Option>, - callback: CheckedCallback, - timeout_ms: usize, - name: &str) { - - let cb = callback.clone(); - let client = client as &RunningClient; - let query = client.rpc_query(query, session).map(move |response| { - let (cstr, len) = match response { - Some(response) => to_cstring(response.as_bytes()), - None => to_cstring(error::EMPTY.as_bytes()), - }; - cb(ptr::null_mut(), cstr, len); - }); - - let _handle = thread::Builder::new() - .name(name.into()) - .spawn(move || { - let mut current_thread = CurrentThread::new(); - current_thread.spawn(query); - let _ = current_thread.run_timeout(Duration::from_millis(timeout_ms as u64)) - .map_err(|_e| { - let (cstr, len) = to_cstring(error::TIMEOUT.as_bytes()); - callback(ptr::null_mut(), cstr, len); - }); - }) - .expect("rpc-query thread shouldn't fail; qed"); + Some((client, query_str)) } #[no_mangle] @@ -198,10 +175,30 @@ pub unsafe extern fn parity_rpc( len: usize, timeout_ms: usize, callback: Callback, + user_data: *mut c_void, ) -> c_int { panic::catch_unwind(|| { - if let Some((client, query, callback)) = parity_rpc_query_checker(client, query, len, callback) { - parity_rpc_dispatcher(client, query, None, callback, timeout_ms, "rpc-query"); + if let Some((client, query)) = parity_rpc_query_checker(client, query, len) { + let client = client as &RunningClient; + let callback = Arc::new(CallbackStr { kind: CallbackKind::Rpc, user_data, f: callback}); + let cb = callback.clone(); + let query = client.rpc_query(query, None) + .map(move |response| { + let response = response.unwrap_or_else(|| error::EMPTY.to_string()); + callback.call(response.as_bytes()); + }); + + let _handle = thread::Builder::new() + .name("rpc_query".to_string()) + .spawn(move || { + let mut current_thread = CurrentThread::new(); + current_thread.spawn(query); + let _ = current_thread.run_timeout(Duration::from_millis(timeout_ms as u64)) + .map_err(|_e| { + cb.call(error::TIMEOUT.as_bytes()); + }); + }) + .expect("rpc-query thread shouldn't fail; qed"); 0 } else { 1 @@ -209,37 +206,36 @@ pub unsafe extern fn parity_rpc( }).unwrap_or(1) } - #[no_mangle] pub unsafe extern fn parity_subscribe_ws( client: *const c_void, query: *const c_char, len: usize, callback: Callback, + user_data: *mut c_void, ) -> *const c_void { panic::catch_unwind(|| { - if let Some((client, query, callback)) = parity_rpc_query_checker(client, query, len, callback) { + if let Some((client, query)) = parity_rpc_query_checker(client, query, len) { let (tx, mut rx) = mpsc::channel(1); let session = Arc::new(PubSubSession::new(tx)); let query = client.rpc_query(query, Some(session.clone())); let weak_session = Arc::downgrade(&session); + let cb = CallbackStr { kind: CallbackKind::WebSocket, user_data, f: callback}; let _handle = thread::Builder::new() .name("ws-subscriber".into()) .spawn(move || { // Wait for subscription ID - // Note this may block forever and be can't destroyed using the session object) - // FIXME: add timeout + // Note this may block forever and be can't destroyed using the session object + // However, this will likely timeout or be catched the RPC layer match query.wait() { Ok(Some(response)) => { - let (cstr, len) = to_cstring(response.as_bytes()); - callback(ptr::null_mut(), cstr, len); + cb.call(response.as_bytes()); } // subscription failed return error and exit _ => { - let (cstr, len) = to_cstring(error::SUBSCRIBE.as_bytes()); - callback(ptr::null_mut(), cstr, len); + cb.call(error::SUBSCRIBE.as_bytes()); return; } }; @@ -247,8 +243,7 @@ pub unsafe extern fn parity_subscribe_ws( loop { for response in rx.by_ref().wait() { if let Ok(r) = response { - let (cstring, len) = to_cstring(r.as_bytes()); - callback(ptr::null_mut(), cstring, len); + cb.call(r.as_bytes()); } } @@ -260,7 +255,6 @@ pub unsafe extern fn parity_subscribe_ws( } }) .expect("rpc-subscriber thread shouldn't fail; qed"); - Arc::into_raw(session) as *const c_void } else { ptr::null() @@ -278,26 +272,26 @@ pub unsafe extern fn parity_unsubscribe_ws(session: *const c_void) { #[no_mangle] pub unsafe extern fn parity_set_panic_hook(callback: Callback, param: *mut c_void) { - let cb = CallbackStr(callback, param); + let cb = CallbackStr {kind: CallbackKind::PanicHook, user_data: param, f: callback}; panic_hook::set_with(move |panic_msg| { - cb.call(panic_msg); + cb.call(panic_msg.as_bytes()); }); } // Internal structure for handling callbacks that get passed a string. -struct CallbackStr(Callback, *mut c_void); +struct CallbackStr { + kind: CallbackKind, + user_data: *mut c_void, + f: Callback, +} + unsafe impl Send for CallbackStr {} unsafe impl Sync for CallbackStr {} impl CallbackStr { - fn call(&self, new_chain: &str) { - if let Some(ref cb) = self.0 { - cb(self.1, new_chain.as_bytes().as_ptr() as *const _, new_chain.len()) + fn call(&self, msg: &[u8]) { + if let Some(ref cb) = self.f { + let cstr = CString::new(msg).expect("valid string with no null bytes in the middle; qed").into_raw(); + cb(self.kind as usize, self.user_data, cstr, msg.len()) } } } - -fn to_cstring(response: &[u8]) -> (*const c_char, usize) { - let len = response.len(); - let cstr = CString::new(response).expect("valid string with no null bytes in the middle; qed").into_raw(); - (cstr, len) -} From f67523e8a4541ddff994a65fc8a9d5eca97103d7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Nov 2018 21:57:56 +0100 Subject: [PATCH 11/24] docs(nits) - don't mention `arc` --- parity-clib/parity.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parity-clib/parity.h b/parity-clib/parity.h index 56a2736ac5a..d06997991be 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -127,7 +127,7 @@ void* parity_subscribe_ws(const void *const parity, const char* ws_query, size_t /// Unsubscribes from a websocket subscription. Caution this function consumes the session object and must only be /// used exactly once per session. /// -/// - session : Underlying pointer to an atomic reference counter +/// - session : Pointer to the session to unsubscribe from /// int parity_unsubscribe_ws(const void *const session); From 22f9e9f308759da557c429a596b56e0c684220e5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 20 Nov 2018 23:07:28 +0100 Subject: [PATCH 12/24] fix(jni bindings): fix compile errors --- parity-clib/src/java.rs | 53 +++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs index 2f57c29913c..4ae26c51f5d 100644 --- a/parity-clib/src/java.rs +++ b/parity-clib/src/java.rs @@ -1,8 +1,8 @@ use std::{mem, ptr}; -use std::os::raw::c_void; -use {Callback, parity_config_from_cli, parity_destroy, parity_start, parity_rpc, ParityParams}; -use jni::{JNIEnv, objects::JClass, objects::JString, sys::jlong, sys::jobjectArray}; +use {Callback, parity_config_from_cli, parity_destroy, parity_start, parity_rpc, parity_subscribe_ws, + parity_unsubscribe_ws, ParityParams}; +use jni::{JNIEnv, objects::JClass, objects::JString, sys::jlong, sys::jobjectArray, sys::va_list}; #[no_mangle] pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_configFromCli(env: JNIEnv, _: JClass, cli: jobjectArray) -> jlong { @@ -12,7 +12,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_configFromCli(env: let mut opts = Vec::with_capacity(cli_len as usize); let mut opts_lens = Vec::with_capacity(cli_len as usize); - for n in 0 .. cli_len { + for n in 0..cli_len { let elem = env.get_object_array_element(cli, n).expect("invalid Java bindings"); let elem_str: JString = elem.into(); match env.get_string(elem_str) { @@ -39,9 +39,9 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_configFromCli(env: } #[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_build(env: JNIEnv, _: JClass, config: jlong) -> jlong { +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_build(env: JNIEnv, _: JClass, config: va_list) -> jlong { let params = ParityParams { - configuration: config as usize as *mut c_void, + configuration: config, .. mem::zeroed() }; @@ -56,8 +56,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_build(env: JNIEnv, } #[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_destroy(_env: JNIEnv, _: JClass, parity: jlong) { - let parity = parity as usize as *mut c_void; +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_destroy(_env: JNIEnv, _: JClass, parity: va_list) { parity_destroy(parity); } @@ -65,13 +64,13 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_destroy(_env: JNIEn pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative<'a>( env: JNIEnv<'a>, _: JClass, - parity:jlong, + parity: va_list, rpc: JString, + timeout_ms: jlong, callback: Callback, + user_data: va_list, ) { - let parity = parity as usize as *mut c_void; - let rpc = match env.get_string(rpc) { Ok(s) => s, Err(err) => { @@ -80,8 +79,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative<'a>( }, }; - //FIXME: provide "callback" in java fashion - match parity_rpc(parity, rpc.as_ptr(), rpc.to_bytes().len(), callback) { + match parity_rpc(parity, rpc.as_ptr(), rpc.to_bytes().len(), timeout_ms as usize, callback, user_data) { 0 => (), _ => { let _ = env.throw_new("java/lang/Exception", "failed to perform RPC query"); @@ -89,3 +87,32 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative<'a>( }, } } + +#[no_mangle] +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocket<'a>( + env: JNIEnv<'a>, + _: JClass, + parity: va_list, + rpc: JString, + callback: Callback, + user_data: va_list, +) -> va_list { + let rpc = match env.get_string(rpc) { + Ok(s) => s, + Err(err) => { + let _ = env.throw_new("java/lang/Exception", err.to_string()); + return ptr::null_mut(); + }, + }; + + let result = parity_subscribe_ws(parity, rpc.as_ptr(), rpc.to_bytes().len(), callback, user_data) as va_list; + if result.is_null() { + let _ = env.throw_new("java/lang/Exception", "failed to subscribe to WebSocket"); + } + result +} + +#[no_mangle] +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_unsubscribeWebSocket<'a>(session: va_list) { + parity_unsubscribe_ws(session); +} From f35d8825712a858561133138bbc5c31b6f15b06b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 22 Nov 2018 23:47:08 +0100 Subject: [PATCH 13/24] feat(java example and updated java bindings) --- parity-clib-examples/java/Main.java | 91 +++++++++++++++ parity-clib-examples/java/run.sh | 11 ++ parity-clib/Parity.java | 24 +++- parity-clib/src/java.rs | 173 +++++++++++++++++++++------- parity-clib/src/lib.rs | 34 +++--- 5 files changed, 271 insertions(+), 62 deletions(-) create mode 100644 parity-clib-examples/java/Main.java create mode 100755 parity-clib-examples/java/run.sh diff --git a/parity-clib-examples/java/Main.java b/parity-clib-examples/java/Main.java new file mode 100644 index 00000000000..0a394a71ea3 --- /dev/null +++ b/parity-clib-examples/java/Main.java @@ -0,0 +1,91 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import java.util.Vector; +import java.util.concurrent.atomic.AtomicInteger; +import io.parity.ethereum.Parity; +import com.sun.jna.Pointer; + +class Main { + public static final int ONE_MINUTE_AS_MILLIS = 60 * 1000; + + public static final String[] rpc_queries = { + "{\"method\":\"parity_versionInfo\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_getTransactionReceipt\",\"params\":[\"0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913fff\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_estimateGas\",\"params\":[{\"from\":\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\"}],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_getBalance\",\"params\":[\"0x0066Dc48bb833d2B59f730F33952B3c29fE926F5\"],\"id\":1,\"jsonrpc\":\"2.0\"}" + }; + + public static final String[] ws_queries = { + "{\"method\":\"parity_subscribe\",\"params\":[\"eth_getBalance\",[\"0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826\",\"latest\"]],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"parity_subscribe\",\"params\":[\"parity_netPeers\"],\"id\":1,\"jsonrpc\":\"2.0\"}", + "{\"method\":\"eth_subscribe\",\"params\":[\"newHeads\"],\"id\":1,\"jsonrpc\":\"2.0\"}" + }; + + public static void runParity(String[] config) { + Parity parity = new Parity(config); + + Callback rpcCallback = new Callback(); + Callback webSocketCallback = new Callback(); + + for (String query : rpc_queries) { + parity.rpcQuery(query, ONE_MINUTE_AS_MILLIS, rpcCallback); + } + + while (rpcCallback.getNumCallbacks() != 4); + + Vector sessions = new Vector(); + + for (String ws : ws_queries) { + parity.subscribeWebSocket(ws, webSocketCallback); + } + + try { + Thread.sleep(ONE_MINUTE_AS_MILLIS); + } catch (Exception e) { + System.out.println(e); + } + + for (Object session : sessions) { + parity.unsubscribeWebSocket(session); + } + } + + public static void main(String[] args) { + String[] full = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; + String[] light = {"--no-ipc", "--light", "--jsonrpc-apis=all", "--chain", "kovan"}; + runParity(full); + } +} + +class Callback { + private AtomicInteger counter; + + public Callback() { + counter = new AtomicInteger(); + } + + public void callback(long kind, long userData, long response, long len) { + Pointer responsePointer = new Pointer(response); + String s = responsePointer.getString(0); + System.out.println("Callback Kind: " + kind + s); + counter.getAndIncrement(); + } + + public int getNumCallbacks() { + return counter.intValue(); + } +} diff --git a/parity-clib-examples/java/run.sh b/parity-clib-examples/java/run.sh new file mode 100755 index 00000000000..30d0b77eccc --- /dev/null +++ b/parity-clib-examples/java/run.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +FLAGS="-Xlint:deprecation" +PARITY_JAVA="../../parity-clib/Parity.java" +PARITY_LIB=".:../../target/debug/" + +# build +javac $FLAGS -d $PWD $PARITY_JAVA +javac $FLAGS *.java +# Setup the path `libparity.so` and run +java -Djava.library.path=$PARITY_LIB Main diff --git a/parity-clib/Parity.java b/parity-clib/Parity.java index 7e70917ae36..02a4b1a5bb6 100644 --- a/parity-clib/Parity.java +++ b/parity-clib/Parity.java @@ -41,8 +41,24 @@ public Parity(String[] options) { * @param query The JSON-encoded RPC query to perform * @return A JSON-encoded result */ - public String rpcQuery(String query) { - return rpcQueryNative(inner, query); + public void rpcQuery(String query, long timeoutMillis, Object callback) { + rpcQueryNative(inner, query, timeoutMillis, callback); + } + + /** FIXME: docs + * + * + */ + public Object subscribeWebSocket(String query, Object callback) { + return subscribeWebSocketNative(inner, query, callback); + } + + /** FIXME: docs + * + * + */ + public void unsubscribeWebSocket(Object session) { + unsubscribeWebSocketNative(session); } @Override @@ -57,7 +73,9 @@ public String rpcQuery(String query) { private static native long configFromCli(String[] cliOptions); private static native long build(long config); private static native void destroy(long inner); - private static native String rpcQueryNative(long inner, String rpc); + private static native void rpcQueryNative(long inner, String rpc, long timeoutMillis, Object callback); + private static native Object subscribeWebSocketNative(long inner, String rpc, Object callback); + private static native void unsubscribeWebSocketNative(Object session); private long inner; } diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs index 4ae26c51f5d..c64b7d554a8 100644 --- a/parity-clib/src/java.rs +++ b/parity-clib/src/java.rs @@ -1,8 +1,52 @@ use std::{mem, ptr}; +use std::sync::Arc; +use std::time::Duration; +use std::thread; +use std::os::raw::c_void; -use {Callback, parity_config_from_cli, parity_destroy, parity_start, parity_rpc, parity_subscribe_ws, - parity_unsubscribe_ws, ParityParams}; -use jni::{JNIEnv, objects::JClass, objects::JString, sys::jlong, sys::jobjectArray, sys::va_list}; +use {CallbackKind, parity_config_from_cli, parity_destroy, parity_start, parity_unsubscribe_ws, ParityParams, error}; + +use futures::{Future, Stream}; +use futures::sync::mpsc; +use jni::{JavaVM, JNIEnv}; +use jni::objects::{JClass, JString, JObject, JValue, GlobalRef}; +use jni::sys::{jlong, jobjectArray, va_list}; +use tokio_current_thread::CurrentThread; +use parity_ethereum::{RunningClient, PubSubSession}; + +type CheckedQuery<'a> = (&'a RunningClient, String, JavaVM, GlobalRef); + +// Creates a Java callback to a static method named `void callback(long, long, long, long)` +struct Callback<'a> { + jvm: JavaVM, + callback: GlobalRef, + // class: JClass<'a>, + method_name: &'a str, + method_descriptor: &'a str, + value: [JValue<'a>; 4], +} + +unsafe impl<'a> Send for Callback<'a> {} +unsafe impl<'a> Sync for Callback<'a> {} +impl<'a> Callback<'a> { + fn new(jvm: JavaVM, callback: GlobalRef, kind: CallbackKind) -> Self { + Self { + jvm, + callback, + method_name: "callback", + method_descriptor: "(JJJJ)V", + value: [JValue::Long(kind as i64), JValue::Long(0), JValue::Long(0), JValue::Long(0)], + } + } + + fn call(&self, msg: &[u8]) { + let mut val = self.value; + val[2] = JValue::Long(msg.as_ptr() as i64); + val[3] = JValue::Long(msg.len() as i64); + let env = self.jvm.attach_current_thread().expect("JavaVM should have an environment; qed"); + env.call_method(self.callback.as_obj(), self.method_name, self.method_descriptor, &val).expect("The method to callmust be static and be named \"void callback(long, long, long, long)\"; qed)"); + } +} #[no_mangle] pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_configFromCli(env: JNIEnv, _: JClass, cli: jobjectArray) -> jlong { @@ -60,59 +104,108 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_destroy(_env: JNIEn parity_destroy(parity); } +unsafe fn async_checker<'a>(client: va_list, rpc: JString, callback: JObject, env: &JNIEnv<'a>) +-> Result, String> { + let query: String = env.get_string(rpc) + .map(Into::into) + .map_err(|e| e.to_string())?; + + let client: &RunningClient = &*(client as *const RunningClient); + let jvm = env.get_java_vm().map_err(|e| e.to_string())?; + let g = env.new_global_ref(callback).map_err(|e| e.to_string())?; + Ok((client, query, jvm, g)) +} + #[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative<'a>( - env: JNIEnv<'a>, +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( + env: JNIEnv<'static>, _: JClass, parity: va_list, rpc: JString, timeout_ms: jlong, - callback: Callback, - user_data: va_list, -) + callback: JObject, + ) { - let rpc = match env.get_string(rpc) { - Ok(s) => s, - Err(err) => { - let _ = env.throw_new("java/lang/Exception", err.to_string()); - return; - }, - }; + let _ = async_checker(parity, rpc, callback, &env) + .map(|(client, query, jvm, g)| { + let callback = Arc::new(Callback::new(jvm, g, CallbackKind::Rpc)); + let cb = callback.clone(); + let future = client.rpc_query(&query, None).map(move |response| { + let response = response.unwrap_or_else(|| error::EMPTY.to_string()); + callback.call(response.as_bytes()); + }); - match parity_rpc(parity, rpc.as_ptr(), rpc.to_bytes().len(), timeout_ms as usize, callback, user_data) { - 0 => (), - _ => { - let _ = env.throw_new("java/lang/Exception", "failed to perform RPC query"); - return; - }, - } + let _handle = thread::Builder::new() + .name("rpc_query".to_string()) + .spawn(move || { + let mut current_thread = CurrentThread::new(); + current_thread.spawn(future); + let _ = current_thread.run_timeout(Duration::from_millis(timeout_ms as u64)) + .map_err(|_e| { + cb.call(error::TIMEOUT.as_bytes()); + }); + }) + .expect("rpc-query thread shouldn't fail; qed"); + }) + .map_err(|e| { + let _ = env.throw_new("java/lang/Exception", e); + }); } #[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocket<'a>( - env: JNIEnv<'a>, +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocketNative( + env: JNIEnv<'static>, _: JClass, parity: va_list, rpc: JString, - callback: Callback, - user_data: va_list, -) -> va_list { - let rpc = match env.get_string(rpc) { - Ok(s) => s, - Err(err) => { - let _ = env.throw_new("java/lang/Exception", err.to_string()); - return ptr::null_mut(); - }, - }; + callback: JObject, + ) -> *const c_void { - let result = parity_subscribe_ws(parity, rpc.as_ptr(), rpc.to_bytes().len(), callback, user_data) as va_list; - if result.is_null() { - let _ = env.throw_new("java/lang/Exception", "failed to subscribe to WebSocket"); - } - result + async_checker(parity, rpc, callback, &env) + .map(move |(client, query, jvm, g)| { + let callback = Arc::new(Callback::new(jvm, g, CallbackKind::WebSocket)); + let (tx, mut rx) = mpsc::channel(1); + let session = Arc::new(PubSubSession::new(tx)); + let weak_session = Arc::downgrade(&session); + let query_future = client.rpc_query(&query, Some(session.clone()));; + + let _handle = thread::Builder::new() + .name("ws-subscriber".into()) + .spawn(move || { + // Wait for subscription ID + // Note this may block forever and be can't destroyed using the session object + // However, this will likely timeout or be catched the RPC layer + if let Ok(Some(response)) = query_future.wait() { + callback.call(response.as_bytes()); + } else { + callback.call(error::SUBSCRIBE.as_bytes()); + return; + }; + + loop { + for response in rx.by_ref().wait() { + if let Ok(r) = response { + callback.call(r.as_bytes()); + } + } + + let rc = weak_session.upgrade().map_or(0,|session| Arc::strong_count(&session)); + // No subscription left, then terminate + if rc <= 1 { + break; + } + } + }) + .expect("rpc-subscriber thread shouldn't fail; qed"); + Arc::into_raw(session) as *const c_void + }) + .unwrap_or_else(|e| { + let _ = env.throw_new("java/lang/Exception", e); + ptr::null_mut() + }) } #[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_unsubscribeWebSocket<'a>(session: va_list) { +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_unsubscribeWebSocketNative(session: va_list) { parity_unsubscribe_ws(session); } diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index 1fbc1f06724..42d3457578e 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -127,7 +127,7 @@ pub unsafe extern fn parity_start(cfg: *const ParityParams, output: *mut *mut c_ let cb = CallbackStr { kind: CallbackKind::Restart, user_data: cfg.on_client_restart_cb_custom, - f: cfg.on_client_restart_cb, + function: cfg.on_client_restart_cb, }; move |new_chain: String| { cb.call(new_chain.as_bytes()); } }; @@ -180,7 +180,7 @@ pub unsafe extern fn parity_rpc( panic::catch_unwind(|| { if let Some((client, query)) = parity_rpc_query_checker(client, query, len) { let client = client as &RunningClient; - let callback = Arc::new(CallbackStr { kind: CallbackKind::Rpc, user_data, f: callback}); + let callback = Arc::new(CallbackStr { kind: CallbackKind::Rpc, user_data, function: callback}); let cb = callback.clone(); let query = client.rpc_query(query, None) .map(move |response| { @@ -197,8 +197,8 @@ pub unsafe extern fn parity_rpc( .map_err(|_e| { cb.call(error::TIMEOUT.as_bytes()); }); - }) - .expect("rpc-query thread shouldn't fail; qed"); + }) + .expect("rpc-query thread shouldn't fail; qed"); 0 } else { 1 @@ -219,9 +219,9 @@ pub unsafe extern fn parity_subscribe_ws( if let Some((client, query)) = parity_rpc_query_checker(client, query, len) { let (tx, mut rx) = mpsc::channel(1); let session = Arc::new(PubSubSession::new(tx)); - let query = client.rpc_query(query, Some(session.clone())); + let query_future = client.rpc_query(query, Some(session.clone())); let weak_session = Arc::downgrade(&session); - let cb = CallbackStr { kind: CallbackKind::WebSocket, user_data, f: callback}; + let cb = CallbackStr { kind: CallbackKind::WebSocket, user_data, function: callback}; let _handle = thread::Builder::new() .name("ws-subscriber".into()) @@ -229,16 +229,12 @@ pub unsafe extern fn parity_subscribe_ws( // Wait for subscription ID // Note this may block forever and be can't destroyed using the session object // However, this will likely timeout or be catched the RPC layer - match query.wait() { - Ok(Some(response)) => { - cb.call(response.as_bytes()); - } - // subscription failed return error and exit - _ => { - cb.call(error::SUBSCRIBE.as_bytes()); - return; - } - }; + if let Ok(Some(response)) = query_future.wait() { + cb.call(response.as_bytes()); + } else { + cb.call(error::SUBSCRIBE.as_bytes()); + return; + } loop { for response in rx.by_ref().wait() { @@ -272,7 +268,7 @@ pub unsafe extern fn parity_unsubscribe_ws(session: *const c_void) { #[no_mangle] pub unsafe extern fn parity_set_panic_hook(callback: Callback, param: *mut c_void) { - let cb = CallbackStr {kind: CallbackKind::PanicHook, user_data: param, f: callback}; + let cb = CallbackStr {kind: CallbackKind::PanicHook, user_data: param, function: callback}; panic_hook::set_with(move |panic_msg| { cb.call(panic_msg.as_bytes()); }); @@ -282,14 +278,14 @@ pub unsafe extern fn parity_set_panic_hook(callback: Callback, param: *mut c_voi struct CallbackStr { kind: CallbackKind, user_data: *mut c_void, - f: Callback, + function: Callback, } unsafe impl Send for CallbackStr {} unsafe impl Sync for CallbackStr {} impl CallbackStr { fn call(&self, msg: &[u8]) { - if let Some(ref cb) = self.f { + if let Some(ref cb) = self.function { let cstr = CString::new(msg).expect("valid string with no null bytes in the middle; qed").into_raw(); cb(self.kind as usize, self.user_data, cstr, msg.len()) } From 489fc7a67c323f45d09f3772146c29fa76f526d8 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 23 Nov 2018 18:00:53 +0100 Subject: [PATCH 14/24] fix(java example) : run both full and light client --- parity-clib-examples/java/Main.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/parity-clib-examples/java/Main.java b/parity-clib-examples/java/Main.java index 0a394a71ea3..d3bb266bd0a 100644 --- a/parity-clib-examples/java/Main.java +++ b/parity-clib-examples/java/Main.java @@ -67,7 +67,16 @@ public static void runParity(String[] config) { public static void main(String[] args) { String[] full = {"--no-ipc" , "--jsonrpc-apis=all", "--chain", "kovan"}; String[] light = {"--no-ipc", "--light", "--jsonrpc-apis=all", "--chain", "kovan"}; + runParity(full); + + try { + Thread.sleep(ONE_MINUTE_AS_MILLIS); + } catch (Exception e) { + System.out.println(e); + } + + runParity(light); } } From 0a4aff155c85d5ea1fba867b465fbf8b13e15de5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 24 Nov 2018 13:03:08 +0100 Subject: [PATCH 15/24] fix(Java shutdown) : unsubscribe to sessions Forgot to pass the JNIEnv environment since it is an instance method --- parity-clib-examples/java/Main.java | 12 ++-- parity-clib/Parity.java | 87 +++++++++++++++-------------- parity-clib/src/java.rs | 45 ++++++++------- 3 files changed, 78 insertions(+), 66 deletions(-) diff --git a/parity-clib-examples/java/Main.java b/parity-clib-examples/java/Main.java index d3bb266bd0a..f1a2347df6f 100644 --- a/parity-clib-examples/java/Main.java +++ b/parity-clib-examples/java/Main.java @@ -47,10 +47,11 @@ public static void runParity(String[] config) { while (rpcCallback.getNumCallbacks() != 4); - Vector sessions = new Vector(); + Vector sessions = new Vector(); for (String ws : ws_queries) { - parity.subscribeWebSocket(ws, webSocketCallback); + long session = parity.subscribeWebSocket(ws, webSocketCallback); + sessions.add(session); } try { @@ -59,9 +60,13 @@ public static void runParity(String[] config) { System.out.println(e); } - for (Object session : sessions) { + for (long session : sessions) { parity.unsubscribeWebSocket(session); } + + // Force GC to destroy parity + parity = null; + System.gc(); } public static void main(String[] args) { @@ -90,7 +95,6 @@ public Callback() { public void callback(long kind, long userData, long response, long len) { Pointer responsePointer = new Pointer(response); String s = responsePointer.getString(0); - System.out.println("Callback Kind: " + kind + s); counter.getAndIncrement(); } diff --git a/parity-clib/Parity.java b/parity-clib/Parity.java index 02a4b1a5bb6..a0559b58808 100644 --- a/parity-clib/Parity.java +++ b/parity-clib/Parity.java @@ -28,54 +28,59 @@ public class Parity { * * @param options The CLI options to start Parity with */ - public Parity(String[] options) { - long config = configFromCli(options); - inner = build(config); - } + public Parity(String[] options) { + long config = configFromCli(options); + inner = build(config); + } - /** Performs a synchronous RPC query. + /** Performs an asynchronous RPC query by spawning a background thread that is executed until + * by either a response is received or the timeout has been expired. * - * Note that this will block the current thread until the query is finished. You are - * encouraged to create a background thread if you don't want to block. - * - * @param query The JSON-encoded RPC query to perform - * @return A JSON-encoded result + * @param query The JSON-encoded RPC query to perform + * @param ttlMillis The maximum time in milliseconds that the query will run + * @param callback An instance of class which must have a instance method named `callback` that will be invoked + * when the result is ready */ - public void rpcQuery(String query, long timeoutMillis, Object callback) { - rpcQueryNative(inner, query, timeoutMillis, callback); - } + public void rpcQuery(String query, long timeoutMillis, Object callback) { + rpcQueryNative(inner, query, timeoutMillis, callback); + } - /** FIXME: docs - * - * - */ - public Object subscribeWebSocket(String query, Object callback) { - return subscribeWebSocketNative(inner, query, callback); - } + /** Subscribes to a specific WebSocket event which will run in a background thread until it is canceled. + * + * @param query The JSON-encoded RPC query to perform + * @param callback An instance of class which must have a instance method named `callback` that will be invoked + * when the result is ready + * + * @return A pointer to the current sessions which can be used to terminate the session later + */ + public long subscribeWebSocket(String query, Object callback) { + return subscribeWebSocketNative(inner, query, callback); + } - /** FIXME: docs - * - * - */ - public void unsubscribeWebSocket(Object session) { - unsubscribeWebSocketNative(session); - } + /** Unsubscribes to a specific WebSocket event + * + * @param session Pointer the the session to terminate + */ + public void unsubscribeWebSocket(long session) { + unsubscribeWebSocketNative(session); + } - @Override - protected void finalize​() { - destroy(inner); - } + // FIXME: `finalize` is deprecated + @Override + protected void finalize​() { + destroy(inner); + } - static { - System.loadLibrary("parity"); - } + static { + System.loadLibrary("parity"); + } - private static native long configFromCli(String[] cliOptions); - private static native long build(long config); - private static native void destroy(long inner); - private static native void rpcQueryNative(long inner, String rpc, long timeoutMillis, Object callback); - private static native Object subscribeWebSocketNative(long inner, String rpc, Object callback); - private static native void unsubscribeWebSocketNative(Object session); + private static native long configFromCli(String[] cliOptions); + private static native long build(long config); + private static native void destroy(long inner); + private static native void rpcQueryNative(long inner, String rpc, long timeoutMillis, Object callback); + private static native long subscribeWebSocketNative(long inner, String rpc, Object callback); + private static native void unsubscribeWebSocketNative(long session); - private long inner; + private long inner; } diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs index c64b7d554a8..8e16a28da4c 100644 --- a/parity-clib/src/java.rs +++ b/parity-clib/src/java.rs @@ -20,7 +20,6 @@ type CheckedQuery<'a> = (&'a RunningClient, String, JavaVM, GlobalRef); struct Callback<'a> { jvm: JavaVM, callback: GlobalRef, - // class: JClass<'a>, method_name: &'a str, method_descriptor: &'a str, value: [JValue<'a>; 4], @@ -44,7 +43,8 @@ impl<'a> Callback<'a> { val[2] = JValue::Long(msg.as_ptr() as i64); val[3] = JValue::Long(msg.len() as i64); let env = self.jvm.attach_current_thread().expect("JavaVM should have an environment; qed"); - env.call_method(self.callback.as_obj(), self.method_name, self.method_descriptor, &val).expect("The method to callmust be static and be named \"void callback(long, long, long, long)\"; qed)"); + env.call_method(self.callback.as_obj(), self.method_name, self.method_descriptor, &val).expect( + "The method to callmust be static and be named \"void callback(long, long, long, long)\"; qed)"); } } @@ -74,7 +74,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_configFromCli(env: let mut out = ptr::null_mut(); match parity_config_from_cli(opts.as_ptr(), opts_lens.as_ptr(), cli_len as usize, &mut out) { - 0 => out as usize as jlong, + 0 => out as jlong, _ => { let _ = env.throw_new("java/lang/Exception", "failed to create config object"); 0 @@ -91,7 +91,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_build(env: JNIEnv, let mut out = ptr::null_mut(); match parity_start(¶ms, &mut out) { - 0 => out as usize as jlong, + 0 => out as jlong, _ => { let _ = env.throw_new("java/lang/Exception", "failed to start Parity"); 0 @@ -112,13 +112,13 @@ unsafe fn async_checker<'a>(client: va_list, rpc: JString, callback: JObject, en let client: &RunningClient = &*(client as *const RunningClient); let jvm = env.get_java_vm().map_err(|e| e.to_string())?; - let g = env.new_global_ref(callback).map_err(|e| e.to_string())?; - Ok((client, query, jvm, g)) + let global_ref = env.new_global_ref(callback).map_err(|e| e.to_string())?; + Ok((client, query, jvm, global_ref)) } #[no_mangle] pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( - env: JNIEnv<'static>, + env: JNIEnv, _: JClass, parity: va_list, rpc: JString, @@ -127,8 +127,8 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( ) { let _ = async_checker(parity, rpc, callback, &env) - .map(|(client, query, jvm, g)| { - let callback = Arc::new(Callback::new(jvm, g, CallbackKind::Rpc)); + .map(|(client, query, jvm, global_ref)| { + let callback = Arc::new(Callback::new(jvm, global_ref, CallbackKind::Rpc)); let cb = callback.clone(); let future = client.rpc_query(&query, None).map(move |response| { let response = response.unwrap_or_else(|| error::EMPTY.to_string()); @@ -142,10 +142,10 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( current_thread.spawn(future); let _ = current_thread.run_timeout(Duration::from_millis(timeout_ms as u64)) .map_err(|_e| { - cb.call(error::TIMEOUT.as_bytes()); - }); + cb.call(error::TIMEOUT.as_bytes()); + }); }) - .expect("rpc-query thread shouldn't fail; qed"); + .expect("rpc-query thread shouldn't fail; qed"); }) .map_err(|e| { let _ = env.throw_new("java/lang/Exception", e); @@ -154,16 +154,16 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( #[no_mangle] pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocketNative( - env: JNIEnv<'static>, + env: JNIEnv, _: JClass, parity: va_list, rpc: JString, callback: JObject, - ) -> *const c_void { + ) -> va_list { - async_checker(parity, rpc, callback, &env) - .map(move |(client, query, jvm, g)| { - let callback = Arc::new(Callback::new(jvm, g, CallbackKind::WebSocket)); + async_checker(parity, rpc, callback, &env) + .map(move |(client, query, jvm, global_ref)| { + let callback = Arc::new(Callback::new(jvm, global_ref, CallbackKind::WebSocket)); let (tx, mut rx) = mpsc::channel(1); let session = Arc::new(PubSubSession::new(tx)); let weak_session = Arc::downgrade(&session); @@ -196,8 +196,8 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocketN } } }) - .expect("rpc-subscriber thread shouldn't fail; qed"); - Arc::into_raw(session) as *const c_void + .expect("rpc-subscriber thread shouldn't fail; qed"); + Arc::into_raw(session) as va_list }) .unwrap_or_else(|e| { let _ = env.throw_new("java/lang/Exception", e); @@ -206,6 +206,9 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocketN } #[no_mangle] -pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_unsubscribeWebSocketNative(session: va_list) { - parity_unsubscribe_ws(session); +pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_unsubscribeWebSocketNative( + _: JNIEnv, + _: JClass, + session: va_list) { + parity_unsubscribe_ws(session as *const c_void); } From e4578357c3d1a9856318dcddaae8e38f98669dbd Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 24 Nov 2018 16:26:46 +0100 Subject: [PATCH 16/24] feat(return valid JString) * Remove Java dependency by constructing a valid Java String in the callback --- parity-clib-examples/java/Main.java | 7 +++---- parity-clib/src/java.rs | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/parity-clib-examples/java/Main.java b/parity-clib-examples/java/Main.java index f1a2347df6f..81a2d44666f 100644 --- a/parity-clib-examples/java/Main.java +++ b/parity-clib-examples/java/Main.java @@ -17,7 +17,6 @@ import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; import io.parity.ethereum.Parity; -import com.sun.jna.Pointer; class Main { public static final int ONE_MINUTE_AS_MILLIS = 60 * 1000; @@ -92,9 +91,9 @@ public Callback() { counter = new AtomicInteger(); } - public void callback(long kind, long userData, long response, long len) { - Pointer responsePointer = new Pointer(response); - String s = responsePointer.getString(0); + public void callback(long kind, Object response) { + response = (String) response; + System.out.println("Response: " + response); counter.getAndIncrement(); } diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs index 8e16a28da4c..eb439c0dcb3 100644 --- a/parity-clib/src/java.rs +++ b/parity-clib/src/java.rs @@ -22,7 +22,7 @@ struct Callback<'a> { callback: GlobalRef, method_name: &'a str, method_descriptor: &'a str, - value: [JValue<'a>; 4], + kind: CallbackKind, } unsafe impl<'a> Send for Callback<'a> {} @@ -33,18 +33,17 @@ impl<'a> Callback<'a> { jvm, callback, method_name: "callback", - method_descriptor: "(JJJJ)V", - value: [JValue::Long(kind as i64), JValue::Long(0), JValue::Long(0), JValue::Long(0)], + method_descriptor: "(JLjava/lang/Object;)V", + kind, } } - fn call(&self, msg: &[u8]) { - let mut val = self.value; - val[2] = JValue::Long(msg.as_ptr() as i64); - val[3] = JValue::Long(msg.len() as i64); + fn call(&self, msg: &str) { let env = self.jvm.attach_current_thread().expect("JavaVM should have an environment; qed"); + let java_str = env.new_string(msg.to_string()).expect("Rust String to JString shouldn't failed; qed"); + let val: [JValue; 2] = [JValue::Long(self.kind as jlong), JValue::Object(JObject::from(java_str))]; env.call_method(self.callback.as_obj(), self.method_name, self.method_descriptor, &val).expect( - "The method to callmust be static and be named \"void callback(long, long, long, long)\"; qed)"); + "The callback must be an instance method and be named \"void callback(long, Object)\"; qed)"); } } @@ -132,7 +131,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( let cb = callback.clone(); let future = client.rpc_query(&query, None).map(move |response| { let response = response.unwrap_or_else(|| error::EMPTY.to_string()); - callback.call(response.as_bytes()); + callback.call(&response); }); let _handle = thread::Builder::new() @@ -142,7 +141,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( current_thread.spawn(future); let _ = current_thread.run_timeout(Duration::from_millis(timeout_ms as u64)) .map_err(|_e| { - cb.call(error::TIMEOUT.as_bytes()); + cb.call(error::TIMEOUT); }); }) .expect("rpc-query thread shouldn't fail; qed"); @@ -176,16 +175,16 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocketN // Note this may block forever and be can't destroyed using the session object // However, this will likely timeout or be catched the RPC layer if let Ok(Some(response)) = query_future.wait() { - callback.call(response.as_bytes()); + callback.call(&response); } else { - callback.call(error::SUBSCRIBE.as_bytes()); + callback.call(error::SUBSCRIBE); return; }; loop { for response in rx.by_ref().wait() { if let Ok(r) = response { - callback.call(r.as_bytes()); + callback.call(&r); } } From 8d73aa28ddb79dce7663b1027142ae01567b6c7e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 26 Nov 2018 10:44:45 +0100 Subject: [PATCH 17/24] fix(logger) : remove `rpc` trace log --- logger/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/logger/src/lib.rs b/logger/src/lib.rs index 6068fa39678..c78c24b29ea 100644 --- a/logger/src/lib.rs +++ b/logger/src/lib.rs @@ -71,7 +71,6 @@ pub fn setup_log(config: &Config) -> Result, String> { builder.filter(Some("ws"), LevelFilter::Warn); builder.filter(Some("hyper"), LevelFilter::Warn); builder.filter(Some("rustls"), LevelFilter::Error); - builder.filter(Some("rpc"), LevelFilter::Trace); // Enable info for others. builder.filter(None, LevelFilter::Info); From fc3bf56a5a5ad4bf17ebd17cf1a8b89b1c3fbd9e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 28 Nov 2018 16:00:07 +0100 Subject: [PATCH 18/24] fix(format) --- parity-clib/Parity.java | 58 ++++++++++++++++++++--------------------- parity-clib/parity.h | 6 ++--- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/parity-clib/Parity.java b/parity-clib/Parity.java index a0559b58808..6199c45d6e2 100644 --- a/parity-clib/Parity.java +++ b/parity-clib/Parity.java @@ -20,52 +20,52 @@ * Interface to the Parity client. */ public class Parity { - /** - * Starts the Parity client with the CLI options passed as an array of strings. - * - * Each space-delimited option corresponds to an array entry. - * For example: `["--port", "12345"]` - * - * @param options The CLI options to start Parity with - */ + /** + * Starts the Parity client with the CLI options passed as an array of strings. + * + * Each space-delimited option corresponds to an array entry. + * For example: `["--port", "12345"]` + * + * @param options The CLI options to start Parity with + */ public Parity(String[] options) { long config = configFromCli(options); inner = build(config); } - /** Performs an asynchronous RPC query by spawning a background thread that is executed until - * by either a response is received or the timeout has been expired. - * - * @param query The JSON-encoded RPC query to perform - * @param ttlMillis The maximum time in milliseconds that the query will run - * @param callback An instance of class which must have a instance method named `callback` that will be invoked - * when the result is ready - */ + /** Performs an asynchronous RPC query by spawning a background thread that is executed until + * either a response is received or the timeout has been expired. + * + * @param query The JSON-encoded RPC query to perform + * @param timeoutMillis The maximum time in milliseconds that the query will run + * @param callback An instance of class which must have a instance method named `callback` that will be + * invoke when the result is ready + */ public void rpcQuery(String query, long timeoutMillis, Object callback) { rpcQueryNative(inner, query, timeoutMillis, callback); } - /** Subscribes to a specific WebSocket event which will run in a background thread until it is canceled. - * - * @param query The JSON-encoded RPC query to perform - * @param callback An instance of class which must have a instance method named `callback` that will be invoked - * when the result is ready - * - * @return A pointer to the current sessions which can be used to terminate the session later - */ + /** Subscribes to a specific WebSocket event that will run in a background thread until it is canceled. + * + * @param query The JSON-encoded RPC query to perform + * @param callback An instance of class which must have a instance method named `callback` that will be invoked + * when the result is ready + * + * @return A pointer to the current sessions which can be used to terminate the session later + */ public long subscribeWebSocket(String query, Object callback) { return subscribeWebSocketNative(inner, query, callback); } - /** Unsubscribes to a specific WebSocket event - * - * @param session Pointer the the session to terminate - */ + /** Unsubscribes to a specific WebSocket event + * + * @param session Pointer the the session to terminate + */ public void unsubscribeWebSocket(long session) { unsubscribeWebSocketNative(session); } - // FIXME: `finalize` is deprecated + // FIXME: `finalize` is deprecated @Override protected void finalize​() { destroy(inner); diff --git a/parity-clib/parity.h b/parity-clib/parity.h index d06997991be..b64ae31708a 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -99,9 +99,9 @@ void parity_destroy(void* parity); /// - timeout_ms : Maximum time that request is waiting for a response /// - response : Callback to invoke when the query gets answered. It will respond with a JSON encoded the string /// with the result -/// - ud : Specific user defined data that can used in the callback ("response) -// -// +/// - ud : Specific user defined data that can used in the callback +/// +/// /// - On success : The parity client reference and the query string were valid /// - On error : The parity client reference and the query string were not valid /// From 269f8e8f4aa74e2d7893e3dc03f4ce3597e26c7a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 29 Nov 2018 13:25:12 +0100 Subject: [PATCH 19/24] fix(parity-clib): remove needless callback `type` --- parity-clib/parity.h | 13 ++++--------- parity-clib/src/java.rs | 20 +++++++++----------- parity-clib/src/lib.rs | 21 +++++---------------- 3 files changed, 18 insertions(+), 36 deletions(-) diff --git a/parity-clib/parity.h b/parity-clib/parity.h index b64ae31708a..c820c873792 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -17,11 +17,6 @@ #ifndef _PARITY_H_INCLUDED_ #define _PARITY_H_INCLUDED_ -#define PARITY_CALLBACK_RESTART 0 -#define PARITY_CALLBACK_RPC 1 -#define PARITY_CALLBACK_WEBSOCKET 2 -#define PARITY_CALLBACK_PANIC_HOOK 3 - #include /// Parameters to pass to `parity_start`. @@ -36,7 +31,7 @@ struct ParityParams { /// /// The first parameter of the callback is the value of `on_client_restart_cb_custom`. /// The second and third parameters of the callback are the string pointer and length. - void (*on_client_restart_cb)(size_t callback_kind, void* custom, const char* new_chain, size_t new_chain_len); + void (*on_client_restart_cb)(void* custom, const char* new_chain, size_t new_chain_len); /// Custom parameter passed to the `on_client_restart_cb` callback as first parameter. void *on_client_restart_cb_custom; @@ -106,7 +101,7 @@ void parity_destroy(void* parity); /// - On error : The parity client reference and the query string were not valid /// int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, - void (*subscribe)(size_t callback_kind, void* ud, const char* response, size_t len), void* ud); + void (*subscribe)(void* ud, const char* response, size_t len), void* ud); /// Subscribes to a specific websocket event that will run until it is canceled @@ -122,7 +117,7 @@ int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, /// - On error : The function returns a null pointer /// void* parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, - void (*subscribe)(size_t callback_kind, void* ud, const char* response, size_t len), void* ud); + void (*subscribe)(void* ud, const char* response, size_t len), void* ud); /// Unsubscribes from a websocket subscription. Caution this function consumes the session object and must only be /// used exactly once per session. @@ -146,7 +141,7 @@ int parity_unsubscribe_ws(const void *const session); /// The callback can be called from any thread and multiple times simultaneously. Make sure that /// your code is thread safe. /// -int parity_set_panic_hook(void (*cb)(size_t callback_kind, void* param, const char* msg, size_t msg_len), void* param); +int parity_set_panic_hook(void (*cb)(void* param, const char* msg, size_t msg_len), void* param); #ifdef __cplusplus } diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs index eb439c0dcb3..f634de64263 100644 --- a/parity-clib/src/java.rs +++ b/parity-clib/src/java.rs @@ -4,7 +4,7 @@ use std::time::Duration; use std::thread; use std::os::raw::c_void; -use {CallbackKind, parity_config_from_cli, parity_destroy, parity_start, parity_unsubscribe_ws, ParityParams, error}; +use {parity_config_from_cli, parity_destroy, parity_start, parity_unsubscribe_ws, ParityParams, error}; use futures::{Future, Stream}; use futures::sync::mpsc; @@ -16,34 +16,32 @@ use parity_ethereum::{RunningClient, PubSubSession}; type CheckedQuery<'a> = (&'a RunningClient, String, JavaVM, GlobalRef); -// Creates a Java callback to a static method named `void callback(long, long, long, long)` +// Creates a Java callback to a static method named `void callback(Object)` struct Callback<'a> { jvm: JavaVM, callback: GlobalRef, method_name: &'a str, method_descriptor: &'a str, - kind: CallbackKind, } unsafe impl<'a> Send for Callback<'a> {} unsafe impl<'a> Sync for Callback<'a> {} impl<'a> Callback<'a> { - fn new(jvm: JavaVM, callback: GlobalRef, kind: CallbackKind) -> Self { + fn new(jvm: JavaVM, callback: GlobalRef) -> Self { Self { jvm, callback, method_name: "callback", - method_descriptor: "(JLjava/lang/Object;)V", - kind, + method_descriptor: "(Ljava/lang/Object;)V", } } fn call(&self, msg: &str) { let env = self.jvm.attach_current_thread().expect("JavaVM should have an environment; qed"); let java_str = env.new_string(msg.to_string()).expect("Rust String to JString shouldn't failed; qed"); - let val: [JValue; 2] = [JValue::Long(self.kind as jlong), JValue::Object(JObject::from(java_str))]; - env.call_method(self.callback.as_obj(), self.method_name, self.method_descriptor, &val).expect( - "The callback must be an instance method and be named \"void callback(long, Object)\"; qed)"); + let val = &[JValue::Object(JObject::from(java_str))]; + env.call_method(self.callback.as_obj(), self.method_name, self.method_descriptor, val).expect( + "The callback must be an instance method and be named \"void callback(Object)\"; qed)"); } } @@ -127,7 +125,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( { let _ = async_checker(parity, rpc, callback, &env) .map(|(client, query, jvm, global_ref)| { - let callback = Arc::new(Callback::new(jvm, global_ref, CallbackKind::Rpc)); + let callback = Arc::new(Callback::new(jvm, global_ref)); let cb = callback.clone(); let future = client.rpc_query(&query, None).map(move |response| { let response = response.unwrap_or_else(|| error::EMPTY.to_string()); @@ -162,7 +160,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocketN async_checker(parity, rpc, callback, &env) .map(move |(client, query, jvm, global_ref)| { - let callback = Arc::new(Callback::new(jvm, global_ref, CallbackKind::WebSocket)); + let callback = Arc::new(Callback::new(jvm, global_ref)); let (tx, mut rx) = mpsc::channel(1); let session = Arc::new(PubSubSession::new(tx)); let weak_session = Arc::downgrade(&session); diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index 42d3457578e..4a8f382c7d5 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -40,7 +40,7 @@ use futures::sync::mpsc; use parity_ethereum::{PubSubSession, RunningClient}; use tokio_current_thread::CurrentThread; -type Callback = Option; +type Callback = Option; type CheckedQuery<'a> = (&'a RunningClient, &'static str); pub mod error { @@ -49,15 +49,6 @@ pub mod error { pub const SUBSCRIBE: &str = r#"{"jsonrpc":"2.0","result":"subcribe_fail","id":1}"#; } -#[derive(Debug, Copy, Clone)] -#[repr(usize)] -enum CallbackKind { - Restart = 0, - Rpc = 1, - WebSocket = 2, - PanicHook = 3 -} - #[repr(C)] pub struct ParityParams { pub configuration: *mut c_void, @@ -125,7 +116,6 @@ pub unsafe extern fn parity_start(cfg: *const ParityParams, output: *mut *mut c_ let on_client_restart_cb = { let cb = CallbackStr { - kind: CallbackKind::Restart, user_data: cfg.on_client_restart_cb_custom, function: cfg.on_client_restart_cb, }; @@ -180,7 +170,7 @@ pub unsafe extern fn parity_rpc( panic::catch_unwind(|| { if let Some((client, query)) = parity_rpc_query_checker(client, query, len) { let client = client as &RunningClient; - let callback = Arc::new(CallbackStr { kind: CallbackKind::Rpc, user_data, function: callback}); + let callback = Arc::new(CallbackStr {user_data, function: callback} ); let cb = callback.clone(); let query = client.rpc_query(query, None) .map(move |response| { @@ -221,7 +211,7 @@ pub unsafe extern fn parity_subscribe_ws( let session = Arc::new(PubSubSession::new(tx)); let query_future = client.rpc_query(query, Some(session.clone())); let weak_session = Arc::downgrade(&session); - let cb = CallbackStr { kind: CallbackKind::WebSocket, user_data, function: callback}; + let cb = CallbackStr { user_data, function: callback}; let _handle = thread::Builder::new() .name("ws-subscriber".into()) @@ -268,7 +258,7 @@ pub unsafe extern fn parity_unsubscribe_ws(session: *const c_void) { #[no_mangle] pub unsafe extern fn parity_set_panic_hook(callback: Callback, param: *mut c_void) { - let cb = CallbackStr {kind: CallbackKind::PanicHook, user_data: param, function: callback}; + let cb = CallbackStr {user_data: param, function: callback}; panic_hook::set_with(move |panic_msg| { cb.call(panic_msg.as_bytes()); }); @@ -276,7 +266,6 @@ pub unsafe extern fn parity_set_panic_hook(callback: Callback, param: *mut c_voi // Internal structure for handling callbacks that get passed a string. struct CallbackStr { - kind: CallbackKind, user_data: *mut c_void, function: Callback, } @@ -287,7 +276,7 @@ impl CallbackStr { fn call(&self, msg: &[u8]) { if let Some(ref cb) = self.function { let cstr = CString::new(msg).expect("valid string with no null bytes in the middle; qed").into_raw(); - cb(self.kind as usize, self.user_data, cstr, msg.len()) + cb(self.user_data, cstr, msg.len()) } } } From 019ab72db9b2d5090cf5d7e6fce10f0ea78d7e29 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 29 Nov 2018 13:27:38 +0100 Subject: [PATCH 20/24] fix(parity-clib-examples) : update examples * `cpp` example pass in a struct instead to determines `callback kind` * `java` add a instance variable the class `Callback` to determine `callback kind` --- parity-clib-examples/java/Main.java | 16 ++++++++---- parity-clib/examples/cpp/main.cpp | 40 ++++++++++++++--------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/parity-clib-examples/java/Main.java b/parity-clib-examples/java/Main.java index 81a2d44666f..88189af1c53 100644 --- a/parity-clib-examples/java/Main.java +++ b/parity-clib-examples/java/Main.java @@ -37,8 +37,8 @@ class Main { public static void runParity(String[] config) { Parity parity = new Parity(config); - Callback rpcCallback = new Callback(); - Callback webSocketCallback = new Callback(); + Callback rpcCallback = new Callback(1); + Callback webSocketCallback = new Callback(2); for (String query : rpc_queries) { parity.rpcQuery(query, ONE_MINUTE_AS_MILLIS, rpcCallback); @@ -86,14 +86,20 @@ public static void main(String[] args) { class Callback { private AtomicInteger counter; + private final int callbackType; - public Callback() { + public Callback(int type) { counter = new AtomicInteger(); + callbackType = type; } - public void callback(long kind, Object response) { + public void callback(Object response) { response = (String) response; - System.out.println("Response: " + response); + if (callbackType == 1) { + System.out.println("rpc: " + response); + } else if (callbackType == 2) { + System.out.println("ws: " + response); + } counter.getAndIncrement(); } diff --git a/parity-clib/examples/cpp/main.cpp b/parity-clib/examples/cpp/main.cpp index ae643e6bcb1..aab05c9066c 100644 --- a/parity-clib/examples/cpp/main.cpp +++ b/parity-clib/examples/cpp/main.cpp @@ -26,6 +26,13 @@ int parity_rpc_queries(void*); const int SUBSCRIPTION_ID_LEN = 18; const size_t TIMEOUT_ONE_MIN_AS_MILLIS = 60 * 1000; +const unsigned int CALLBACK_RPC = 1; +const unsigned int CALLBACK_WS = 2; + +struct Callback { + unsigned int type; + long unsigned int counter; +}; // list of rpc queries const std::vector rpc_queries { @@ -43,26 +50,18 @@ const std::vector ws_subscriptions { }; // callback that gets invoked upon an event -void callback(size_t type, void* ud, const char* response, size_t _len) { - if (type == PARITY_CALLBACK_RESTART) { - printf("restart\r\n"); - } else if (type == PARITY_CALLBACK_RPC) { +void callback(void* user_data, const char* response, size_t _len) { + Callback* cb = static_cast(user_data); + if (cb->type == CALLBACK_RPC) { printf("rpc response: %s\r\n", response); - size_t* counter = static_cast(ud); - *counter -= 1; - } else if (type == PARITY_CALLBACK_WEBSOCKET) { + cb->counter -= 1; + } else if (cb->type == CALLBACK_WS) { printf("websocket response: %s\r\n", response); std::regex is_subscription ("\\{\"jsonrpc\":\"2.0\",\"result\":\"0[xX][a-fA-F0-9]{16}\",\"id\":1\\}"); if (std::regex_match(response, is_subscription) == true) { - size_t* counter = static_cast(ud); - *counter -= 1; + cb->counter -= 1; } } - else if (type == PARITY_CALLBACK_PANIC_HOOK) { - printf("panic hook\r\n"); - } else { - printf("Not supported callback id\r\n"); - } } int main() { @@ -112,15 +111,15 @@ int parity_rpc_queries(void* parity) { return 1; } - size_t num_queries = rpc_queries.size(); + Callback cb { .type = CALLBACK_RPC, .counter = rpc_queries.size() }; for (auto query : rpc_queries) { - if (parity_rpc(parity, query.c_str(), query.length(), TIMEOUT_ONE_MIN_AS_MILLIS, callback, &num_queries) != 0) { + if (parity_rpc(parity, query.c_str(), query.length(), TIMEOUT_ONE_MIN_AS_MILLIS, callback, &cb) != 0) { return 1; } } - while(num_queries != 0); + while(cb.counter != 0); return 0; } @@ -131,17 +130,18 @@ int parity_subscribe_to_websocket(void* parity) { } std::vector sessions; - size_t num_subs = ws_subscriptions.size(); + + Callback cb { .type = CALLBACK_WS, .counter = ws_subscriptions.size() }; for (auto sub : ws_subscriptions) { - void *const session = parity_subscribe_ws(parity, sub.c_str(), sub.length(), callback, &num_subs); + void *const session = parity_subscribe_ws(parity, sub.c_str(), sub.length(), callback, &cb); if (!session) { return 1; } sessions.push_back(session); } - while(num_subs != 0); + while(cb.counter != 0); std::this_thread::sleep_for(std::chrono::seconds(60)); for (auto session : sessions) { parity_unsubscribe_ws(session); From 981d320bcf8acea95c1feb7c62241cf86e30b2b7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 14 Dec 2018 12:12:42 +0100 Subject: [PATCH 21/24] fix(review comments): docs and format --- parity-clib/Parity.java | 2 +- parity-clib/parity.h | 23 +++++++++++------------ parity-clib/src/java.rs | 2 +- parity-clib/src/lib.rs | 7 +++---- parity/run.rs | 9 +++------ 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/parity-clib/Parity.java b/parity-clib/Parity.java index 6199c45d6e2..37a6722b781 100644 --- a/parity-clib/Parity.java +++ b/parity-clib/Parity.java @@ -65,7 +65,7 @@ public void unsubscribeWebSocket(long session) { unsubscribeWebSocketNative(session); } - // FIXME: `finalize` is deprecated + // FIXME: `finalize` is deprecated - https://github.com/paritytech/parity-ethereum/issues/10066 @Override protected void finalize​() { destroy(inner); diff --git a/parity-clib/parity.h b/parity-clib/parity.h index c820c873792..71d6ca775db 100644 --- a/parity-clib/parity.h +++ b/parity-clib/parity.h @@ -93,12 +93,11 @@ void parity_destroy(void* parity); /// - len : Length of the RPC query /// - timeout_ms : Maximum time that request is waiting for a response /// - response : Callback to invoke when the query gets answered. It will respond with a JSON encoded the string -/// with the result +/// with the result both on success and error. /// - ud : Specific user defined data that can used in the callback /// -/// -/// - On success : The parity client reference and the query string were valid -/// - On error : The parity client reference and the query string were not valid +/// - On success : The function returns 0 +/// - On error : The function returns 1 /// int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, size_t timeout_ms, void (*subscribe)(void* ud, const char* response, size_t len), void* ud); @@ -106,15 +105,15 @@ int parity_rpc(const void *const parity, const char* rpc_query, size_t rpc_len, /// Subscribes to a specific websocket event that will run until it is canceled /// -/// - parity : Reference to the running parity client -/// - ws_query : JSON encoded string representing the websocket event to subscribe to -/// - len : Length of the query -/// - response : Callback to invoke when a websocket event occurs -/// - ud : Specific user defined data that can used in the callback +/// - parity : Reference to the running parity client +/// - ws_query : JSON encoded string representing the websocket event to subscribe to +/// - len : Length of the query +/// - response : Callback to invoke when a websocket event occurs +/// - ud : Specific user defined data that can used in the callback /// -/// - On success : The function returns an object to the current session +/// - On success : The function returns an object to the current session /// which can be used cancel the subscription -/// - On error : The function returns a null pointer +/// - On error : The function returns a null pointer /// void* parity_subscribe_ws(const void *const parity, const char* ws_query, size_t len, void (*subscribe)(void* ud, const char* response, size_t len), void* ud); @@ -122,7 +121,7 @@ void* parity_subscribe_ws(const void *const parity, const char* ws_query, size_t /// Unsubscribes from a websocket subscription. Caution this function consumes the session object and must only be /// used exactly once per session. /// -/// - session : Pointer to the session to unsubscribe from +/// - session : Pointer to the session to unsubscribe from /// int parity_unsubscribe_ws(const void *const session); diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs index f634de64263..a41c6c9d54e 100644 --- a/parity-clib/src/java.rs +++ b/parity-clib/src/java.rs @@ -38,7 +38,7 @@ impl<'a> Callback<'a> { fn call(&self, msg: &str) { let env = self.jvm.attach_current_thread().expect("JavaVM should have an environment; qed"); - let java_str = env.new_string(msg.to_string()).expect("Rust String to JString shouldn't failed; qed"); + let java_str = env.new_string(msg.to_string()).expect("Rust String is valid JString; qed"); let val = &[JValue::Object(JObject::from(java_str))]; env.call_method(self.callback.as_obj(), self.method_name, self.method_descriptor, val).expect( "The callback must be an instance method and be named \"void callback(Object)\"; qed)"); diff --git a/parity-clib/src/lib.rs b/parity-clib/src/lib.rs index 4a8f382c7d5..b4f776c89c0 100644 --- a/parity-clib/src/lib.rs +++ b/parity-clib/src/lib.rs @@ -172,10 +172,9 @@ pub unsafe extern fn parity_rpc( let client = client as &RunningClient; let callback = Arc::new(CallbackStr {user_data, function: callback} ); let cb = callback.clone(); - let query = client.rpc_query(query, None) - .map(move |response| { - let response = response.unwrap_or_else(|| error::EMPTY.to_string()); - callback.call(response.as_bytes()); + let query = client.rpc_query(query, None).map(move |response| { + let response = response.unwrap_or_else(|| error::EMPTY.to_string()); + callback.call(response.as_bytes()); }); let _handle = thread::Builder::new() diff --git a/parity/run.rs b/parity/run.rs index 1f2b9ffcfd7..6dad5fd6d54 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -877,6 +877,7 @@ enum RunningClientInner { impl RunningClient { /// Performs an asynchronous RPC query. + // FIXME: [tomaka] This API should be better, with for example a Future pub fn rpc_query(&self, request: &str, session: Option>) -> FutureResult { @@ -886,12 +887,8 @@ impl RunningClient { }; match self.inner { - RunningClientInner::Light { ref rpc, .. } => { - rpc.handle_request(request, metadata) - }, - RunningClientInner::Full { ref rpc, .. } => { - rpc.handle_request(request, metadata) - }, + RunningClientInner::Light { ref rpc, .. } => rpc.handle_request(request, metadata), + RunningClientInner::Full { ref rpc, .. } => rpc.handle_request(request, metadata), } } From 6014461468bc8b1c83cb89a01316b186749cb1ae Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Sun, 16 Dec 2018 10:00:06 +0100 Subject: [PATCH 22/24] Update parity-clib/src/java.rs Co-Authored-By: niklasad1 --- parity-clib/src/java.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs index a41c6c9d54e..89b3e98f286 100644 --- a/parity-clib/src/java.rs +++ b/parity-clib/src/java.rs @@ -142,7 +142,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_rpcQueryNative( cb.call(error::TIMEOUT); }); }) - .expect("rpc-query thread shouldn't fail; qed"); + .expect("rpc-query thread shouldn't fail; qed"); }) .map_err(|e| { let _ = env.throw_new("java/lang/Exception", e); From a50b062a639b74bb862d5b6995568a75675ef6aa Mon Sep 17 00:00:00 2001 From: niklasad1 Date: Wed, 2 Jan 2019 14:02:10 +0100 Subject: [PATCH 23/24] fix(bad merge + spelling) --- parity-clib/Cargo.toml | 2 +- parity-clib/src/java.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parity-clib/Cargo.toml b/parity-clib/Cargo.toml index 4072494e4a6..b3635c6e08f 100644 --- a/parity-clib/Cargo.toml +++ b/parity-clib/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["cdylib", "staticlib"] [dependencies] futures = "0.1.6" jni = { version = "0.10.1", optional = true } -panic_hook = { path = "../util/panic_hook" } +panic_hook = { path = "../util/panic-hook" } parity-ethereum = { path = "../", default-features = false } tokio = "0.1.11" tokio-current-thread = "0.1.3" diff --git a/parity-clib/src/java.rs b/parity-clib/src/java.rs index 89b3e98f286..30e63e60162 100644 --- a/parity-clib/src/java.rs +++ b/parity-clib/src/java.rs @@ -170,7 +170,7 @@ pub unsafe extern "system" fn Java_io_parity_ethereum_Parity_subscribeWebSocketN .name("ws-subscriber".into()) .spawn(move || { // Wait for subscription ID - // Note this may block forever and be can't destroyed using the session object + // Note this may block forever and can't be destroyed using the session object // However, this will likely timeout or be catched the RPC layer if let Ok(Some(response)) = query_future.wait() { callback.call(&response); From 9164b327b97faf6c1d240dccef833b3ab7ad36b4 Mon Sep 17 00:00:00 2001 From: niklasad1 Date: Wed, 2 Jan 2019 15:39:01 +0100 Subject: [PATCH 24/24] fix(move examples to parity-clib/examples) --- parity-clib/examples/cpp/CMakeLists.txt | 2 +- .../examples}/java/Main.java | 0 parity-clib/examples/java/README.md | 9 +++++++++ .../examples}/java/run.sh | 8 ++++++-- 4 files changed, 16 insertions(+), 3 deletions(-) rename {parity-clib-examples => parity-clib/examples}/java/Main.java (100%) create mode 100644 parity-clib/examples/java/README.md rename {parity-clib-examples => parity-clib/examples}/java/run.sh (52%) diff --git a/parity-clib/examples/cpp/CMakeLists.txt b/parity-clib/examples/cpp/CMakeLists.txt index ce68915884b..d3aaf457b3b 100644 --- a/parity-clib/examples/cpp/CMakeLists.txt +++ b/parity-clib/examples/cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) include(ExternalProject) -include_directories("${CMAKE_SOURCE_DIR}/../../parity-clib") +include_directories("${CMAKE_SOURCE_DIR}/../..") set (CMAKE_CXX_STANDARD 11) # Enfore C++11 add_executable(parity-example main.cpp) diff --git a/parity-clib-examples/java/Main.java b/parity-clib/examples/java/Main.java similarity index 100% rename from parity-clib-examples/java/Main.java rename to parity-clib/examples/java/Main.java diff --git a/parity-clib/examples/java/README.md b/parity-clib/examples/java/README.md new file mode 100644 index 00000000000..ec83905bf2b --- /dev/null +++ b/parity-clib/examples/java/README.md @@ -0,0 +1,9 @@ +parity-clib: Java example +=================================== + +An example Java application to demonstrate how to use `jni` bindings to parity-ethereum. Note, that the example is built in debug-mode to reduce the build time. If you want to use it in real project use release-mode instead to facilitate all compiler optimizations. + +## How to compile and run + +1. Make sure you have installed [JDK](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) +2. Run `run.sh` \ No newline at end of file diff --git a/parity-clib-examples/java/run.sh b/parity-clib/examples/java/run.sh similarity index 52% rename from parity-clib-examples/java/run.sh rename to parity-clib/examples/java/run.sh index 30d0b77eccc..428a7dc751f 100755 --- a/parity-clib-examples/java/run.sh +++ b/parity-clib/examples/java/run.sh @@ -1,10 +1,14 @@ #!/usr/bin/env bash FLAGS="-Xlint:deprecation" -PARITY_JAVA="../../parity-clib/Parity.java" -PARITY_LIB=".:../../target/debug/" +PARITY_JAVA="../../Parity.java" +# parity-clib must be built with feature `jni` in debug-mode to work +PARITY_LIB=".:../../../target/debug/" # build +cd .. +cargo build --features jni +cd - javac $FLAGS -d $PWD $PARITY_JAVA javac $FLAGS *.java # Setup the path `libparity.so` and run