diff --git a/Cargo.lock b/Cargo.lock index ba62ddef50eefaf84463c67904569113bcbc42f2..13e6e51102a2a39da946259403a4dd645ea99971 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,28 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +[[package]] +name = "backtrace" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" +dependencies = [ + "backtrace-sys", + "cfg-if", + "libc", + "rustc-demangle", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fbebbe1c9d1f383a9cc7e8ccdb471b91c8d024ee9c2ca5b5346121fe8b4399" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "base64" version = "0.11.0" @@ -229,15 +251,20 @@ dependencies = [ [[package]] name = "duniteroxyde" -version = "0.2.9" +version = "0.3.0" dependencies = [ "bincode", "bs58", + "dubp-pow", "dubp-wot", + "duniter-common-tools", "dup-crypto", "flate2", "neon", "neon-build", + "neon-serde", + "serde", + "serde_json", ] [[package]] @@ -262,6 +289,16 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +[[package]] +name = "error-chain" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" +dependencies = [ + "backtrace", + "version_check", +] + [[package]] name = "flate2" version = "1.0.14" @@ -312,6 +349,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + [[package]] name = "js-sys" version = "0.3.37" @@ -384,9 +427,9 @@ dependencies = [ [[package]] name = "neon" -version = "0.4.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cac4691701b686e6c07b2eb5b51a9f26f5c11179c5d7924b78100dd387fc99d" +checksum = "53b85accbbd250627f899a6fc1f220bbb4c8c2ff6dc71830dc6b752b39c2eb97" dependencies = [ "cslice", "neon-build", @@ -396,9 +439,9 @@ dependencies = [ [[package]] name = "neon-build" -version = "0.4.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9ed332afd4711b84f4f83d334428a1fd9ce53620b62b87595934297c5ede2ed" +checksum = "ae406bf1065c4399e69d328a3bd8d4f088f2a205dc3881bf68c0ac775bfef337" dependencies = [ "cfg-if", "neon-sys", @@ -406,24 +449,101 @@ dependencies = [ [[package]] name = "neon-runtime" -version = "0.4.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2beea093a60c08463f65e1da4cda68149986f60d8d2177489b44589463c782a6" +checksum = "d8465ac4ed3f340dead85e053b75a5f639f48ac6343b3523eff90a751758eead" dependencies = [ "cfg-if", "neon-sys", ] +[[package]] +name = "neon-serde" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45847cc4cec46db1ff2e921cd9948b38afa0c2348c99c06f9ae70406a30b60" +dependencies = [ + "error-chain", + "neon", + "neon-runtime", + "num", + "serde", +] + [[package]] name = "neon-sys" -version = "0.4.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69a6c1ba6b926746f4d3f596de18ce49d062d78fd9f35f636080232aa77a0e16" +checksum = "8ae4cf3871ca5a395077e68144c1754e94e9e1e3329e7f8399d999ca573ed89a" dependencies = [ "cc", "regex", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -577,6 +697,18 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "ryu" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" + [[package]] name = "scopeguard" version = "1.1.0" @@ -618,6 +750,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "shrinkwraprs" version = "0.3.0" @@ -719,6 +862,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/neon/lib/index.ts b/neon/lib/index.ts index 48fdad95ce394d4af0b1875f0940595960e531aa..5dec97d8e4f1f977ec6955ab5c64009d27ab4ec6 100644 --- a/neon/lib/index.ts +++ b/neon/lib/index.ts @@ -1,3 +1,4 @@ -export { Ed25519Signator, generateRandomSeed, seedToSecretKey, sha256, verify, Wot } from "../native"; +export { Ed25519Signator, generateRandomSeed, seedToSecretKey, sha256, verify, PowConf, PowQuery, ValidProof, Wot } from "../native"; export { KeyPairBuilder } from "./crypto"; +export { PowCluster } from "./pow"; export { WotBuilder } from "./wot"; diff --git a/neon/lib/pow.ts b/neon/lib/pow.ts new file mode 100644 index 0000000000000000000000000000000000000000..fed7a3a697b7dca69b651eefd8d7a64af4178840 --- /dev/null +++ b/neon/lib/pow.ts @@ -0,0 +1,95 @@ +import { PowCluster as RustPowCluster } from "../native"; +import { EventEmitter } from 'events'; +import { PowQuery } from "../native/pow"; + +// The `PowCluster` class provides glue code to abstract the `poll` +// interface provided by the Neon class. It may be constructed and used +// as a normal `EventEmitter`, including use by multiple subscribers. +export class PowCluster extends EventEmitter { + + private logger: any; + private rustCluster: RustPowCluster; + private isComputing: boolean; + + constructor(logger: any, nbCores: number | null) { + super(); + + this.logger = logger; + + // Create an instance of the Neon class + this.rustCluster = new RustPowCluster(nbCores); + + this.isComputing = false; + } + + getWorkersCount() { + return this.rustCluster.getWorkersCount(); + } + + startPow(powQuery: PowQuery, timeout: number): void { + + this.isComputing = true; + this.rustCluster.startPow(powQuery); + + setTimeout(() => { + if (this.isComputing) { + this.isComputing = false; + this.emit('proof', null); + this.rustCluster.stopPow(false); + } + }, timeout) + + // The `loop` method is called continuously to receive data from the Rust + // work thread. + const loop = () => { + // Poll for data + this.rustCluster.poll((err, powEvent) => { + if (err) { + this.emit('error', err); + } else if (powEvent) { + if (powEvent.cancelled) { + this.logger.debug("prover: rust pow cluster emit pow cancellation signal"); + this.emit('proof', null); + } else if (powEvent.proof) { + this.logger.debug("prover: rust pow cluster emit valid proof"); + this.emit('proof', powEvent.proof); + } + } else { + // Otherwise, timeout on poll, no data to emit + + // Schedule the next iteration of the loop. This is performed with + // a `setImmediate` to yield to the event loop, to let JS code run + // and avoid a stack overflow. + setImmediate(loop); + } + }); + + // Stop the receiving loop and shutdown the work thead. However, since + // the `poll` method uses a blocking `recv_timeout`, this code will not execute + // until either the next event is sent on the channel or a receive + // timeout has occurred. + if (!this.isComputing) { + return; + } + }; + + // Start the polling loop on next iteration of the JS event loop to prevent zalgo. + setImmediate(loop); + } + + updateConf(newCpu: number, newSecretKey: string, newPrefix: number): void { + this.rustCluster.updateConf(newCpu, newSecretKey, newPrefix); + } + + stopPow(notify: boolean): void { + this.rustCluster.stopPow(notify); + this.isComputing = false; + } + + quit(): void { + this.rustCluster.quit(); + this.isComputing = false; + } +} + +module.exports = { PowCluster }; diff --git a/neon/native/Cargo.toml b/neon/native/Cargo.toml index 7331a0a471d4617948e4b9b10b3531428ad46393..89696b9dcd44e86a71b3dd1dc67026a01f5b8c03 100644 --- a/neon/native/Cargo.toml +++ b/neon/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "duniteroxyde" -version = "0.2.9" +version = "0.3.0" authors = ["librelois <elois@ifee.fr>"] license = "AGPL-3.0" build = "build.rs" @@ -12,12 +12,17 @@ name = "duniteroxyde" crate-type = ["cdylib"] [build-dependencies] -neon-build = "0.4.0" +neon-build = "0.3.3" [dependencies] bincode = "1.2.1" bs58 = "0.3.0" +duniter-common-tools = { path = "../../rust-libs/common-tools" } dup-crypto = { version = "0.15.0", default-features = false, features = ["rand"] } +dubp-pow = { path="../../rust-libs/pow" } dubp-wot = "0.11.0" flate2 = "1.0.14" -neon = "0.4.0" +neon = "0.3.3" +neon-serde = "0.3.0" +serde = { version = "1.0.*", features = ["derive"] } +serde_json = "1.0.52" diff --git a/neon/native/index.d.ts b/neon/native/index.d.ts index e31c8cbdf33858186239385d59905349669bc4d6..476645481f13f72ddf6d8eafca963322e2f71b11 100644 --- a/neon/native/index.d.ts +++ b/neon/native/index.d.ts @@ -1,6 +1,7 @@ /* tslint:disable */ import * as _crypto from './crypto'; +import * as _pow from './pow'; import * as _wot from './wot'; export import Ed25519Signator = _crypto.Ed25519Signator; @@ -9,5 +10,10 @@ export import seedToSecretKey = _crypto.seedToSecretKey; export import sha256 = _crypto.sha256; export import verify = _crypto.verify; +export import PowCluster = _pow.PowCluster; +export import PowConf = _pow.PowConf; +export import PowQuery = _pow.PowQuery; +export import ValidProof = _pow.ValidProof; + export import Wot = _wot.Wot; export import DetailedDistance = _wot.DetailedDistance; diff --git a/neon/native/pow.d.ts b/neon/native/pow.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c3d6df40bff9dc944d55e1237d99b74212b432e --- /dev/null +++ b/neon/native/pow.d.ts @@ -0,0 +1,47 @@ +/* tslint:disable */ + +import { EventEmitter } from "events"; + +export class PowCluster extends EventEmitter { + constructor(nbCores: number | null) + + getWorkersCount(): number + + startPow(powQuery: PowQuery): void + + poll(callback: (err: any, event: any) => void): void + + updateConf( + newCpu: number, + newSecretKey: string, + newPrefix: number + ): void + + stopPow(notifyRequestor: boolean): void + + quit(): void +} + +export class PowConf { + cpu: number + secretKey: string + prefix: number +} + +export class PowQuery { + blockInnerHash: string + cpu: number + diff: number + marker: number + nbCores?: number + prefix: number + secretKey: string +} + +export class ValidProof { + itersCount: number + nonce: number + sig: string + hash: string + workerId: number +} diff --git a/neon/native/src/crypto.rs b/neon/native/src/crypto.rs index 76414e8dfbdc51a91b14a6dc7acf7bef828a2186..c8b8680fee1fc5c6dbdc8e1947a2eb5835599a39 100644 --- a/neon/native/src/crypto.rs +++ b/neon/native/src/crypto.rs @@ -92,7 +92,8 @@ declare_types! { .downcast::<JsString>() .or_throw(&mut cx)? .value(); - into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&expanded_base58_secret_key)) + into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&expanded_base58_secret_key)) + .map(|keypair| keypair.generate_signator()) } else if arg0.is_a::<JsBuffer>() { let seed_js_buffer = arg0 .downcast::<JsBuffer>() @@ -135,9 +136,9 @@ declare_types! { } } -fn keypair_from_expanded_base58_secret_key( +pub(crate) fn keypair_from_expanded_base58_secret_key( expanded_base58_secret_key: &str, -) -> Result<Ed25519Signator, &'static str> { +) -> Result<Ed25519KeyPair, &'static str> { let bytes = bs58::decode(expanded_base58_secret_key) .into_vec() .map_err(|_| "fail to decode b58")?; @@ -152,7 +153,7 @@ fn keypair_from_expanded_base58_secret_key( //let expected_pubkey = Ed25519PublicKey::try_from(pubkey_bytes.as_ref()); if keypair.public_key().as_ref()[..32] == pubkey_bytes { - Ok(keypair.generate_signator()) + Ok(keypair) } else { Err("corrupted keypair") } @@ -200,7 +201,8 @@ mod tests { let expanded_base58_secret_key = "51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP"; let signator = keypair_from_expanded_base58_secret_key(expanded_base58_secret_key) - .expect("fail to generate keypair"); + .expect("fail to generate keypair") + .generate_signator(); assert_eq!( "HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd", diff --git a/neon/native/src/lib.rs b/neon/native/src/lib.rs index 94aa1964de9374642d5aff07763705765f6ce4d5..b6eee9ce5cfcbe60c96ed339d7ad7260b2101275 100644 --- a/neon/native/src/lib.rs +++ b/neon/native/src/lib.rs @@ -14,10 +14,12 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. mod crypto; +mod pow; mod wot; use neon::{prelude::*, register_module}; +#[inline] fn into_neon_res<'c, C: Context<'c>, T, S: AsRef<str>>( context: &mut C, rust_result: Result<T, S>, @@ -28,6 +30,14 @@ fn into_neon_res<'c, C: Context<'c>, T, S: AsRef<str>>( } } +#[inline] +fn return_json_obj<'c, C: Context<'c>>( + context: &mut C, + json: &serde_json::Value, +) -> NeonResult<Handle<'c, JsValue>> { + Ok(neon_serde::to_value(context, json)?.upcast()) +} + register_module!(mut cx, { cx.export_function("generateRandomSeed", crate::crypto::generate_random_seed)?; cx.export_function( @@ -37,6 +47,7 @@ register_module!(mut cx, { cx.export_function("sha256", crate::crypto::sha256)?; cx.export_function("verify", crate::crypto::verify)?; cx.export_class::<crate::crypto::JsKeyPair>("Ed25519Signator")?; + cx.export_class::<crate::pow::PowCluster>("PowCluster")?; cx.export_class::<crate::wot::JsWoT>("Wot")?; Ok(()) }); diff --git a/neon/native/src/pow.rs b/neon/native/src/pow.rs new file mode 100644 index 0000000000000000000000000000000000000000..be532c27202b38386042a1b6ffe6e4e4062ee2b4 --- /dev/null +++ b/neon/native/src/pow.rs @@ -0,0 +1,247 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::crypto::keypair_from_expanded_base58_secret_key; +use crate::{into_neon_res, return_json_obj}; +use dubp_pow::{PowCluster as RustPowCluster, PowConf, PowQuery, RequestorMsg, ValidProof}; +use duniter_common_tools::Percent; +use dup_crypto::hashs::Hash; +use neon::declare_types; +use neon::prelude::*; +use serde::Deserialize; +use serde_json::json; +use std::num::NonZeroUsize; +use std::sync::{Arc, Mutex}; +use std::{ + sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender}, + time::Duration, +}; + +const POW_WORKERS_NAME: &str = "duniter_pow_w"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct PowQueryFromJs { + block_inner_hash: String, + cpu: u8, + diff: usize, + #[serde(rename = "secretKey")] + expanded_base58_secret_key: String, + marker: usize, + nb_cores: Option<usize>, + prefix: usize, +} + +struct PowTask(Arc<Mutex<Receiver<RequestorMsg>>>); + +impl Task for PowTask { + type Output = Option<RequestorMsg>; + type Error = String; + type JsEvent = JsValue; + + fn perform(&self) -> Result<Self::Output, Self::Error> { + let rx = self + .0 + .lock() + .map_err(|_| "Could not obtain lock on receiver".to_string())?; + + // Attempt to read from the channel. Block for at most 100 ms. + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(event) => Ok(Some(event)), + Err(RecvTimeoutError::Timeout) => Ok(None), + Err(RecvTimeoutError::Disconnected) => Err("Failed to receive event".to_string()), + } + } + + fn complete( + self, + mut cx: TaskContext, + event: Result<Self::Output, Self::Error>, + ) -> JsResult<Self::JsEvent> { + // Receive the event or return early with the error + let event = event.or_else(|err| cx.throw_error(err))?; + + // Timeout occured, return early with `undefined + match event { + Some(RequestorMsg::FoundValidProof(ValidProof { + iters_count, + nonce, + sig, + hash, + worker_nonce_area, + })) => return_json_obj( + &mut cx, + &json!({ + "cancelled": false, + "proof": { + "itersCount": iters_count, + "nonce": nonce, + "sig": sig.to_string(), + "hash": hash.to_string(), + "workerId": worker_nonce_area.0, + }, + }), + ), + Some(RequestorMsg::PowStoppedByCluster) => { + return_json_obj(&mut cx, &json!({ "cancelled": true })) + } + None => Ok(cx.undefined().upcast()), + } + } +} + +pub struct PowClusterWithChannel { + cluster: RustPowCluster, + receiver: Arc<Mutex<Receiver<RequestorMsg>>>, + sender: Sender<RequestorMsg>, +} + +declare_types! { + pub class PowCluster for PowClusterWithChannel { + init(mut cx) { + let arg0 = cx.argument::<JsValue>(0)?; + let max_cores_opt = if arg0.is_a::<JsNumber>() { + Some(arg0 + .downcast::<JsNumber>() + .or_throw(&mut cx)? + .value() + .trunc() as usize) + } else if arg0.is_a::<JsNull>() || arg0.is_a::<JsUndefined>() { + None + } else { + return cx.throw_type_error("arg0 must be a number"); + }; + + let (sender, receiver) = channel(); + + match RustPowCluster::init(max_cores_opt, POW_WORKERS_NAME) { + Ok(pow_cluster) => Ok(PowClusterWithChannel { + cluster: pow_cluster, + receiver: Arc::new(Mutex::new(receiver)), + sender, + }), + Err(e) => cx.throw_error(e.to_string()) + } + } + method startPow(mut cx) { + let arg0 = cx.argument::<JsValue>(0)?; + let pow_query_from_js: PowQueryFromJs = neon_serde::from_value(&mut cx, arg0)?; + + //println!("{:?}", pow_query_from_js); + + let block_inner_hash = into_neon_res( + &mut cx, + Hash::from_hex(&pow_query_from_js.block_inner_hash).map_err(|e| format!("Invalid innerHash: {}", e)) + )?; + let conf = PowConf { + cpu: into_neon_res(&mut cx, Percent::new(pow_query_from_js.cpu).map_err(|e| format!("Invalid cpu: {}", e)))?, + keypair: into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&pow_query_from_js.expanded_base58_secret_key))?, + prefix: into_neon_res(&mut cx, NonZeroUsize::new(pow_query_from_js.prefix).ok_or("prefix must not be zero"))?, + }; + + let mut this = cx.this(); + let res = { + let guard = cx.lock(); + let mut pow_cluster_with_channel = this.borrow_mut(&guard); + + let pow_query = PowQuery { + block_inner_hash, + conf, + diff: pow_query_from_js.diff, + marker: pow_query_from_js.marker, + requestor_sender: pow_cluster_with_channel.sender.clone(), + }; + pow_cluster_with_channel.cluster.start_pow(&pow_query, pow_query_from_js.nb_cores) + }; + + into_neon_res(&mut cx, res.map_err(|e| format!("Fail to start pow: {}", e)))?; + Ok(cx.undefined().upcast()) + } + // This method should be called by JS to receive data. It accepts a + // `function (err, data)` style asynchronous callback. It may be called + // in a loop, but care should be taken to only call it once at a time. + method poll(mut cx) { + // The callback to be executed when data is available + let cb = cx.argument::<JsFunction>(0)?; + let this = cx.this(); + + // Create an asynchronously `PowTask` to receive data + let proof_receiver = cx.borrow(&this, |cluster_with_channel| Arc::clone(&cluster_with_channel.receiver)); + let task = PowTask(proof_receiver); + + // Schedule the task on the `libuv` thread pool + task.schedule(cb); + + // The `poll` method does not return any data. + Ok(JsUndefined::new().upcast()) + } + method getWorkersCount(mut cx) { + let this = cx.this(); + let workers_count = { + let guard = cx.lock(); + let pow_cluster_with_task = this.borrow(&guard); + pow_cluster_with_task.cluster.get_workers_count() + }; + + Ok(cx.number(workers_count as f64).upcast()) + } + method updateConf(mut cx) { + let arg0 = cx.argument::<JsNumber>(0)?.value(); + let arg1 = cx.argument::<JsString>(1)?.value(); + let arg2 = cx.argument::<JsNumber>(2)?.value(); + + let new_pow_conf = PowConf { + cpu: into_neon_res(&mut cx, Percent::new(arg0 as u8).map_err(|e| format!("Invalid cpu: {}", e)))?, + keypair: into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&arg1))?, + prefix: into_neon_res(&mut cx, NonZeroUsize::new(arg2 as usize).ok_or("prefix must not be zero"))?, + }; + + let this = cx.this(); + let res = { + let guard = cx.lock(); + let pow_cluster_with_channel = this.borrow(&guard); + pow_cluster_with_channel.cluster.change_pow_conf(&new_pow_conf) + }; + + into_neon_res(&mut cx, res.map_err(|e| format!("Fail to update pow conf: {}", e)))?; + + Ok(cx.undefined().upcast()) + } + method stopPow(mut cx) { + let notify_requestor = cx.argument::<JsBoolean>(0)?.value(); + let this = cx.this(); + let res = { + let guard = cx.lock(); + let pow_cluster_with_channel = this.borrow(&guard); + + pow_cluster_with_channel.cluster.stop_pow(notify_requestor) + }; + + into_neon_res(&mut cx, res.map_err(|e| e.to_string()))?; + + Ok(cx.undefined().upcast()) + } + method quit(mut cx) { + let mut this = cx.this(); + { + let guard = cx.lock(); + let mut pow_cluster_with_channel = this.borrow_mut(&guard); + pow_cluster_with_channel.cluster.quit_unchecked(); + } + + Ok(cx.undefined().upcast()) + } + } +} diff --git a/package-lock.json b/package-lock.json index 0361be35c884feef7a1bddb07beb737308517dfe..459b7c2c96e75f38ba2d363abd4e6e4cfa461c9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5486,6 +5486,19 @@ "os-tmpdir": "^1.0.0" } }, + "p-event": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", + "requires": { + "p-timeout": "^2.0.1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, "p-limit": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", @@ -5511,6 +5524,14 @@ "aggregate-error": "^3.0.0" } }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "requires": { + "p-finally": "^1.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", diff --git a/package.json b/package.json index 654c92e86ec389e66980f2af21ff2294567434eb..8f10c63ec47f889a72d1e5965a7ba99ed7aec31d 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "node-pre-gyp": "0.14.0", "node-uuid": "1.4.8", "optimist": "0.6.1", + "p-event": "4.1.0", "prettier": "^2.0.4", "q-io": "1.13.6", "querablep": "0.1.0", diff --git a/test/neon/test_pow.ts b/test/neon/test_pow.ts new file mode 100644 index 0000000000000000000000000000000000000000..512ee9be65f1f6cf2738cc25518fa5e2badbbc1d --- /dev/null +++ b/test/neon/test_pow.ts @@ -0,0 +1,96 @@ +"use strict"; + +import * as pEvent from "p-event"; +import { PowCluster } from "../../neon/lib"; +import { assertNotNull, assertNull, assertEqual } from "../integration/tools/test-framework"; +const winston = require('winston') + +describe('PoW cluster tests:', function(){ + + it('Start pow', async() => { + let powCluster = new PowCluster(winston, 1); + assertEqual(powCluster.getWorkersCount(), 1); + powCluster.startPow({ + blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824', + cpu: 100, + diff: 50, + secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP', + marker: 999, + prefix: 4, + }, 1000); + + let proofOrNull = await pEvent(powCluster, 'proof'); + assertNotNull(proofOrNull); + console.log(proofOrNull); + powCluster.quit(); + }); + + it('Start pow with a single worker', async() => { + let powCluster2 = new PowCluster(winston, 2); + assertEqual(powCluster2.getWorkersCount(), 2); + powCluster2.startPow({ + blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824', + cpu: 100, + diff: 16, + secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP', + marker: 0, + nbCores: 1, + prefix: 4, + }, 1000); + let proofOrNull = await pEvent(powCluster2, 'proof'); + assertNotNull(proofOrNull); + assertEqual(proofOrNull.workerId, 0); + + powCluster2.startPow({ + blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824', + cpu: 100, + diff: 16, + secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP', + marker: 1, + nbCores: 1, + prefix: 4, + }, 1000); + let proofOrNull2 = await pEvent(powCluster2, 'proof'); + assertNotNull(proofOrNull2); + assertEqual(proofOrNull2.workerId, 0); + + powCluster2.quit(); + }); + + it('Cancel pow and restart', async() => { + let powCluster3 = new PowCluster(winston, 1); + powCluster3.startPow({ + blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824', + cpu: 1, + diff: 150, + secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP', + marker: 999, + prefix: 4, + }, 1000); + + await new Promise((res) => setTimeout(res, 20)) + + powCluster3.stopPow(true); + + console.log("Wait pow cancellation.."); + assertNull(await pEvent(powCluster3, 'proof')); + + console.log("Pow cancelled. Restart pow.."); + + powCluster3.startPow({ + blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824', + cpu: 1, + diff: 32, + secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP', + marker: 0, + prefix: 8, + }, 1000); + //await new Promise((res) => setTimeout(res, 20)) + console.log("Wait proof.."); + let proofOrNull2 = await pEvent(powCluster3, 'proof'); + assertNotNull(proofOrNull2); + console.log(proofOrNull2); + powCluster3.quit(); + }); +}); +