From 57eb7ad0a95c0729ad4b9f8d844773548911b0d0 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Tue, 20 Dec 2016 17:35:04 -0800 Subject: [PATCH] A few more basic block checks in the chain pipeline. Added a chain adapter trait that's notified when a block is accepted. --- chain/Cargo.toml | 1 + chain/src/lib.rs | 4 +- chain/src/pipe.rs | 66 +++++++++++++++++++++++++------- chain/src/store.rs | 3 -- chain/src/types.rs | 14 +++++++ chain/tests/mine_simple_chain.rs | 3 +- 6 files changed, 72 insertions(+), 19 deletions(-) diff --git a/chain/Cargo.toml b/chain/Cargo.toml index 73f8df93d..243b88b54 100644 --- a/chain/Cargo.toml +++ b/chain/Cargo.toml @@ -6,6 +6,7 @@ authors = ["Ignotus Peverell "] [dependencies] bitflags = "^0.7.0" byteorder = "^0.5" +log = "^0.3" time = "^0.1" grin_core = { path = "../core" } diff --git a/chain/src/lib.rs b/chain/src/lib.rs index 6b17e9293..44807ee85 100644 --- a/chain/src/lib.rs +++ b/chain/src/lib.rs @@ -23,6 +23,8 @@ #[macro_use] extern crate bitflags; extern crate byteorder; +#[macro_use] +extern crate log; extern crate time; extern crate grin_core as core; @@ -35,5 +37,5 @@ pub mod types; // Re-export the base interface -pub use types::{ChainStore, Tip}; +pub use types::{ChainStore, Tip, ChainAdapter}; pub use pipe::{NONE, process_block}; diff --git a/chain/src/pipe.rs b/chain/src/pipe.rs index 239fd9551..631c21343 100644 --- a/chain/src/pipe.rs +++ b/chain/src/pipe.rs @@ -24,7 +24,7 @@ use core::core::hash::Hash; use core::core::{BlockHeader, Block, Proof}; use core::pow; use types; -use types::{Tip, ChainStore}; +use types::{Tip, ChainStore, ChainAdapter, NoopAdapter}; use store; bitflags! { @@ -41,6 +41,7 @@ bitflags! { pub struct BlockContext { opts: Options, store: Arc, + adapter: Arc, head: Tip, tip: Option, } @@ -51,6 +52,8 @@ pub enum Error { Unfit(String), /// Target is too high either compared to ours or the block PoW hash TargetTooHigh, + /// Size of the Cuckoo graph in block header doesn't match PoW requirements + WrongCuckooSize, /// The proof of work is invalid InvalidPow, /// The block doesn't sum correctly or a tx signature is invalid @@ -61,34 +64,53 @@ pub enum Error { StoreErr(types::Error), } -pub fn process_block(b: &Block, store: Arc, opts: Options) -> Result<(), Error> { +/// Runs the block processing pipeline, including validation and finding a +/// place for the new block in the chain. Returns the new +/// chain head if updated. +pub fn process_block(b: &Block, + store: Arc, + adapter: Arc, + opts: Options) + -> Result, Error> { // TODO should just take a promise for a block with a full header so we don't // spend resources reading the full block when its header is invalid let head = try!(store.head().map_err(&Error::StoreErr)); + let mut ctx = BlockContext { opts: opts, store: store, + adapter: adapter, head: head, tip: None, }; + info!("Starting validation pipeline for block {} at {}.", + b.hash(), + b.header.height); + try!(check_known(b.hash(), &mut ctx)); try!(validate_header(&b, &mut ctx)); try!(set_tip(&b.header, &mut ctx)); try!(validate_block(b, &mut ctx)); + info!("Block at {} with hash {} is valid, going to save and append.", + b.header.height, + b.hash()); try!(add_block(b, &mut ctx)); + // TODO a global lock should be set before that step or even earlier try!(update_tips(&mut ctx)); - Ok(()) + + // TODO make sure we always return the head, and not a fork that just got longer + Ok(ctx.tip) } -// block processing pipeline -// 1. is the header valid (time, PoW, etc.) -// 2. is it the next head, a new fork, or extension of a fork (not a too old -// fork tho) -// 3. ok fine, is all of it valid (txs, merkle, utxo merkle, etc.) -// 4. add the sucker to the head/fork -// 5. did we increase a fork difficulty over the head? -// 6. ok fine, swap them up (can be tricky, think addresses invalidation) +/// Quick in-memory check to fast-reject any block we've already handled +/// recently. Keeps duplicates from the network in check. +fn check_known(bh: Hash, ctx: &mut BlockContext) -> Result<(), Error> { + if bh == ctx.head.last_block_h || bh == ctx.head.prev_block_h { + return Err(Error::Unfit("already known".to_string())); + } + Ok(()) +} /// First level of black validation that only needs to act on the block header /// to make it as cheap as possible. The different validations are also @@ -115,6 +137,7 @@ fn validate_header(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { return Err(Error::InvalidBlockTime); } + // verify the proof of work and related parameters let (diff_target, cuckoo_sz) = consensus::next_target(header.timestamp.to_timespec().sec, prev.timestamp.to_timespec().sec, prev.target, @@ -122,12 +145,15 @@ fn validate_header(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { if header.target > diff_target { return Err(Error::TargetTooHigh); } + if header.cuckoo_len != cuckoo_sz && !ctx.opts.intersects(EASY_POW) { + return Err(Error::WrongCuckooSize); + } if ctx.opts.intersects(EASY_POW) { if !pow::verify_size(b, 15) { return Err(Error::InvalidPow); } - } else if !pow::verify_size(b, cuckoo_sz as u32) { + } else if !pow::verify(b) { return Err(Error::InvalidPow); } @@ -135,6 +161,11 @@ fn validate_header(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { } fn set_tip(h: &BlockHeader, ctx: &mut BlockContext) -> Result<(), Error> { + // TODO actually support more than one branch + if h.previous != ctx.head.last_block_h { + return Err(Error::Unfit("Just don't know where to put it right now".to_string())); + } + // TODO validate block header height ctx.tip = Some(ctx.head.clone()); Ok(()) } @@ -147,10 +178,17 @@ fn validate_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { } fn add_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { + // save the block and appends it to the selected tip ctx.tip = ctx.tip.as_ref().map(|t| t.append(b.hash())); - ctx.store.save_block(b).map_err(&Error::StoreErr) + ctx.store.save_block(b).map_err(&Error::StoreErr); + + // broadcast the block + let adapter = ctx.adapter.clone(); + adapter.block_accepted(b); + Ok(()) } fn update_tips(ctx: &mut BlockContext) -> Result<(), Error> { - ctx.store.save_head(ctx.tip.as_ref().unwrap()).map_err(&Error::StoreErr) + let tip = ctx.tip.as_ref().unwrap(); + ctx.store.save_head(tip).map_err(&Error::StoreErr) } diff --git a/chain/src/store.rs b/chain/src/store.rs index bbb256685..a3c5415d8 100644 --- a/chain/src/store.rs +++ b/chain/src/store.rs @@ -36,9 +36,6 @@ pub struct ChainKVStore { db: grin_store::Store, } -unsafe impl Sync for ChainKVStore {} -unsafe impl Send for ChainKVStore {} - impl ChainKVStore { pub fn new(root_path: String) -> Result { let db = try!(grin_store::Store::open(format!("{}/{}", root_path, STORE_SUBPATH).as_str()) diff --git a/chain/src/types.rs b/chain/src/types.rs index b58099207..1075005bd 100644 --- a/chain/src/types.rs +++ b/chain/src/types.rs @@ -152,3 +152,17 @@ pub trait ChainStore: Send + Sync { /// Save the provided tip without setting it as head fn save_tip(&self, t: &Tip) -> Result<(), Error>; } + +/// Bridge between the chain pipeline and the rest of the system. Handles +/// downstream processing of valid blocks by the rest of the system, most +/// importantly the broadcasting of blocks to our peers. +pub trait ChainAdapter { + /// The blockchain pipeline has accepted this block as valid and added + /// it to our chain. + fn block_accepted(&self, b: &Block); +} + +pub struct NoopAdapter { } +impl ChainAdapter for NoopAdapter { + fn block_accepted(&self, b: &Block) {} +} diff --git a/chain/tests/mine_simple_chain.rs b/chain/tests/mine_simple_chain.rs index e426aef09..00c04bcc8 100644 --- a/chain/tests/mine_simple_chain.rs +++ b/chain/tests/mine_simple_chain.rs @@ -43,6 +43,7 @@ fn mine_empty_chain() { 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(); @@ -56,7 +57,7 @@ fn mine_empty_chain() { b.header.pow = proof; b.header.nonce = nonce; b.header.target = diff_target; - grin_chain::pipe::process_block(&b, arc_store.clone(), grin_chain::pipe::EASY_POW).unwrap(); + grin_chain::pipe::process_block(&b, arc_store.clone(), adapter.clone(), grin_chain::pipe::EASY_POW).unwrap(); // checking our new head let head = arc_store.clone().head().unwrap();