From 2d907f3322afb90b2c93bedff5c3d5d4553cd08c Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 3 Oct 2016 19:41:00 +0200 Subject: [PATCH 01/30] auto-adjust number of verification threads --- ethcore/src/verification/queue/mod.rs | 164 +++++++++++++++++++++----- 1 file changed, 137 insertions(+), 27 deletions(-) diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index 3f81d53ce..b35b95e2b 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -61,6 +61,24 @@ impl Default for Config { } } +struct VerifierHandle { + deleting: Arc, + thread: JoinHandle<()>, +} + +impl VerifierHandle { + // signal to the verifier thread that it should conclude its + // operations. + fn conclude(&self) { + self.deleting.store(true, AtomicOrdering::Release); + } + + // join the verifier thread. + fn join(self) { + self.thread.join().unwrap(); + } +} + /// An item which is in the process of being verified. pub struct Verifying { hash: H256, @@ -90,7 +108,7 @@ pub struct VerificationQueue { engine: Arc, more_to_verify: Arc, verification: Arc>, - verifiers: Vec>, + verifiers: Mutex>, deleting: Arc, ready_signal: Arc, empty: Arc, @@ -157,7 +175,7 @@ impl VerificationQueue { let empty = Arc::new(SCondvar::new()); let panic_handler = PanicHandler::new_in_arc(); - let mut verifiers: Vec> = Vec::new(); + let mut verifiers: Vec = Vec::new(); let thread_count = max(::num_cpus::get(), 3) - 2; for i in 0..thread_count { let verification = verification.clone(); @@ -165,29 +183,30 @@ impl VerificationQueue { let more_to_verify = more_to_verify.clone(); let ready_signal = ready_signal.clone(); let empty = empty.clone(); - let deleting = deleting.clone(); + let deleting = Arc::new(AtomicBool::new(false)); let panic_handler = panic_handler.clone(); - verifiers.push( - thread::Builder::new() - .name(format!("Verifier #{}", i)) - .spawn(move || { - panic_handler.catch_panic(move || { - VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty) - }).unwrap() - }) - .expect("Error starting block verification thread") - ); + verifiers.push(VerifierHandle { + deleting: deleting.clone(), + thread: thread::Builder::new() + .name(format!("Verifier #{}", i)) + .spawn(move || { + panic_handler.catch_panic(move || { + VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty) + }).unwrap() + }) + .expect("Error starting block verification thread") + }); } VerificationQueue { engine: engine, panic_handler: panic_handler, - ready_signal: ready_signal.clone(), - more_to_verify: more_to_verify.clone(), - verification: verification.clone(), - verifiers: verifiers, - deleting: deleting.clone(), + ready_signal: ready_signal, + more_to_verify: more_to_verify, + verification: verification, + verifiers: Mutex::new(verifiers), + deleting: deleting, processing: RwLock::new(HashSet::new()), - empty: empty.clone(), + empty: empty, max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT), max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT), } @@ -420,14 +439,93 @@ impl VerificationQueue { } } - /// Optimise memory footprint of the heap fields. + /// Optimise memory footprint of the heap fields, and adjust the number of threads + /// to better suit the workload. pub fn collect_garbage(&self) { - { - self.verification.unverified.lock().shrink_to_fit(); + // thresholds for adding and removing verifier threads + // these are unbalanced since having all blocks verified + // is the desirable position. + const ADD_THREAD_THRESHOLD: usize = 10; + const DEL_THREAD_THRESHOLD: usize = 20; + + // TODO: sample over 5 or so ticks. + let (u_len, v_len) = { + let u_len = { + let mut v = self.verification.unverified.lock(); + v.shrink_to_fit(); + v.len() + }; + self.verification.verifying.lock().shrink_to_fit(); - self.verification.verified.lock().shrink_to_fit(); - } + + let v_len = { + let mut v = self.verification.verified.lock(); + v.shrink_to_fit(); + v.len() + }; + + (u_len, v_len) + }; self.processing.write().shrink_to_fit(); + + // more than 10x as many unverified as verified. + if v_len * ADD_THREAD_THRESHOLD < u_len { + self.add_verifier(); + } + + // more than 20x as many verified as unverified. + if u_len * DEL_THREAD_THRESHOLD < v_len { + self.remove_verifier(); + } + } + + // add a verifier thread if possible. + fn add_verifier(&self) { + let mut verifiers = self.verifiers.lock(); + let len = verifiers.len(); + if len == ::num_cpus::get() { + return; + } + + debug!(target: "verification", "Adding verification thread #{}", len); + + let deleting = Arc::new(AtomicBool::new(false)); + let panic_handler = self.panic_handler.clone(); + let verification = self.verification.clone(); + let engine = self.engine.clone(); + let wait = self.more_to_verify.clone(); + let ready = self.ready_signal.clone(); + let empty = self.empty.clone(); + + verifiers.push(VerifierHandle { + deleting: deleting.clone(), + thread: thread::Builder::new() + .name(format!("Verifier #{}", len)) + .spawn(move || { + panic_handler.catch_panic(move || { + VerificationQueue::verify(verification, engine, wait, ready, deleting, empty) + }).unwrap() + }) + .expect("Failed to create verifier thread.") + }); + } + + // remove a verifier thread if possible. + fn remove_verifier(&self) { + let mut verifiers = self.verifiers.lock(); + let len = verifiers.len(); + + if len == 1 { + return; + } + + debug!(target: "verification", "Removing verification thread #{}", len); + + if let Some(handle) = verifiers.pop() { + handle.conclude(); + self.more_to_verify.notify_all(); // to ensure it's joinable immediately. + handle.join(); + } } } @@ -442,10 +540,22 @@ impl Drop for VerificationQueue { trace!(target: "shutdown", "[VerificationQueue] Closing..."); self.clear(); self.deleting.store(true, AtomicOrdering::Release); - self.more_to_verify.notify_all(); - for t in self.verifiers.drain(..) { - t.join().unwrap(); + + let mut verifiers = self.verifiers.lock(); + + // first pass to signal conclusion. must be done before + // notify or deadlock possible. + for handle in verifiers.iter() { + handle.conclude(); } + + self.more_to_verify.notify_all(); + + // second pass to join. + for handle in verifiers.drain(..) { + handle.join(); + } + trace!(target: "shutdown", "[VerificationQueue] Closed."); } } From a7b5dff2520c461ea5873e7b7b250f72ca8344d1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 3 Oct 2016 19:47:07 +0200 Subject: [PATCH 02/30] ethash unsafety cleanup --- ethcore/src/ethereum/ethash.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 982698a50..1d5d2448e 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethash::{quick_get_difficulty, slow_get_seedhash, EthashManager, H256 as EH256}; +use ethash::{quick_get_difficulty, slow_get_seedhash, EthashManager}; use common::*; use block::*; use spec::CommonParams; @@ -182,8 +182,8 @@ impl Engine for Ethash { // Commit state so that we can actually figure out the state root. if let Err(e) = fields.state.commit() { - warn!("Encountered error on state commit: {}", e); - } + warn!("Encountered error on state commit: {}", e); + } } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { @@ -202,10 +202,10 @@ impl Engine for Ethash { return Err(From::from(BlockError::DifficultyOutOfBounds(OutOfBounds { min: Some(min_difficulty), max: None, found: header.difficulty().clone() }))) } - let difficulty = Ethash::boundary_to_difficulty(&Ethash::from_ethash(quick_get_difficulty( - &Ethash::to_ethash(header.bare_hash()), + let difficulty = Ethash::boundary_to_difficulty(&H256(quick_get_difficulty( + &header.bare_hash().0, header.nonce().low_u64(), - &Ethash::to_ethash(header.mix_hash()) + &header.mix_hash().0 ))); if &difficulty < header.difficulty() { return Err(From::from(BlockError::InvalidProofOfWork(OutOfBounds { min: Some(header.difficulty().clone()), max: None, found: difficulty }))); @@ -230,10 +230,10 @@ impl Engine for Ethash { Mismatch { expected: self.seal_fields(), found: header.seal().len() } ))); } - let result = self.pow.compute_light(header.number() as u64, &Ethash::to_ethash(header.bare_hash()), header.nonce().low_u64()); - let mix = Ethash::from_ethash(result.mix_hash); - let difficulty = Ethash::boundary_to_difficulty(&Ethash::from_ethash(result.value)); - trace!(target: "miner", "num: {}, seed: {}, h: {}, non: {}, mix: {}, res: {}" , header.number() as u64, Ethash::from_ethash(slow_get_seedhash(header.number() as u64)), header.bare_hash(), header.nonce().low_u64(), Ethash::from_ethash(result.mix_hash), Ethash::from_ethash(result.value)); + let result = self.pow.compute_light(header.number() as u64, &header.bare_hash().0, header.nonce().low_u64()); + let mix = H256(result.mix_hash); + let difficulty = Ethash::boundary_to_difficulty(&H256(result.value)); + trace!(target: "miner", "num: {}, seed: {}, h: {}, non: {}, mix: {}, res: {}" , header.number() as u64, H256(slow_get_seedhash(header.number() as u64)), header.bare_hash(), header.nonce().low_u64(), H256(result.mix_hash), H256(result.value)); if mix != header.mix_hash() { return Err(From::from(BlockError::MismatchedH256SealElement(Mismatch { expected: mix, found: header.mix_hash() }))); } @@ -275,7 +275,7 @@ impl Engine for Ethash { } } -#[cfg_attr(feature="dev", allow(wrong_self_convention))] // to_ethash should take self +#[cfg_attr(feature="dev", allow(wrong_self_convention))] impl Ethash { fn calculate_difficulty(&self, header: &Header, parent: &Header) -> U256 { const EXP_DIFF_PERIOD: u64 = 100000; @@ -337,14 +337,6 @@ impl Ethash { (((U256::one() << 255) / *difficulty) << 1).into() } } - - fn to_ethash(hash: H256) -> EH256 { - unsafe { mem::transmute(hash) } - } - - fn from_ethash(hash: EH256) -> H256 { - unsafe { mem::transmute(hash) } - } } impl Header { From 5e382602dd7a18cebcb6bf47ce24aade482d40f5 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 3 Oct 2016 20:09:57 +0200 Subject: [PATCH 03/30] fix logging accuracy --- ethcore/src/verification/queue/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index b35b95e2b..3db7135a6 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -515,11 +515,12 @@ impl VerificationQueue { let mut verifiers = self.verifiers.lock(); let len = verifiers.len(); + // never remove the last thread. if len == 1 { return; } - debug!(target: "verification", "Removing verification thread #{}", len); + debug!(target: "verification", "Removing verification thread #{}", len + 1); if let Some(handle) = verifiers.pop() { handle.conclude(); From 2d28c703d6a98586df5744e52a1de77aa59501c7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 3 Oct 2016 20:36:49 +0200 Subject: [PATCH 04/30] reuse add_verifier instrumentation, rolling sample of 5 ticks --- ethcore/src/verification/queue/mod.rs | 53 ++++++++++++++------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index 3db7135a6..ae0555dfa 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -113,6 +113,7 @@ pub struct VerificationQueue { ready_signal: Arc, empty: Arc, processing: RwLock>, + rolling_sample: Mutex>, max_queue_size: usize, max_mem_use: usize, } @@ -175,41 +176,27 @@ impl VerificationQueue { let empty = Arc::new(SCondvar::new()); let panic_handler = PanicHandler::new_in_arc(); - let mut verifiers: Vec = Vec::new(); - let thread_count = max(::num_cpus::get(), 3) - 2; - for i in 0..thread_count { - let verification = verification.clone(); - let engine = engine.clone(); - let more_to_verify = more_to_verify.clone(); - let ready_signal = ready_signal.clone(); - let empty = empty.clone(); - let deleting = Arc::new(AtomicBool::new(false)); - let panic_handler = panic_handler.clone(); - verifiers.push(VerifierHandle { - deleting: deleting.clone(), - thread: thread::Builder::new() - .name(format!("Verifier #{}", i)) - .spawn(move || { - panic_handler.catch_panic(move || { - VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty) - }).unwrap() - }) - .expect("Error starting block verification thread") - }); - } - VerificationQueue { + let queue = VerificationQueue { engine: engine, panic_handler: panic_handler, ready_signal: ready_signal, more_to_verify: more_to_verify, verification: verification, - verifiers: Mutex::new(verifiers), + verifiers: Mutex::new(Vec::with_capacity(::num_cpus::get())), deleting: deleting, processing: RwLock::new(HashSet::new()), empty: empty, + rolling_sample: Mutex::new(VecDeque::new()), max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT), max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT), + }; + + let thread_count = max(::num_cpus::get(), 3) - 2; + for _ in 0..thread_count { + queue.add_verifier(); } + + queue } fn verify(verification: Arc>, engine: Arc, wait: Arc, ready: Arc, deleting: Arc, empty: Arc) { @@ -448,7 +435,10 @@ impl VerificationQueue { const ADD_THREAD_THRESHOLD: usize = 10; const DEL_THREAD_THRESHOLD: usize = 20; - // TODO: sample over 5 or so ticks. + // number of ticks to average queue stats over + // when deciding whether to change the number of verifiers. + const SAMPLE_SIZE: usize = 5; + let (u_len, v_len) = { let u_len = { let mut v = self.verification.unverified.lock(); @@ -468,6 +458,17 @@ impl VerificationQueue { }; self.processing.write().shrink_to_fit(); + let (u_len, v_len) = { + let mut sample = self.rolling_sample.lock(); + sample.push_back((u_len, v_len)); + + if sample.len() > SAMPLE_SIZE { + let _ = sample.pop_front(); + } + + sample.iter().cloned().fold((0, 0), |(u_t, v_t), (u_i, v_i)| (u_t + u_i, v_t + v_i)) + }; + // more than 10x as many unverified as verified. if v_len * ADD_THREAD_THRESHOLD < u_len { self.add_verifier(); @@ -520,7 +521,7 @@ impl VerificationQueue { return; } - debug!(target: "verification", "Removing verification thread #{}", len + 1); + debug!(target: "verification", "Removing verification thread #{}", len - 1); if let Some(handle) = verifiers.pop() { handle.conclude(); From abbf3b3c5840ba18872cd7ac442f7cf39def4a0a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 4 Oct 2016 20:09:54 +0200 Subject: [PATCH 05/30] verification-rate based thread scaling --- ethcore/src/verification/queue/mod.rs | 89 +++++++++++++++------------ 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index ae0555dfa..ac0dbf592 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -18,7 +18,7 @@ //! Sorts them ready for blockchain insertion. use std::thread::{JoinHandle, self}; -use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering}; use std::sync::{Condvar as SCondvar, Mutex as SMutex}; use util::*; use io::*; @@ -113,7 +113,7 @@ pub struct VerificationQueue { ready_signal: Arc, empty: Arc, processing: RwLock>, - rolling_sample: Mutex>, + ticks_since_adjustment: AtomicUsize, max_queue_size: usize, max_mem_use: usize, } @@ -152,6 +152,8 @@ struct Verification { bad: Mutex>, more_to_verify: SMutex<()>, empty: SMutex<()>, + verified_count: AtomicUsize, + drained: AtomicUsize, } impl VerificationQueue { @@ -164,7 +166,8 @@ impl VerificationQueue { bad: Mutex::new(HashSet::new()), more_to_verify: SMutex::new(()), empty: SMutex::new(()), - + verified_count: AtomicUsize::new(0), + drained: AtomicUsize::new(0), }); let more_to_verify = Arc::new(SCondvar::new()); let deleting = Arc::new(AtomicBool::new(false)); @@ -186,7 +189,7 @@ impl VerificationQueue { deleting: deleting, processing: RwLock::new(HashSet::new()), empty: empty, - rolling_sample: Mutex::new(VecDeque::new()), + ticks_since_adjustment: AtomicUsize::new(0), max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT), max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT), }; @@ -248,7 +251,7 @@ impl VerificationQueue { // we're next! let mut verified = verification.verified.lock(); let mut bad = verification.bad.lock(); - VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad); + VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad, &verification.verified_count); ready.set(); } }, @@ -261,7 +264,7 @@ impl VerificationQueue { verifying.retain(|e| e.hash != hash); if verifying.front().map_or(false, |x| x.output.is_some()) { - VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad); + VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad, &verification.verified_count); ready.set(); } } @@ -269,7 +272,13 @@ impl VerificationQueue { } } - fn drain_verifying(verifying: &mut VecDeque>, verified: &mut VecDeque, bad: &mut HashSet) { + fn drain_verifying( + verifying: &mut VecDeque>, + verified: &mut VecDeque, + bad: &mut HashSet, + v_count: &AtomicUsize + ) { + let start_len = verified.len(); while let Some(output) = verifying.front_mut().and_then(|x| x.output.take()) { assert!(verifying.pop_front().is_some()); @@ -279,6 +288,8 @@ impl VerificationQueue { verified.push_back(output); } } + + v_count.fetch_add(verified.len() - start_len, AtomicOrdering::AcqRel); } /// Clear the queue and stop verification activity. @@ -389,6 +400,8 @@ impl VerificationQueue { let count = min(max, verified.len()); let result = verified.drain(..count).collect::>(); + self.verification.drained.fetch_add(count, AtomicOrdering::AcqRel); + self.ready_signal.reset(); if !verified.is_empty() { self.ready_signal.set(); @@ -429,53 +442,49 @@ impl VerificationQueue { /// Optimise memory footprint of the heap fields, and adjust the number of threads /// to better suit the workload. pub fn collect_garbage(&self) { - // thresholds for adding and removing verifier threads - // these are unbalanced since having all blocks verified - // is the desirable position. - const ADD_THREAD_THRESHOLD: usize = 10; - const DEL_THREAD_THRESHOLD: usize = 20; - // number of ticks to average queue stats over // when deciding whether to change the number of verifiers. - const SAMPLE_SIZE: usize = 5; - - let (u_len, v_len) = { - let u_len = { - let mut v = self.verification.unverified.lock(); - v.shrink_to_fit(); - v.len() - }; + const READJUSTMENT_PERIOD: usize = 5; + { + self.verification.unverified.lock().shrink_to_fit(); self.verification.verifying.lock().shrink_to_fit(); + self.verification.verified.lock().shrink_to_fit(); + } - let v_len = { - let mut v = self.verification.verified.lock(); - v.shrink_to_fit(); - v.len() - }; - - (u_len, v_len) - }; self.processing.write().shrink_to_fit(); - let (u_len, v_len) = { - let mut sample = self.rolling_sample.lock(); - sample.push_back((u_len, v_len)); + if self.ticks_since_adjustment.load(AtomicOrdering::SeqCst) == READJUSTMENT_PERIOD { + self.ticks_since_adjustment.store(0, AtomicOrdering::SeqCst); + } else { + self.ticks_since_adjustment.fetch_add(1, AtomicOrdering::SeqCst); + return; + } - if sample.len() > SAMPLE_SIZE { - let _ = sample.pop_front(); - } + let v_count = self.verification.verified_count.load(AtomicOrdering::Acquire); + let drained = self.verification.drained.load(AtomicOrdering::Acquire); - sample.iter().cloned().fold((0, 0), |(u_t, v_t), (u_i, v_i)| (u_t + u_i, v_t + v_i)) + self.verification.verified_count.store(0, AtomicOrdering::Release); + self.verification.drained.store(0, AtomicOrdering::Release); + + // compute the average rate of verification per thread and determine + // how many are necessary to match the rate of draining. + let num_verifiers = self.verifiers.lock().len(); + let v_count_per = v_count as f64 / num_verifiers as f64; + let needed = if v_count < 20 { + 1 + } else { + (drained as f64 / v_count_per as f64).ceil() as usize }; - // more than 10x as many unverified as verified. - if v_len * ADD_THREAD_THRESHOLD < u_len { + trace!(target: "verification", "v_rate_per={}, drained={}, scaling to {} verifiers", + v_count_per, drained, needed); + + for _ in num_verifiers..needed { self.add_verifier(); } - // more than 20x as many verified as unverified. - if u_len * DEL_THREAD_THRESHOLD < v_len { + for _ in needed..num_verifiers { self.remove_verifier(); } } From 8a5576d133a1c770bd4717235f77ba6aae0aafd2 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 5 Oct 2016 12:10:28 +0200 Subject: [PATCH 06/30] balance rates of draining and importing --- ethcore/src/verification/queue/mod.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index ac0dbf592..8c6b1e5ca 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -154,6 +154,7 @@ struct Verification { empty: SMutex<()>, verified_count: AtomicUsize, drained: AtomicUsize, + imported: AtomicUsize, } impl VerificationQueue { @@ -168,6 +169,7 @@ impl VerificationQueue { empty: SMutex::new(()), verified_count: AtomicUsize::new(0), drained: AtomicUsize::new(0), + imported: AtomicUsize::new(0), }); let more_to_verify = Arc::new(SCondvar::new()); let deleting = Arc::new(AtomicBool::new(false)); @@ -345,6 +347,7 @@ impl VerificationQueue { Ok(item) => { self.processing.write().insert(h.clone()); self.verification.unverified.lock().push_back(item); + self.verification.imported.fetch_add(1, AtomicOrdering::AcqRel); self.more_to_verify.notify_all(); Ok(h) }, @@ -463,9 +466,14 @@ impl VerificationQueue { let v_count = self.verification.verified_count.load(AtomicOrdering::Acquire); let drained = self.verification.drained.load(AtomicOrdering::Acquire); + let imported = self.verification.imported.load(AtomicOrdering::Acquire); self.verification.verified_count.store(0, AtomicOrdering::Release); self.verification.drained.store(0, AtomicOrdering::Release); + self.verification.imported.store(0, AtomicOrdering::Release); + + // select which side of the queue is the bottleneck. + let target = min(drained, imported); // compute the average rate of verification per thread and determine // how many are necessary to match the rate of draining. @@ -474,11 +482,11 @@ impl VerificationQueue { let needed = if v_count < 20 { 1 } else { - (drained as f64 / v_count_per as f64).ceil() as usize + (target as f64 / v_count_per as f64).ceil() as usize }; - trace!(target: "verification", "v_rate_per={}, drained={}, scaling to {} verifiers", - v_count_per, drained, needed); + trace!(target: "verification", "v_rate_per={}, target={}, scaling to {} verifiers", + v_count_per, target, needed); for _ in num_verifiers..needed { self.add_verifier(); From 44dcd6bc3b56ae80dc8ef6a9a0d81c4d7bb438dd Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 17 Nov 2016 13:10:33 +0100 Subject: [PATCH 07/30] increase readjustment period --- ethcore/src/verification/queue/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index 2376215f0..23f82b730 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -518,7 +518,7 @@ impl VerificationQueue { pub fn collect_garbage(&self) { // number of ticks to average queue stats over // when deciding whether to change the number of verifiers. - const READJUSTMENT_PERIOD: usize = 5; + const READJUSTMENT_PERIOD: usize = 12; { self.verification.unverified.lock().shrink_to_fit(); From 546cd0065925279c38d82220d1d6a2de0dc21214 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 17 Nov 2016 16:00:23 +0100 Subject: [PATCH 08/30] allocate verifiers up front, hibernate when not needed --- ethcore/src/verification/queue/mod.rs | 166 ++++++++++++++++---------- 1 file changed, 103 insertions(+), 63 deletions(-) diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index 23f82b730..f4a9287d6 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -35,6 +35,9 @@ pub mod kind; const MIN_MEM_LIMIT: usize = 16384; const MIN_QUEUE_LIMIT: usize = 512; +// maximum possible number of verification threads. +const MAX_VERIFIERS: usize = 8; + /// Type alias for block queue convenience. pub type BlockQueue = VerificationQueue; @@ -63,13 +66,26 @@ impl Default for Config { struct VerifierHandle { deleting: Arc, + sleep: Arc<(Mutex, Condvar)>, thread: JoinHandle<()>, } impl VerifierHandle { + // signal to the verifier thread that it should sleep. + fn sleep(&self) { + *self.sleep.0.lock() = true; + } + + // signal to the verifier thread that it should wake up. + fn wake_up(&self) { + *self.sleep.0.lock() = false; + self.sleep.1.notify_all(); + } + // signal to the verifier thread that it should conclude its // operations. fn conclude(&self) { + self.wake_up(); self.deleting.store(true, AtomicOrdering::Release); } @@ -115,7 +131,7 @@ pub struct VerificationQueue { engine: Arc, more_to_verify: Arc, verification: Arc>, - verifiers: Mutex>, + verifiers: Mutex<(Vec, usize)>, deleting: Arc, ready_signal: Arc, empty: Arc, @@ -212,31 +228,83 @@ impl VerificationQueue { let empty = Arc::new(SCondvar::new()); let panic_handler = PanicHandler::new_in_arc(); - let queue = VerificationQueue { + let max_verifiers = min(::num_cpus::get(), MAX_VERIFIERS); + let default_amount = max(::num_cpus::get(), 3) - 2; + let mut verifiers = Vec::with_capacity(max_verifiers); + + debug!(target: "verification", "Allocating {} verifiers, {} initially active", max_verifiers, default_amount); + + for i in 0..max_verifiers { + debug!(target: "verification", "Adding verification thread #{}", i); + + let deleting = deleting.clone(); + let panic_handler = panic_handler.clone(); + let verification = verification.clone(); + let engine = engine.clone(); + let wait = more_to_verify.clone(); + let ready = ready_signal.clone(); + let empty = empty.clone(); + + // enable only the first few verifiers. + let sleep = if i < default_amount { + Arc::new((Mutex::new(false), Condvar::new())) + } else { + Arc::new((Mutex::new(true), Condvar::new())) + }; + + verifiers.push(VerifierHandle { + deleting: deleting.clone(), + sleep: sleep.clone(), + thread: thread::Builder::new() + .name(format!("Verifier #{}", i)) + .spawn(move || { + panic_handler.catch_panic(move || { + VerificationQueue::verify(verification, engine, wait, ready, deleting, empty, sleep) + }).unwrap() + }) + .expect("Failed to create verifier thread.") + }); + } + + VerificationQueue { engine: engine, panic_handler: panic_handler, ready_signal: ready_signal, more_to_verify: more_to_verify, verification: verification, - verifiers: Mutex::new(Vec::with_capacity(::num_cpus::get())), + verifiers: Mutex::new((verifiers, default_amount)), deleting: deleting, processing: RwLock::new(HashSet::new()), empty: empty, ticks_since_adjustment: AtomicUsize::new(0), max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT), max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT), - }; - - let thread_count = max(::num_cpus::get(), 3) - 2; - for _ in 0..thread_count { - queue.add_verifier(); } - - queue } - fn verify(verification: Arc>, engine: Arc, wait: Arc, ready: Arc, deleting: Arc, empty: Arc) { + fn verify( + verification: Arc>, + engine: Arc, + wait: Arc, + ready: Arc, + deleting: Arc, + empty: Arc, + sleep: Arc<(Mutex, Condvar)>, + ) { while !deleting.load(AtomicOrdering::Acquire) { + { + let mut should_sleep = sleep.0.lock(); + while *should_sleep { + trace!(target: "verification", "Verifier sleeping"); + sleep.1.wait(&mut should_sleep); + trace!(target: "verification", "Verifier waking up"); + + if deleting.load(AtomicOrdering::Acquire) { + return; + } + } + } + { let mut more_to_verify = verification.more_to_verify.lock().unwrap(); @@ -548,7 +616,7 @@ impl VerificationQueue { // compute the average rate of verification per thread and determine // how many are necessary to match the rate of draining. - let num_verifiers = self.verifiers.lock().len(); + let num_verifiers = self.verifiers.lock().1; let v_count_per = v_count as f64 / num_verifiers as f64; let needed = if v_count < 20 { 1 @@ -559,63 +627,34 @@ impl VerificationQueue { trace!(target: "verification", "v_rate_per={}, target={}, scaling to {} verifiers", v_count_per, target, needed); - for _ in num_verifiers..needed { - self.add_verifier(); - } - - for _ in needed..num_verifiers { - self.remove_verifier(); - } + self.scale_verifiers(needed); } - // add a verifier thread if possible. - fn add_verifier(&self) { + // wake up or sleep verifiers to get as close to the target as + // possible, never going over the amount of initially allocated threads + // or below 1. + fn scale_verifiers(&self, target: usize) { let mut verifiers = self.verifiers.lock(); - let len = verifiers.len(); - if len == ::num_cpus::get() { - return; + let &mut (ref mut verifiers, ref mut verifier_count) = &mut *verifiers; + + let target = min(verifiers.capacity(), target); + let target = max(1, target); + + debug!(target: "verification", "Scaling from {} to {} verifiers", verifier_count, target); + + // scaling up + for i in *verifier_count..target { + debug!(target: "verification", "Waking up verifier {}", i); + verifiers[i].wake_up(); } - debug!(target: "verification", "Adding verification thread #{}", len); - - let deleting = Arc::new(AtomicBool::new(false)); - let panic_handler = self.panic_handler.clone(); - let verification = self.verification.clone(); - let engine = self.engine.clone(); - let wait = self.more_to_verify.clone(); - let ready = self.ready_signal.clone(); - let empty = self.empty.clone(); - - verifiers.push(VerifierHandle { - deleting: deleting.clone(), - thread: thread::Builder::new() - .name(format!("Verifier #{}", len)) - .spawn(move || { - panic_handler.catch_panic(move || { - VerificationQueue::verify(verification, engine, wait, ready, deleting, empty) - }).unwrap() - }) - .expect("Failed to create verifier thread.") - }); - } - - // remove a verifier thread if possible. - fn remove_verifier(&self) { - let mut verifiers = self.verifiers.lock(); - let len = verifiers.len(); - - // never remove the last thread. - if len == 1 { - return; + // scaling down. + for i in target..*verifier_count { + debug!(target: "verification", "Putting verifier {} to sleep", i); + verifiers[i].sleep(); } - debug!(target: "verification", "Removing verification thread #{}", len - 1); - - if let Some(handle) = verifiers.pop() { - handle.conclude(); - self.more_to_verify.notify_all(); // to ensure it's joinable immediately. - handle.join(); - } + *verifier_count = target; } } @@ -631,7 +670,8 @@ impl Drop for VerificationQueue { self.clear(); self.deleting.store(true, AtomicOrdering::Release); - let mut verifiers = self.verifiers.lock(); + let mut verifiers = self.verifiers.get_mut(); + let mut verifiers = &mut verifiers.0; // first pass to signal conclusion. must be done before // notify or deadlock possible. From 8c0e511ebe0a5541fa804ad2a8393f12afec1d6e Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 17 Nov 2016 18:10:09 +0100 Subject: [PATCH 09/30] rewrite scaling logic --- ethcore/src/verification/queue/mod.rs | 72 ++++++++++++--------------- 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index f4a9287d6..b4f2ab5a2 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -191,9 +191,6 @@ struct Verification { bad: Mutex>, more_to_verify: SMutex<()>, empty: SMutex<()>, - verified_count: AtomicUsize, - drained: AtomicUsize, - imported: AtomicUsize, sizes: Sizes, check_seal: bool, } @@ -208,9 +205,6 @@ impl VerificationQueue { bad: Mutex::new(HashSet::new()), more_to_verify: SMutex::new(()), empty: SMutex::new(()), - verified_count: AtomicUsize::new(0), - drained: AtomicUsize::new(0), - imported: AtomicUsize::new(0), sizes: Sizes { unverified: AtomicUsize::new(0), verifying: AtomicUsize::new(0), @@ -355,7 +349,7 @@ impl VerificationQueue { // we're next! let mut verified = verification.verified.lock(); let mut bad = verification.bad.lock(); - VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad, &verification.verified_count, &verification.sizes); + VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad, &verification.sizes); true } else { false @@ -370,7 +364,7 @@ impl VerificationQueue { verifying.retain(|e| e.hash != hash); if verifying.front().map_or(false, |x| x.output.is_some()) { - VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad, &verification.verified_count, &verification.sizes); + VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad, &verification.sizes); true } else { false @@ -388,10 +382,8 @@ impl VerificationQueue { verifying: &mut VecDeque>, verified: &mut VecDeque, bad: &mut HashSet, - v_count: &AtomicUsize, sizes: &Sizes, ) { - let start_len = verified.len(); let mut removed_size = 0; let mut inserted_size = 0; @@ -408,7 +400,6 @@ impl VerificationQueue { } } - v_count.fetch_add(verified.len() - start_len, AtomicOrdering::AcqRel); sizes.verifying.fetch_sub(removed_size, AtomicOrdering::SeqCst); sizes.verified.fetch_add(inserted_size, AtomicOrdering::SeqCst); } @@ -474,7 +465,6 @@ impl VerificationQueue { self.processing.write().insert(h.clone()); self.verification.unverified.lock().push_back(item); - self.verification.imported.fetch_add(1, AtomicOrdering::AcqRel); self.more_to_verify.notify_all(); Ok(h) }, @@ -536,8 +526,6 @@ impl VerificationQueue { let count = min(max, verified.len()); let result = verified.drain(..count).collect::>(); - self.verification.drained.fetch_add(result.len(), AtomicOrdering::AcqRel); - let drained_size = result.iter().map(HeapSizeOf::heap_size_of_children).fold(0, |a, c| a + c); self.verification.sizes.verified.fetch_sub(drained_size, AtomicOrdering::SeqCst); @@ -588,11 +576,22 @@ impl VerificationQueue { // when deciding whether to change the number of verifiers. const READJUSTMENT_PERIOD: usize = 12; - { - self.verification.unverified.lock().shrink_to_fit(); + let (u_len, v_len) = { + let u_len = { + let mut q = self.verification.unverified.lock(); + q.shrink_to_fit(); + q.len() + }; self.verification.verifying.lock().shrink_to_fit(); - self.verification.verified.lock().shrink_to_fit(); - } + + let v_len = { + let mut q = self.verification.verified.lock(); + q.shrink_to_fit(); + q.len() + }; + + (u_len as isize, v_len as isize) + }; self.processing.write().shrink_to_fit(); @@ -603,31 +602,22 @@ impl VerificationQueue { return; } - let v_count = self.verification.verified_count.load(AtomicOrdering::Acquire); - let drained = self.verification.drained.load(AtomicOrdering::Acquire); - let imported = self.verification.imported.load(AtomicOrdering::Acquire); + let current = self.verifiers.lock().1; - self.verification.verified_count.store(0, AtomicOrdering::Release); - self.verification.drained.store(0, AtomicOrdering::Release); - self.verification.imported.store(0, AtomicOrdering::Release); + let diff = (v_len - u_len).abs(); + let total = v_len + u_len; - // select which side of the queue is the bottleneck. - let target = min(drained, imported); - - // compute the average rate of verification per thread and determine - // how many are necessary to match the rate of draining. - let num_verifiers = self.verifiers.lock().1; - let v_count_per = v_count as f64 / num_verifiers as f64; - let needed = if v_count < 20 { - 1 - } else { - (target as f64 / v_count_per as f64).ceil() as usize - }; - - trace!(target: "verification", "v_rate_per={}, target={}, scaling to {} verifiers", - v_count_per, target, needed); - - self.scale_verifiers(needed); + self.scale_verifiers( + if u_len < 20 { + 1 + } else if diff <= total / 10 { + current + } else if v_len > u_len { + current - 1 + } else { + current + 1 + } + ); } // wake up or sleep verifiers to get as close to the target as From e69b7f66a3a530812a01e6ba9b05190c4066d4e6 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 18 Nov 2016 14:18:50 +0100 Subject: [PATCH 10/30] Gas calculation & limit errors --- .../DetailsStep/detailsStep.js | 16 +++++ .../ExecuteContract/executeContract.css | 8 +++ .../modals/ExecuteContract/executeContract.js | 72 +++++++++++++++++-- js/src/modals/Transfer/errors.js | 4 +- js/src/modals/Transfer/transfer.css | 9 +++ js/src/modals/Transfer/transfer.js | 57 +++++++++++++-- js/src/util/constants.js | 26 +++++++ 7 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 js/src/util/constants.js diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index b549abb18..aad54e360 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -36,6 +36,7 @@ export default class DetailsStep extends Component { onFuncChange: PropTypes.func, values: PropTypes.array.isRequired, valuesError: PropTypes.array.isRequired, + warning: PropTypes.string, onValueChange: PropTypes.func.isRequired } @@ -44,6 +45,7 @@ export default class DetailsStep extends Component { return (
+ { this.renderWarning() } + { warning } + + ); + } + onFuncChange = (event, index, signature) => { const { contract, onFuncChange } = this.props; diff --git a/js/src/modals/ExecuteContract/executeContract.css b/js/src/modals/ExecuteContract/executeContract.css index cdebfef32..55135a92e 100644 --- a/js/src/modals/ExecuteContract/executeContract.css +++ b/js/src/modals/ExecuteContract/executeContract.css @@ -33,3 +33,11 @@ .txhash { word-break: break-all; } + +.warning { + border-radius: 0.5em; + background: #f80; + color: white; + padding: 0.75em; + text-align: center; +} diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index a57c18a1d..8022690f5 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -15,17 +15,21 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; +import { MAX_GAS_ESTIMATION } from '../../util/constants'; import { validateAddress, validateUint } from '../../util/validation'; import DetailsStep from './DetailsStep'; +import ERRORS from '../Transfer/errors'; import { ERROR_CODES } from '../../api/transport/error'; -export default class ExecuteContract extends Component { +class ExecuteContract extends Component { static contextTypes = { api: PropTypes.object.isRequired, store: PropTypes.object.isRequired @@ -36,6 +40,7 @@ export default class ExecuteContract extends Component { fromAddress: PropTypes.string, accounts: PropTypes.object, contract: PropTypes.object, + gasLimit: PropTypes.object.isRequired, onClose: PropTypes.func.isRequired, onFromAddressChange: PropTypes.func.isRequired } @@ -46,6 +51,8 @@ export default class ExecuteContract extends Component { fromAddressError: null, func: null, funcError: null, + gas: null, + gasLimitError: null, values: [], valuesError: [], step: 0, @@ -119,7 +126,7 @@ export default class ExecuteContract extends Component { renderStep () { const { onFromAddressChange } = this.props; - const { step, busyState, txhash, rejected } = this.state; + const { step, busyState, gasLimitError, txhash, rejected } = this.state; if (rejected) { return ( @@ -135,6 +142,7 @@ export default class ExecuteContract extends Component { { + this.estimateGas(); this.setState({ amount }); } @@ -179,6 +188,7 @@ export default class ExecuteContract extends Component { } }); + this.estimateGas(); this.setState({ func, values @@ -208,17 +218,54 @@ export default class ExecuteContract extends Component { values[index] = value; valuesError[index] = valueError; + if (!valueError) { + this.estimateGas(); + } + this.setState({ values: [].concat(values), valuesError: [].concat(valuesError) }); } + estimateGas = () => { + const { api } = this.context; + const { fromAddress, gasLimit } = this.props; + const { amount, func, values } = this.state; + const options = { + gas: MAX_GAS_ESTIMATION, + from: fromAddress, + value: api.util.toWei(amount || 0) + }; + + func + .estimateGas(options, values) + .then((gasEst) => { + const gas = gasEst.mul(1.2); + let gasLimitError = null; + + if (gas.gte(MAX_GAS_ESTIMATION)) { + gasLimitError = ERRORS.gasException; + } else if (gas.gt(gasLimit)) { + gasLimitError = ERRORS.gasBlockLimit; + } + + this.setState({ + gas, + gasLimitError + }); + }) + .catch((error) => { + console.warn('estimateGas', error); + }); + } + postTransaction = () => { const { api, store } = this.context; const { fromAddress } = this.props; const { amount, func, values } = this.state; const options = { + gas: MAX_GAS_ESTIMATION, from: fromAddress, value: api.util.toWei(amount || 0) }; @@ -237,13 +284,13 @@ export default class ExecuteContract extends Component { return api .pollMethod('parity_checkRequest', requestId) - .catch((e) => { - if (e.code === ERROR_CODES.REQUEST_REJECTED) { + .catch((error) => { + if (error.code === ERROR_CODES.REQUEST_REJECTED) { this.setState({ rejected: true }); return false; } - throw e; + throw error; }); }) .then((txhash) => { @@ -255,3 +302,18 @@ export default class ExecuteContract extends Component { }); } } + +function mapStateToProps (state) { + const { gasLimit } = state.status; + + return { gasLimit }; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({}, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(ExecuteContract); diff --git a/js/src/modals/Transfer/errors.js b/js/src/modals/Transfer/errors.js index a6456c785..c35d82636 100644 --- a/js/src/modals/Transfer/errors.js +++ b/js/src/modals/Transfer/errors.js @@ -19,7 +19,9 @@ const ERRORS = { invalidAddress: 'the supplied address is an invalid network address', invalidAmount: 'the supplied amount should be a valid positive number', invalidDecimals: 'the supplied amount exceeds the allowed decimals', - largeAmount: 'the transaction total is higher than the available balance' + largeAmount: 'the transaction total is higher than the available balance', + gasException: 'the transaction indicates a thrown exception', + gasBlockLimit: 'the transaction will exceed the block gas limit' }; export default ERRORS; diff --git a/js/src/modals/Transfer/transfer.css b/js/src/modals/Transfer/transfer.css index 89b58666e..76f83c62b 100644 --- a/js/src/modals/Transfer/transfer.css +++ b/js/src/modals/Transfer/transfer.css @@ -14,6 +14,7 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + .info { line-height: 1.618em; width: 100%; @@ -151,3 +152,11 @@ .gasPriceDesc { font-size: 0.9em; } + +.warning { + border-radius: 0.5em; + background: #f80; + color: white; + padding: 0.75em; + text-align: center; +} diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index e41ea08f9..196307923 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -16,12 +16,15 @@ import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; +import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '../../util/constants'; import Details from './Details'; import Extras from './Extras'; @@ -30,8 +33,6 @@ import styles from './transfer.css'; import { ERROR_CODES } from '../../api/transport/error'; -const DEFAULT_GAS = '21000'; -const DEFAULT_GASPRICE = '20000000000'; const TITLES = { transfer: 'transfer details', sending: 'sending', @@ -42,7 +43,7 @@ const TITLES = { const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete]; const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete]; -export default class Transfer extends Component { +class Transfer extends Component { static contextTypes = { api: PropTypes.object.isRequired, store: PropTypes.object.isRequired @@ -52,6 +53,7 @@ export default class Transfer extends Component { account: PropTypes.object, balance: PropTypes.object, balances: PropTypes.object, + gasLimit: PropTypes.object.isRequired, images: PropTypes.object.isRequired, onClose: PropTypes.func } @@ -64,6 +66,7 @@ export default class Transfer extends Component { gas: DEFAULT_GAS, gasEst: '0', gasError: null, + gasLimitError: null, gasPrice: DEFAULT_GASPRICE, gasPriceHistogram: {}, gasPriceError: null, @@ -103,6 +106,7 @@ export default class Transfer extends Component { visible scroll > + { this.renderWarning() } { this.renderPage() } ); @@ -264,6 +268,20 @@ export default class Transfer extends Component { } } + renderWarning () { + const { gasLimitError } = this.state; + + if (!gasLimitError) { + return null; + } + + return ( +
+ { gasLimitError } +
+ ); + } + isValid () { const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError; const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError; @@ -519,6 +537,7 @@ export default class Transfer extends Component { return token.contract.instance.transfer .estimateGas({ + gas: MAX_GAS_ESTIMATION, from: account.address, to: token.address }, [ @@ -532,6 +551,7 @@ export default class Transfer extends Component { const { account } = this.props; const { data, recipient, value } = this.state; const options = { + gas: MAX_GAS_ESTIMATION, from: account.address, to: recipient, value: api.util.toWei(value || 0) @@ -552,19 +572,29 @@ export default class Transfer extends Component { return; } + const { gasLimit } = this.props; + (this.state.isEth ? this._estimateGasEth() : this._estimateGasToken() - ).then((_value) => { - let gas = _value; + ).then((gasEst) => { + let gas = gasEst; + let gasLimitError = null; if (gas.gt(DEFAULT_GAS)) { gas = gas.mul(1.2); } + if (gas.gte(MAX_GAS_ESTIMATION)) { + gasLimitError = ERRORS.gasException; + } else if (gas.gt(gasLimit)) { + gasLimitError = ERRORS.gasBlockLimit; + } + this.setState({ gas: gas.toFixed(0), - gasEst: _value.toFormat() + gasEst: gasEst.toFormat(), + gasLimitError }, this.recalculate); }) .catch((error) => { @@ -649,3 +679,18 @@ export default class Transfer extends Component { store.dispatch({ type: 'newError', error }); } } + +function mapStateToProps (state) { + const { gasLimit } = state.status; + + return { gasLimit }; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({}, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Transfer); diff --git a/js/src/util/constants.js b/js/src/util/constants.js new file mode 100644 index 000000000..87ca59668 --- /dev/null +++ b/js/src/util/constants.js @@ -0,0 +1,26 @@ +// Copyright 2015, 2016 Ethcore (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 . + +const DEFAULT_GAS = '21000'; +const DEFAULT_GASPRICE = '20000000000'; + +const MAX_GAS_ESTIMATION = '50000000'; + +export default { + DEFAULT_GAS, + DEFAULT_GASPRICE, + MAX_GAS_ESTIMATION +}; From b433e7e9a0207b6d3c6b7403ea9c54dee6787fc9 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 18 Nov 2016 15:05:02 +0100 Subject: [PATCH 11/30] Updated, contract execution in-place --- .../ExecuteContract/executeContract.css | 1 + .../modals/ExecuteContract/executeContract.js | 31 ++++++++++++------- js/src/modals/Transfer/errors.js | 4 +-- js/src/modals/Transfer/transfer.css | 1 + js/src/modals/Transfer/transfer.js | 2 +- js/src/util/constants.js | 2 +- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/js/src/modals/ExecuteContract/executeContract.css b/js/src/modals/ExecuteContract/executeContract.css index 55135a92e..a83b373ee 100644 --- a/js/src/modals/ExecuteContract/executeContract.css +++ b/js/src/modals/ExecuteContract/executeContract.css @@ -38,6 +38,7 @@ border-radius: 0.5em; background: #f80; color: white; + font-size: 0.75em; padding: 0.75em; text-align: center; } diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 8022690f5..1354e8543 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -71,6 +71,12 @@ class ExecuteContract extends Component { this.onFuncChange(null, functions[0]); } + componentWillReceiveProps (newProps) { + if (newProps.fromAddress !== this.props.fromAddress) { + this.estimateGas(newProps.fromAddress); + } + } + render () { const { sending } = this.state; @@ -164,8 +170,7 @@ class ExecuteContract extends Component { } onAmountChange = (amount) => { - this.estimateGas(); - this.setState({ amount }); + this.setState({ amount }, this.estimateGas); } onFuncChange = (event, func) => { @@ -188,11 +193,10 @@ class ExecuteContract extends Component { } }); - this.estimateGas(); this.setState({ func, values - }); + }, this.estimateGas); } onValueChange = (event, index, _value) => { @@ -218,29 +222,34 @@ class ExecuteContract extends Component { values[index] = value; valuesError[index] = valueError; - if (!valueError) { - this.estimateGas(); - } - this.setState({ values: [].concat(values), valuesError: [].concat(valuesError) + }, () => { + if (!valueError) { + this.estimateGas(); + } }); } - estimateGas = () => { + estimateGas = (_fromAddress) => { const { api } = this.context; const { fromAddress, gasLimit } = this.props; const { amount, func, values } = this.state; const options = { gas: MAX_GAS_ESTIMATION, - from: fromAddress, + from: _fromAddress || fromAddress, value: api.util.toWei(amount || 0) }; + if (!func) { + return; + } + func .estimateGas(options, values) .then((gasEst) => { + console.log(gasEst.toFormat(0)); const gas = gasEst.mul(1.2); let gasLimitError = null; @@ -304,7 +313,7 @@ class ExecuteContract extends Component { } function mapStateToProps (state) { - const { gasLimit } = state.status; + const { gasLimit } = state.nodeStatus; return { gasLimit }; } diff --git a/js/src/modals/Transfer/errors.js b/js/src/modals/Transfer/errors.js index c35d82636..3a6bd63ae 100644 --- a/js/src/modals/Transfer/errors.js +++ b/js/src/modals/Transfer/errors.js @@ -20,8 +20,8 @@ const ERRORS = { invalidAmount: 'the supplied amount should be a valid positive number', invalidDecimals: 'the supplied amount exceeds the allowed decimals', largeAmount: 'the transaction total is higher than the available balance', - gasException: 'the transaction indicates a thrown exception', - gasBlockLimit: 'the transaction will exceed the block gas limit' + gasException: 'the transaction will throw an exception with the current values', + gasBlockLimit: 'the transaction execution will exceed the block gas limit' }; export default ERRORS; diff --git a/js/src/modals/Transfer/transfer.css b/js/src/modals/Transfer/transfer.css index 76f83c62b..b7f478b4f 100644 --- a/js/src/modals/Transfer/transfer.css +++ b/js/src/modals/Transfer/transfer.css @@ -157,6 +157,7 @@ border-radius: 0.5em; background: #f80; color: white; + font-size: 0.75em; padding: 0.75em; text-align: center; } diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index 196307923..e906e45d7 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -681,7 +681,7 @@ class Transfer extends Component { } function mapStateToProps (state) { - const { gasLimit } = state.status; + const { gasLimit } = state.nodeStatus; return { gasLimit }; } diff --git a/js/src/util/constants.js b/js/src/util/constants.js index 87ca59668..d0664d25e 100644 --- a/js/src/util/constants.js +++ b/js/src/util/constants.js @@ -19,7 +19,7 @@ const DEFAULT_GASPRICE = '20000000000'; const MAX_GAS_ESTIMATION = '50000000'; -export default { +export { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION From 8d339ad379239f1968693952f39c3340dbbd8bf2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 18 Nov 2016 15:13:37 +0100 Subject: [PATCH 12/30] Adjust styling --- js/src/modals/Transfer/transfer.css | 1 + 1 file changed, 1 insertion(+) diff --git a/js/src/modals/Transfer/transfer.css b/js/src/modals/Transfer/transfer.css index b7f478b4f..3bd17ba96 100644 --- a/js/src/modals/Transfer/transfer.css +++ b/js/src/modals/Transfer/transfer.css @@ -158,6 +158,7 @@ background: #f80; color: white; font-size: 0.75em; + margin-bottom: 1em; padding: 0.75em; text-align: center; } From f827ade6161caca36ec7cecffb7e1eba2b0da762 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 18 Nov 2016 16:32:57 +0100 Subject: [PATCH 13/30] debug log removal --- js/src/modals/ExecuteContract/executeContract.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 1354e8543..97d44a029 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -249,7 +249,6 @@ class ExecuteContract extends Component { func .estimateGas(options, values) .then((gasEst) => { - console.log(gasEst.toFormat(0)); const gas = gasEst.mul(1.2); let gasLimitError = null; From ca5fd0b23d5616ac523e7825bfe526db47fd4787 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Mon, 21 Nov 2016 14:14:25 +0100 Subject: [PATCH 14/30] React Perf in dev mode // Smarter background component #3240 --- js/package.json | 1 + js/src/index.js | 6 +++ .../ui/ParityBackground/parityBackground.js | 43 ++++++++++--------- .../views/Application/Container/container.js | 7 ++- js/src/views/ParityBar/parityBar.js | 11 ++++- .../views/Settings/Background/background.js | 5 ++- 6 files changed, 49 insertions(+), 24 deletions(-) diff --git a/js/package.json b/js/package.json index 7f4157cad..f8705f1a1 100644 --- a/js/package.json +++ b/js/package.json @@ -102,6 +102,7 @@ "postcss-nested": "^1.0.0", "postcss-simple-vars": "^3.0.0", "raw-loader": "^0.5.1", + "react-addons-perf": "~15.3.2", "react-addons-test-utils": "~15.3.2", "react-copy-to-clipboard": "^4.2.3", "react-dom": "~15.3.2", diff --git a/js/src/index.js b/js/src/index.js index fda785842..0e0433c1e 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -46,6 +46,12 @@ import './index.html'; injectTapEventPlugin(); +if (process.env.NODE_ENV === 'development') { + // Expose the React Performance Tools on the`window` object + const Perf = require('react-addons-perf'); + window.Perf = Perf; +} + const AUTH_HASH = '#/auth?'; const parityUrl = process.env.PARITY_URL || ( diff --git a/js/src/ui/ParityBackground/parityBackground.js b/js/src/ui/ParityBackground/parityBackground.js index 0916d3a85..5198195c0 100644 --- a/js/src/ui/ParityBackground/parityBackground.js +++ b/js/src/ui/ParityBackground/parityBackground.js @@ -16,26 +16,17 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; class ParityBackground extends Component { - static contextTypes = { - muiTheme: PropTypes.object.isRequired - } - static propTypes = { + style: PropTypes.object.isRequired, children: PropTypes.node, className: PropTypes.string, - gradient: PropTypes.string, - seed: PropTypes.any, - settings: PropTypes.object.isRequired, onClick: PropTypes.func - } + }; render () { - const { muiTheme } = this.context; - const { children, className, gradient, seed, settings, onClick } = this.props; - const style = muiTheme.parity.getBackgroundStyle(gradient, seed || settings.backgroundSeed); + const { children, className, style, onClick } = this.props; return (
{ + const { backgroundSeed } = state.settings; + const { seed } = props; + + const newSeed = seed || backgroundSeed; + + if (newSeed === _seed) { + return _props; + } + + _seed = newSeed; + _props = { style: muiTheme.parity.getBackgroundStyle(gradient, newSeed) }; + + return _props; + }; } export default connect( - mapStateToProps, - mapDispatchToProps + mapStateToProps )(ParityBackground); diff --git a/js/src/views/Application/Container/container.js b/js/src/views/Application/Container/container.js index a1b9124c7..d3908f570 100644 --- a/js/src/views/Application/Container/container.js +++ b/js/src/views/Application/Container/container.js @@ -22,6 +22,10 @@ import { Errors, ParityBackground, Tooltips } from '../../../ui'; import styles from '../application.css'; export default class Container extends Component { + static contextTypes = { + muiTheme: PropTypes.object.isRequired + }; + static propTypes = { children: PropTypes.node.isRequired, showFirstRun: PropTypes.bool, @@ -30,9 +34,10 @@ export default class Container extends Component { render () { const { children, showFirstRun, onCloseFirstRun } = this.props; + const { muiTheme } = this.context; return ( - + diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js index 0f3380ca0..40fe659ad 100644 --- a/js/src/views/ParityBar/parityBar.js +++ b/js/src/views/ParityBar/parityBar.js @@ -28,6 +28,10 @@ import imagesEthcoreBlock from '../../../assets/images/parity-logo-white-no-text import styles from './parityBar.css'; class ParityBar extends Component { + static contextTypes = { + muiTheme: PropTypes.object.isRequired + }; + static propTypes = { pending: PropTypes.array, dapp: PropTypes.bool @@ -62,6 +66,7 @@ class ParityBar extends Component { renderBar () { const { dapp } = this.props; + const { muiTheme } = this.context; if (!dapp) { return null; @@ -75,7 +80,7 @@ class ParityBar extends Component { return (
- +
); } From bd6f343cbe1f70c4ef5084af4c8d4d8e23297808 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Tue, 22 Nov 2016 11:54:20 +0100 Subject: [PATCH 20/30] DappRegistry (#3405) * Initial version, just loading * Warning dialog * Retrive & show applications * Display completed * Edit/New mode start * Display distry state, resolve hashes to urls * Move buttons to top-level * Rework display * Update error handling & dirty checks * Formatting * Split sections into components * Styling updates * Slight styling adjustments * Start delete modal * Add modals for register & update * Flesh-out register modal * Register error handling * Register registers * List refresh on add & remove * Update operational * Simplify, remove bg image * Really remove bg * fix case * Collapse text-only components --- js/src/dapps/dappreg.html | 17 + js/src/dapps/dappreg.js | 35 ++ .../dapps/dappreg/Application/application.css | 58 +++ .../dapps/dappreg/Application/application.js | 64 +++ js/src/dapps/dappreg/Application/index.js | 17 + js/src/dapps/dappreg/Button/button.css | 38 ++ js/src/dapps/dappreg/Button/button.js | 52 ++ js/src/dapps/dappreg/Button/index.js | 17 + js/src/dapps/dappreg/ButtonBar/buttonBar.css | 21 + js/src/dapps/dappreg/ButtonBar/buttonBar.js | 101 ++++ js/src/dapps/dappreg/ButtonBar/index.js | 17 + js/src/dapps/dappreg/Dapp/dapp.css | 19 + js/src/dapps/dappreg/Dapp/dapp.js | 162 ++++++ js/src/dapps/dappreg/Dapp/index.js | 17 + js/src/dapps/dappreg/Input/index.js | 17 + js/src/dapps/dappreg/Input/input.css | 92 ++++ js/src/dapps/dappreg/Input/input.js | 47 ++ js/src/dapps/dappreg/Modal/index.js | 17 + js/src/dapps/dappreg/Modal/modal.css | 116 +++++ js/src/dapps/dappreg/Modal/modal.js | 66 +++ js/src/dapps/dappreg/ModalDelete/index.js | 17 + .../dapps/dappreg/ModalDelete/modalDelete.js | 159 ++++++ js/src/dapps/dappreg/ModalRegister/index.js | 17 + .../dappreg/ModalRegister/modalRegister.js | 159 ++++++ js/src/dapps/dappreg/ModalUpdate/index.js | 17 + .../dapps/dappreg/ModalUpdate/modalUpdate.js | 169 ++++++ js/src/dapps/dappreg/SelectAccount/index.js | 17 + .../dappreg/SelectAccount/selectAccount.js | 49 ++ js/src/dapps/dappreg/SelectDapp/index.js | 17 + js/src/dapps/dappreg/SelectDapp/selectDapp.js | 76 +++ js/src/dapps/dappreg/Warning/index.js | 17 + js/src/dapps/dappreg/Warning/warning.css | 36 ++ js/src/dapps/dappreg/Warning/warning.js | 51 ++ js/src/dapps/dappreg/dappsStore.js | 482 ++++++++++++++++++ js/src/dapps/dappreg/modalStore.js | 266 ++++++++++ js/src/dapps/dappreg/parity.js | 53 ++ js/webpack.config.js | 1 + 37 files changed, 2593 insertions(+) create mode 100644 js/src/dapps/dappreg.html create mode 100644 js/src/dapps/dappreg.js create mode 100644 js/src/dapps/dappreg/Application/application.css create mode 100644 js/src/dapps/dappreg/Application/application.js create mode 100644 js/src/dapps/dappreg/Application/index.js create mode 100644 js/src/dapps/dappreg/Button/button.css create mode 100644 js/src/dapps/dappreg/Button/button.js create mode 100644 js/src/dapps/dappreg/Button/index.js create mode 100644 js/src/dapps/dappreg/ButtonBar/buttonBar.css create mode 100644 js/src/dapps/dappreg/ButtonBar/buttonBar.js create mode 100644 js/src/dapps/dappreg/ButtonBar/index.js create mode 100644 js/src/dapps/dappreg/Dapp/dapp.css create mode 100644 js/src/dapps/dappreg/Dapp/dapp.js create mode 100644 js/src/dapps/dappreg/Dapp/index.js create mode 100644 js/src/dapps/dappreg/Input/index.js create mode 100644 js/src/dapps/dappreg/Input/input.css create mode 100644 js/src/dapps/dappreg/Input/input.js create mode 100644 js/src/dapps/dappreg/Modal/index.js create mode 100644 js/src/dapps/dappreg/Modal/modal.css create mode 100644 js/src/dapps/dappreg/Modal/modal.js create mode 100644 js/src/dapps/dappreg/ModalDelete/index.js create mode 100644 js/src/dapps/dappreg/ModalDelete/modalDelete.js create mode 100644 js/src/dapps/dappreg/ModalRegister/index.js create mode 100644 js/src/dapps/dappreg/ModalRegister/modalRegister.js create mode 100644 js/src/dapps/dappreg/ModalUpdate/index.js create mode 100644 js/src/dapps/dappreg/ModalUpdate/modalUpdate.js create mode 100644 js/src/dapps/dappreg/SelectAccount/index.js create mode 100644 js/src/dapps/dappreg/SelectAccount/selectAccount.js create mode 100644 js/src/dapps/dappreg/SelectDapp/index.js create mode 100644 js/src/dapps/dappreg/SelectDapp/selectDapp.js create mode 100644 js/src/dapps/dappreg/Warning/index.js create mode 100644 js/src/dapps/dappreg/Warning/warning.css create mode 100644 js/src/dapps/dappreg/Warning/warning.js create mode 100644 js/src/dapps/dappreg/dappsStore.js create mode 100644 js/src/dapps/dappreg/modalStore.js create mode 100644 js/src/dapps/dappreg/parity.js diff --git a/js/src/dapps/dappreg.html b/js/src/dapps/dappreg.html new file mode 100644 index 000000000..89c95c472 --- /dev/null +++ b/js/src/dapps/dappreg.html @@ -0,0 +1,17 @@ + + + + + + + + Dapp Registry + + +
+ + + + + + diff --git a/js/src/dapps/dappreg.js b/js/src/dapps/dappreg.js new file mode 100644 index 000000000..243576a34 --- /dev/null +++ b/js/src/dapps/dappreg.js @@ -0,0 +1,35 @@ +// Copyright 2015, 2016 Ethcore (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 React from 'react'; +import ReactDOM from 'react-dom'; +import injectTapEventPlugin from 'react-tap-event-plugin'; +import { useStrict } from 'mobx'; + +injectTapEventPlugin(); +useStrict(true); + +import Application from './dappreg/Application'; + +import '../../assets/fonts/Roboto/font.css'; +import '../../assets/fonts/RobotoMono/font.css'; +import './style.css'; +import './dappreg.html'; + +ReactDOM.render( + , + document.querySelector('#container') +); diff --git a/js/src/dapps/dappreg/Application/application.css b/js/src/dapps/dappreg/Application/application.css new file mode 100644 index 000000000..f171d8127 --- /dev/null +++ b/js/src/dapps/dappreg/Application/application.css @@ -0,0 +1,58 @@ +/* Copyright 2015, 2016 Ethcore (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 . +*/ + +.body { + color: #333; + background: #eee; + padding: 4.5em 0; + text-align: center; +} + +.apps { + background: #fff; + border-radius: 0.5em; + margin: 0 auto; + max-width: 980px; + padding: 1.5em; + text-align: left; +} + +.footer { + font-size: 0.75em; + margin: 1em; + padding: 1.5em; + text-align: center; +} + +.header { + background: #44e; + border-radius: 0 0 0.25em 0.25em; + color: #fff; + left: 0; + padding: 1em; + position: fixed; + right: 0; + top: 0; + z-index: 25; +} + +.loading { + text-align: center; + padding-top: 5em; + font-size: 2em; + color: #999; +} diff --git a/js/src/dapps/dappreg/Application/application.js b/js/src/dapps/dappreg/Application/application.js new file mode 100644 index 000000000..b5e4d5a97 --- /dev/null +++ b/js/src/dapps/dappreg/Application/application.js @@ -0,0 +1,64 @@ +// Copyright 2015, 2016 Ethcore (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 React, { Component } from 'react'; +import { observer } from 'mobx-react'; + +import DappsStore from '../dappsStore'; + +import ButtonBar from '../ButtonBar'; +import Dapp from '../Dapp'; +import ModalDelete from '../ModalDelete'; +import ModalRegister from '../ModalRegister'; +import ModalUpdate from '../ModalUpdate'; +import SelectDapp from '../SelectDapp'; +import Warning from '../Warning'; +import styles from './application.css'; + +@observer +export default class Application extends Component { + dappsStore = DappsStore.instance(); + + render () { + if (this.dappsStore.isLoading) { + return ( +
+ Loading application +
+ ); + } + + return ( +
+
+ DAPP REGISTRY, a global view of distributed applications available on the network. Putting the puzzle together. +
+
+ + + +
+
+ { this.dappsStore.count } applications registered, { this.dappsStore.ownedCount } owned by user +
+ + + + +
+ ); + } +} diff --git a/js/src/dapps/dappreg/Application/index.js b/js/src/dapps/dappreg/Application/index.js new file mode 100644 index 000000000..236578226 --- /dev/null +++ b/js/src/dapps/dappreg/Application/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (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 . + +export default from './application'; diff --git a/js/src/dapps/dappreg/Button/button.css b/js/src/dapps/dappreg/Button/button.css new file mode 100644 index 000000000..a66736e46 --- /dev/null +++ b/js/src/dapps/dappreg/Button/button.css @@ -0,0 +1,38 @@ +/* Copyright 2015, 2016 Ethcore (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 . +*/ + +.button { + background: #44e; + border: none; + border-radius: 0.25em; + color: #fff; + cursor: pointer; + font-size: 1em; + margin: 1em 0.375em; + opacity: 0.85; + padding: 0.75em 2em; + + &[disabled] { + opacity: 0.5; + cursor: default; + background: #aaa; + } + + &[data-warning="true"] { + background: #e44; + } +} diff --git a/js/src/dapps/dappreg/Button/button.js b/js/src/dapps/dappreg/Button/button.js new file mode 100644 index 000000000..1f3b67b4c --- /dev/null +++ b/js/src/dapps/dappreg/Button/button.js @@ -0,0 +1,52 @@ +// Copyright 2015, 2016 Ethcore (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 React, { Component, PropTypes } from 'react'; + +import styles from './button.css'; + +export default class Button extends Component { + static propTypes = { + className: PropTypes.string, + disabled: PropTypes.bool, + label: PropTypes.string.isRequired, + warning: PropTypes.bool, + onClick: PropTypes.func.isRequired + } + + render () { + const { className, disabled, label, warning } = this.props; + const classes = `${styles.button} ${className}`; + + return ( + + ); + } + + onClick = (event) => { + if (this.props.disabled) { + return; + } + + this.props.onClick(event); + } +} diff --git a/js/src/dapps/dappreg/Button/index.js b/js/src/dapps/dappreg/Button/index.js new file mode 100644 index 000000000..f69a65e3d --- /dev/null +++ b/js/src/dapps/dappreg/Button/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (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 . + +export default from './button'; diff --git a/js/src/dapps/dappreg/ButtonBar/buttonBar.css b/js/src/dapps/dappreg/ButtonBar/buttonBar.css new file mode 100644 index 000000000..0aa84ee29 --- /dev/null +++ b/js/src/dapps/dappreg/ButtonBar/buttonBar.css @@ -0,0 +1,21 @@ +/* Copyright 2015, 2016 Ethcore (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 . +*/ + +.buttonbar { + text-align: center; + margin: 1em 0 0 0; +} diff --git a/js/src/dapps/dappreg/ButtonBar/buttonBar.js b/js/src/dapps/dappreg/ButtonBar/buttonBar.js new file mode 100644 index 000000000..289def0ea --- /dev/null +++ b/js/src/dapps/dappreg/ButtonBar/buttonBar.js @@ -0,0 +1,101 @@ +// Copyright 2015, 2016 Ethcore (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 React, { Component } from 'react'; +import { observer } from 'mobx-react'; + +import DappsStore from '../dappsStore'; +import ModalStore from '../modalStore'; + +import Button from '../Button'; +import styles from './buttonBar.css'; + +@observer +export default class ButtonBar extends Component { + dappsStore = DappsStore.instance(); + modalStore = ModalStore.instance(); + + render () { + let buttons = []; + + if (this.dappsStore.isEditing || this.dappsStore.isNew) { + buttons = [ +