diff --git a/Cargo.lock b/Cargo.lock index 4dee89f7..f2fa8ec6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -530,6 +530,7 @@ dependencies = [ "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.1.0", "serialization 0.1.0", + "test-data 0.1.0", ] [[package]] diff --git a/test-data/src/lib.rs b/test-data/src/lib.rs index 4654f936..9499ca0e 100644 --- a/test-data/src/lib.rs +++ b/test-data/src/lib.rs @@ -8,3 +8,17 @@ pub fn block1() -> Block { let block: Block = "01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad27b9137190000000000190760b278fe7b8565fda3b968b918d5fd997f993b23674c0af3b6fde300b38f33a5914ce6ed5b1b01e32f570201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704e6ed5b1b014effffffff0100f2052a01000000434104b68a50eaa0287eff855189f949c1c6e5f58b37c88231373d8a59809cbae83059cc6469d65c665ccfd1cfeb75c6e8e19413bba7fbff9bc762419a76d87b16086eac000000000100000001a6b97044d03da79c005b20ea9c0e1a6d9dc12d9f7b91a5911c9030a439eed8f5000000004948304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501ffffffff0100f2052a010000001976a914404371705fa9bd789a2fcd52d2c580b65d35549d88ac00000000".into(); block } + +// https://webbtc.com/block/00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048 +// height 1 +pub fn block_h1() -> Block { + let block: Block = "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000".into(); + block +} + +// https://webbtc.com/block/000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd.hex +// height 2 +pub fn block_h2() -> Block { + let block: Block = "010000004860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd610101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d010bffffffff0100f2052a010000004341047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac00000000".into(); + block +} diff --git a/verification/Cargo.toml b/verification/Cargo.toml index cbc2b687..1bf4b4b1 100644 --- a/verification/Cargo.toml +++ b/verification/Cargo.toml @@ -11,3 +11,4 @@ chain = { path = "../chain" } serialization = { path = "../serialization" } parking_lot = "0.3" linked-hash-map = "0.3" +test-data = { path = "../test-data" } diff --git a/verification/src/lib.rs b/verification/src/lib.rs index e0fdbcfa..89478523 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -9,6 +9,8 @@ extern crate linked_hash_map; #[cfg(test)] extern crate ethcore_devtools as devtools; +#[cfg(test)] +extern crate test_data; mod queue; @@ -67,6 +69,6 @@ pub enum BlockStatus { pub type VerificationResult = Result; /// Interface for block verification -pub trait Verify { +pub trait Verify : Send + Sync { fn verify(&self, block: &chain::Block) -> VerificationResult; } diff --git a/verification/src/queue.rs b/verification/src/queue.rs index 02deef5d..674554dc 100644 --- a/verification/src/queue.rs +++ b/verification/src/queue.rs @@ -7,6 +7,8 @@ use linked_hash_map::LinkedHashMap; use parking_lot::RwLock; use std::collections::HashSet; +const MAX_PENDING_PRESET: usize = 128; + pub struct VerifiedBlock { pub chain: Chain, pub block: Block, @@ -18,6 +20,15 @@ impl VerifiedBlock { } } +#[derive(Debug)] +/// Queue errors +pub enum Error { + /// Queue is currently full + Full, + /// There is already block in the queue + Duplicate, +} + /// Verification queue pub struct Queue { verifier: Box, @@ -69,24 +80,121 @@ impl Queue { else if self.items.read().contains_key(hash) { BlockStatus::Pending } else { BlockStatus::Absent } } + + pub fn max_pending(&self) -> usize { + // todo: later might be calculated with lazy-static here based on memory usage + MAX_PENDING_PRESET + } + + pub fn push(&self, block: Block) -> Result<(), Error> { + let hash = block.hash(); + + if self.block_status(&hash) != BlockStatus::Absent { return Err(Error::Duplicate) } + + let mut items = self.items.write(); + if items.len() > self.max_pending() { return Err(Error::Full) } + items.insert(hash, block); + + Ok(()) + } } #[cfg(test)] mod tests { use super::Queue; - use super::super::{BlockStatus, VerificationResult, Verify, Chain}; + use super::super::{BlockStatus, VerificationResult, Verify, Chain, Error as VerificationError}; use chain::Block; use primitives::hash::H256; + use test_data; struct FacileVerifier; impl Verify for FacileVerifier { fn verify(&self, _block: &Block) -> VerificationResult { Ok(Chain::Main) } } + struct EvilVerifier; + impl Verify for EvilVerifier { + fn verify(&self, _block: &Block) -> VerificationResult { Err(VerificationError::Empty) } + } + #[test] fn new() { let queue = Queue::new(Box::new(FacileVerifier)); assert_eq!(queue.block_status(&H256::from(0u8)), BlockStatus::Absent); } + #[test] + fn push() { + let queue = Queue::new(Box::new(FacileVerifier)); + let block = test_data::block1(); + let hash = block.hash(); + + queue.push(block).unwrap(); + + assert_eq!(queue.block_status(&hash), BlockStatus::Pending); + } + + #[test] + fn push_duplicate() { + let queue = Queue::new(Box::new(FacileVerifier)); + let block = test_data::block1(); + let dup_block = test_data::block1(); + + queue.push(block).unwrap(); + let second_push = queue.push(dup_block); + + assert!(second_push.is_err()); + } + + #[test] + fn process_happy() { + let queue = Queue::new(Box::new(FacileVerifier)); + let block = test_data::block1(); + let hash = block.hash(); + + queue.push(block).unwrap(); + queue.process(); + + assert_eq!(queue.block_status(&hash), BlockStatus::Valid); + } + + #[test] + fn process_unhappy() { + let queue = Queue::new(Box::new(EvilVerifier)); + let block = test_data::block1(); + let hash = block.hash(); + + queue.push(block).unwrap(); + queue.process(); + + assert_eq!(queue.block_status(&hash), BlockStatus::Invalid); + } + + #[test] + fn process_async() { + use std::thread; + use std::sync::Arc; + + let queue = Arc::new(Queue::new(Box::new(FacileVerifier))); + + let t1_queue = queue.clone(); + let t1_handle = thread::spawn(move || { + let block_h1 = test_data::block_h1(); + t1_queue.push(block_h1).unwrap(); + t1_queue.process(); + }); + + let t2_queue = queue.clone(); + let t2_handle = thread::spawn(move || { + let block_h2 = test_data::block_h2(); + t2_queue.push(block_h2).unwrap(); + t2_queue.process(); + }); + + t1_handle.join().unwrap(); + t2_handle.join().unwrap(); + + assert_eq!(queue.block_status(&test_data::block_h1().hash()), BlockStatus::Valid); + assert_eq!(queue.block_status(&test_data::block_h2().hash()), BlockStatus::Valid); + } }