From cc9690ee8c94f3e115ede91f4b007ac4fa0fb2e7 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Mon, 9 Jan 2017 20:30:02 -0800 Subject: [PATCH] Test for chain forks and head switching. --- chain/Cargo.toml | 1 + chain/src/pipe.rs | 39 +++++++++---------- chain/tests/mine_simple_chain.rs | 66 +++++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/chain/Cargo.toml b/chain/Cargo.toml index 243b88b54..3d99666cf 100644 --- a/chain/Cargo.toml +++ b/chain/Cargo.toml @@ -14,4 +14,5 @@ grin_store = { path = "../store" } secp256k1zkp = { path = "../secp256k1zkp" } [dev-dependencies] +env_logger="^0.3.5" rand = "^0.3" diff --git a/chain/src/pipe.rs b/chain/src/pipe.rs index 8d7fdd05a..684b1293c 100644 --- a/chain/src/pipe.rs +++ b/chain/src/pipe.rs @@ -32,8 +32,8 @@ bitflags! { /// Options for block validation pub flags Options: u32 { const NONE = 0b00000001, - /// Runs with the easier version of the Proof of Work, mostly to make testing easier. - const EASY_POW = 0b00000010, + /// Runs without checking the Proof of Work, mostly to make testing easier. + const SKIP_POW = 0b00000010, } } @@ -140,28 +140,26 @@ fn validate_header(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { return Err(Error::InvalidBlockTime); } - if header.total_difficulty != prev.total_difficulty.clone() + prev.pow.to_difficulty() { - return Err(Error::WrongTotalDifficulty); - } + if !ctx.opts.intersects(SKIP_POW) { + // verify the proof of work and related parameters - // verify the proof of work and related parameters - let (difficulty, cuckoo_sz) = consensus::next_target(header.timestamp.to_timespec().sec, - prev.timestamp.to_timespec().sec, - prev.difficulty, - prev.cuckoo_len); - if header.difficulty < difficulty { - return Err(Error::DifficultyTooLow); - } - if header.cuckoo_len != cuckoo_sz && !ctx.opts.intersects(EASY_POW) { - return Err(Error::WrongCuckooSize); - } + if header.total_difficulty != prev.total_difficulty.clone() + prev.pow.to_difficulty() { + return Err(Error::WrongTotalDifficulty); + } - if ctx.opts.intersects(EASY_POW) { - if !pow::verify_size(b, 16) { + let (difficulty, cuckoo_sz) = consensus::next_target(header.timestamp.to_timespec().sec, + prev.timestamp.to_timespec().sec, + prev.difficulty, + prev.cuckoo_len); + if header.difficulty < difficulty { + return Err(Error::DifficultyTooLow); + } + if header.cuckoo_len != cuckoo_sz { + return Err(Error::WrongCuckooSize); + } + if !pow::verify(b) { return Err(Error::InvalidPow); } - } else if !pow::verify(b) { - return Err(Error::InvalidPow); } Ok(()) @@ -196,6 +194,7 @@ fn update_head(b: &Block, ctx: &mut BlockContext) -> Result, Error> if tip.total_difficulty > ctx.head.total_difficulty { try!(ctx.store.save_head(&tip).map_err(&Error::StoreErr)); ctx.head = tip.clone(); + info!("Updated head to {} at {}.", b.hash(), b.header.height); Ok(Some(tip)) } else { Ok(None) diff --git a/chain/tests/mine_simple_chain.rs b/chain/tests/mine_simple_chain.rs index c13f0cbb4..a63f534f2 100644 --- a/chain/tests/mine_simple_chain.rs +++ b/chain/tests/mine_simple_chain.rs @@ -14,21 +14,25 @@ extern crate grin_core; extern crate grin_chain; -extern crate rand; +extern crate env_logger; extern crate time; +extern crate rand; extern crate secp256k1zkp as secp; use std::sync::Arc; +use std::thread; use rand::os::OsRng; use grin_chain::types::*; use grin_core::core::hash::Hashed; +use grin_core::core::target::Difficulty; use grin_core::pow; use grin_core::core; use grin_core::consensus; #[test] fn mine_empty_chain() { + env_logger::init().unwrap(); let mut rng = OsRng::new().unwrap(); let store = grin_chain::store::ChainKVStore::new(".grin".to_string()).unwrap(); @@ -64,7 +68,7 @@ fn mine_empty_chain() { grin_chain::pipe::process_block(&b, arc_store.clone(), adapter.clone(), - grin_chain::pipe::EASY_POW) + grin_chain::pipe::NONE) .unwrap(); // checking our new head @@ -75,3 +79,61 @@ fn mine_empty_chain() { prev = b; } } + +#[test] +fn mine_forks() { + env_logger::init().unwrap(); + let mut rng = OsRng::new().unwrap(); + let store = grin_chain::store::ChainKVStore::new(".grin2".to_string()).unwrap(); + + // save a genesis block + let mut gen = grin_core::genesis::genesis(); + store.save_block(&gen).unwrap(); + + // setup a new head tip + let tip = Tip::new(gen.hash()); + store.save_head(&tip).unwrap(); + + // mine and add a few blocks + let mut prev = gen; + let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + let reward_key = secp::key::SecretKey::new(&secp, &mut rng); + let arc_store = Arc::new(store); + let adapter = Arc::new(NoopAdapter {}); + + for n in 1..4 { + let mut b = core::Block::new(&prev.header, vec![], reward_key).unwrap(); + b.header.timestamp = prev.header.timestamp + time::Duration::seconds(60); + b.header.total_difficulty = Difficulty::from_num(2*n); + grin_chain::pipe::process_block(&b, + arc_store.clone(), + adapter.clone(), + grin_chain::pipe::SKIP_POW) + .unwrap(); + + // checking our new head + thread::sleep(::std::time::Duration::from_millis(50)); + let head = arc_store.clone().head().unwrap(); + assert_eq!(head.height, n as u64); + assert_eq!(head.last_block_h, b.hash()); + assert_eq!(head.prev_block_h, prev.hash()); + + let mut b = core::Block::new(&prev.header, vec![], reward_key).unwrap(); + b.header.timestamp = prev.header.timestamp + time::Duration::seconds(60); + b.header.total_difficulty = Difficulty::from_num(2*n+1); + grin_chain::pipe::process_block(&b, + arc_store.clone(), + adapter.clone(), + grin_chain::pipe::SKIP_POW) + .unwrap(); + + // checking our new head + thread::sleep(::std::time::Duration::from_millis(50)); + let head = arc_store.clone().head().unwrap(); + assert_eq!(head.height, n as u64); + assert_eq!(head.last_block_h, b.hash()); + assert_eq!(head.prev_block_h, prev.hash()); + + prev = b; + } +}