mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
A few more basic block checks in the chain pipeline. Added a chain adapter trait that's notified when a block is accepted.
This commit is contained in:
parent
dc33ebcf39
commit
57eb7ad0a9
6 changed files with 72 additions and 19 deletions
|
@ -6,6 +6,7 @@ authors = ["Ignotus Peverell <igno.peverell@protonmail.com>"]
|
|||
[dependencies]
|
||||
bitflags = "^0.7.0"
|
||||
byteorder = "^0.5"
|
||||
log = "^0.3"
|
||||
time = "^0.1"
|
||||
|
||||
grin_core = { path = "../core" }
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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<ChainStore>,
|
||||
adapter: Arc<ChainAdapter>,
|
||||
head: Tip,
|
||||
tip: Option<Tip>,
|
||||
}
|
||||
|
@ -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<ChainStore>, 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<ChainStore>,
|
||||
adapter: Arc<ChainAdapter>,
|
||||
opts: Options)
|
||||
-> Result<Option<Tip>, 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)
|
||||
}
|
||||
|
|
|
@ -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<ChainKVStore, Error> {
|
||||
let db = try!(grin_store::Store::open(format!("{}/{}", root_path, STORE_SUBPATH).as_str())
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue