Initial import.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
//! The block chain itself, validates and accepts new blocks, handles reorgs.
|
||||
|
||||
#![deny(non_upper_case_globals)]
|
||||
#![deny(non_camel_case_types)]
|
||||
#![deny(non_snake_case)]
|
||||
#![deny(unused_mut)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate byteorder;
|
||||
|
||||
#[macro_use(try_m)]
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_store;
|
||||
extern crate secp256k1zkp as secp;
|
||||
|
||||
pub mod pipe;
|
||||
pub mod store;
|
||||
pub mod types;
|
||||
|
||||
// Re-export the base interface
|
||||
|
||||
pub use types::ChainStore;
|
||||
pub use chain::Options;
|
||||
pub use chain::process_block;
|
||||
@@ -0,0 +1,118 @@
|
||||
//! Implementation of the chain block acceptance (or refusal) pipeline.
|
||||
|
||||
use secp;
|
||||
|
||||
use core::core::{Hash, BlockHeader, Block, Proof};
|
||||
use core::pow;
|
||||
use types;
|
||||
use types::{Tip, ChainStore};
|
||||
use store;
|
||||
|
||||
bitflags! {
|
||||
/// Options for block validation
|
||||
pub flags Options: u32 {
|
||||
/// Runs with the easier version of the Proof of Work, mostly to make testing easier.
|
||||
const EASY_POW = 0b00000001,
|
||||
}
|
||||
}
|
||||
|
||||
/// Contextual information required to process a new block and either reject or
|
||||
/// accept it.
|
||||
pub struct BlockContext<'a> {
|
||||
opts: Options,
|
||||
store: &'a ChainStore,
|
||||
head: Tip,
|
||||
tip: Option<Tip>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// The block doesn't fit anywhere in our chain
|
||||
Unfit(String),
|
||||
/// The proof of work is invalid
|
||||
InvalidPow,
|
||||
/// The block doesn't sum correctly or a tx signature is invalid
|
||||
InvalidBlockProof(secp::Error),
|
||||
/// Internal issue when trying to save the block
|
||||
StoreErr(types::Error),
|
||||
}
|
||||
|
||||
pub fn process_block(b: &Block, store: &ChainStore, opts: Options) -> Option<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 = match store.head() {
|
||||
Ok(head) => head,
|
||||
Err(err) => return Some(Error::StoreErr(err)),
|
||||
};
|
||||
let mut ctx = BlockContext {
|
||||
opts: opts,
|
||||
store: store,
|
||||
head: head,
|
||||
tip: None,
|
||||
};
|
||||
|
||||
try_m!(validate_header(&b, &mut ctx));
|
||||
try_m!(set_tip(&b.header, &mut ctx));
|
||||
try_m!(validate_block(b, &mut ctx));
|
||||
try_m!(add_block(b, &mut ctx));
|
||||
try_m!(update_tips(&mut ctx));
|
||||
None
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
/// 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
|
||||
/// arranged by order of cost to have as little DoS surface as possible.
|
||||
/// TODO actually require only the block header (with length information)
|
||||
fn validate_header(b: &Block, ctx: &mut BlockContext) -> Option<Error> {
|
||||
let header = &b.header;
|
||||
println!("{} {}", header.height, ctx.head.height);
|
||||
if header.height > ctx.head.height + 1 {
|
||||
// TODO actually handle orphans and add them to a size-limited set
|
||||
return Some(Error::Unfit("orphan".to_string()));
|
||||
}
|
||||
// TODO check time wrt to chain time, refuse older than 100 blocks or too far
|
||||
// in future
|
||||
|
||||
// TODO maintain current difficulty
|
||||
let diff_target = Proof(pow::MAX_TARGET);
|
||||
|
||||
if ctx.opts.intersects(EASY_POW) {
|
||||
if !pow::verify20(b, diff_target) {
|
||||
return Some(Error::InvalidPow);
|
||||
}
|
||||
} else if !pow::verify(b, diff_target) {
|
||||
return Some(Error::InvalidPow);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn set_tip(h: &BlockHeader, ctx: &mut BlockContext) -> Option<Error> {
|
||||
ctx.tip = Some(ctx.head.clone());
|
||||
None
|
||||
}
|
||||
|
||||
fn validate_block(b: &Block, ctx: &mut BlockContext) -> Option<Error> {
|
||||
// TODO check tx merkle tree
|
||||
let curve = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
|
||||
try_m!(b.verify(&curve).err().map(&Error::InvalidBlockProof));
|
||||
None
|
||||
}
|
||||
|
||||
fn add_block(b: &Block, ctx: &mut BlockContext) -> Option<Error> {
|
||||
ctx.tip = ctx.tip.as_ref().map(|t| t.append(b.hash()));
|
||||
ctx.store.save_block(b).map(&Error::StoreErr)
|
||||
}
|
||||
|
||||
fn update_tips(ctx: &mut BlockContext) -> Option<Error> {
|
||||
ctx.store.save_head(ctx.tip.as_ref().unwrap()).map(&Error::StoreErr)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! Implements storage primitives required by the chain
|
||||
|
||||
use byteorder::{WriteBytesExt, BigEndian};
|
||||
|
||||
use types::*;
|
||||
use core::core::Block;
|
||||
use grin_store;
|
||||
|
||||
const STORE_PATH: &'static str = ".grin/chain";
|
||||
|
||||
const SEP: u8 = ':' as u8;
|
||||
|
||||
const BLOCK_PREFIX: u8 = 'B' as u8;
|
||||
const TIP_PREFIX: u8 = 'T' as u8;
|
||||
const HEAD_PREFIX: u8 = 'H' as u8;
|
||||
|
||||
/// An implementation of the ChainStore trait backed by a simple key-value
|
||||
/// store.
|
||||
pub struct ChainKVStore {
|
||||
db: grin_store::Store,
|
||||
}
|
||||
|
||||
impl ChainKVStore {
|
||||
pub fn new() -> Result<ChainKVStore, Error> {
|
||||
let db = try!(grin_store::Store::open(STORE_PATH).map_err(to_store_err));
|
||||
Ok(ChainKVStore { db: db })
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainStore for ChainKVStore {
|
||||
fn head(&self) -> Result<Tip, Error> {
|
||||
option_to_not_found(self.db.get_ser(&vec![HEAD_PREFIX]))
|
||||
}
|
||||
|
||||
fn save_block(&self, b: &Block) -> Option<Error> {
|
||||
self.db.put_ser(&to_key(BLOCK_PREFIX, &mut b.hash().to_vec())[..], b).map(&to_store_err)
|
||||
}
|
||||
|
||||
fn save_head(&self, t: &Tip) -> Option<Error> {
|
||||
try_m!(self.save_tip(t));
|
||||
self.db.put_ser(&vec![HEAD_PREFIX], t).map(&to_store_err)
|
||||
}
|
||||
|
||||
fn save_tip(&self, t: &Tip) -> Option<Error> {
|
||||
let last_branch = t.lineage.last_branch();
|
||||
let mut k = vec![TIP_PREFIX, SEP];
|
||||
k.write_u32::<BigEndian>(last_branch);
|
||||
self.db.put_ser(&mut k, t).map(&to_store_err)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_key(prefix: u8, val: &mut Vec<u8>) -> &mut Vec<u8> {
|
||||
val.insert(0, SEP);
|
||||
val.insert(0, prefix);
|
||||
val
|
||||
}
|
||||
|
||||
fn to_store_err(e: grin_store::Error) -> Error {
|
||||
Error::StorageErr(format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// unwraps the inner option by converting the none case to a not found error
|
||||
fn option_to_not_found<T>(res: Result<Option<T>, grin_store::Error>) -> Result<T, Error> {
|
||||
match res {
|
||||
Ok(None) => Err(Error::NotFoundErr),
|
||||
Ok(Some(o)) => Ok(o),
|
||||
Err(e) => Err(to_store_err(e)),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
//! Base types that the block chain pipeline requires.
|
||||
|
||||
use core::core::{Hash, Block};
|
||||
use core::ser;
|
||||
|
||||
/// The lineage of a fork, defined as a series of numbers. Each new branch gets
|
||||
/// a new number that gets added to a fork's ancestry to form a new fork.
|
||||
/// Example:
|
||||
/// head [1] -> fork1 [1, 2]
|
||||
/// fork2 [1, 3]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Lineage(Vec<u32>);
|
||||
|
||||
impl Lineage {
|
||||
/// New lineage initialized just with branch 0
|
||||
pub fn new() -> Lineage {
|
||||
Lineage(vec![0])
|
||||
}
|
||||
/// The last branch that was added to the lineage. Also the only branch
|
||||
/// that's
|
||||
/// unique to this lineage.
|
||||
pub fn last_branch(&self) -> u32 {
|
||||
*self.0.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialization for lineage, necessary to serialize fork tips.
|
||||
impl ser::Writeable for Lineage {
|
||||
fn write(&self, writer: &mut ser::Writer) -> Option<ser::Error> {
|
||||
try_m!(writer.write_u32(self.0.len() as u32));
|
||||
for num in &self.0 {
|
||||
try_m!(writer.write_u32(*num));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Deserialization for lineage, necessary to deserialize fork tips.
|
||||
impl ser::Readable<Lineage> for Lineage {
|
||||
fn read(reader: &mut ser::Reader) -> Result<Lineage, ser::Error> {
|
||||
let len = try!(reader.read_u32());
|
||||
let mut branches = Vec::with_capacity(len as usize);
|
||||
for _ in 0..len {
|
||||
branches.push(try!(reader.read_u32()));
|
||||
}
|
||||
Ok(Lineage(branches))
|
||||
}
|
||||
}
|
||||
|
||||
/// The tip of a fork. A handle to the fork ancestry from its leaf in the
|
||||
/// blockchain tree. References both the lineage of the fork as well as its max
|
||||
/// height and its latest and previous blocks for convenience.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tip {
|
||||
/// Height of the tip (max height of the fork)
|
||||
pub height: u64,
|
||||
/// Last block pushed to the fork
|
||||
pub last_block_h: Hash,
|
||||
/// Block previous to last
|
||||
pub prev_block_h: Hash,
|
||||
/// Lineage in branch numbers of the fork
|
||||
pub lineage: Lineage,
|
||||
}
|
||||
|
||||
impl Tip {
|
||||
/// Creates a new tip at height zero and the provided genesis hash.
|
||||
pub fn new(gbh: Hash) -> Tip {
|
||||
Tip {
|
||||
height: 0,
|
||||
last_block_h: gbh,
|
||||
prev_block_h: gbh,
|
||||
lineage: Lineage::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a new block hash to this tip, returning a new updated tip.
|
||||
pub fn append(&self, bh: Hash) -> Tip {
|
||||
Tip {
|
||||
height: self.height + 1,
|
||||
last_block_h: bh,
|
||||
prev_block_h: self.last_block_h,
|
||||
lineage: self.lineage.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialization of a tip, required to save to datastore.
|
||||
impl ser::Writeable for Tip {
|
||||
fn write(&self, writer: &mut ser::Writer) -> Option<ser::Error> {
|
||||
try_m!(writer.write_u64(self.height));
|
||||
try_m!(writer.write_fixed_bytes(&self.last_block_h));
|
||||
try_m!(writer.write_fixed_bytes(&self.prev_block_h));
|
||||
self.lineage.write(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Readable<Tip> for Tip {
|
||||
fn read(reader: &mut ser::Reader) -> Result<Tip, ser::Error> {
|
||||
let height = try!(reader.read_u64());
|
||||
let last = try!(reader.read_fixed_bytes(32));
|
||||
let prev = try!(reader.read_fixed_bytes(32));
|
||||
let line = try!(Lineage::read(reader));
|
||||
Ok(Tip {
|
||||
height: height,
|
||||
last_block_h: Hash::from_vec(last),
|
||||
prev_block_h: Hash::from_vec(prev),
|
||||
lineage: line,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Couldn't find what we were looking for
|
||||
NotFoundErr,
|
||||
/// Error generated by the underlying storage layer
|
||||
StorageErr(String),
|
||||
}
|
||||
|
||||
/// Trait the chain pipeline requires an implementor for in order to process
|
||||
/// blocks.
|
||||
pub trait ChainStore {
|
||||
/// Get the tip that's also the head of the chain
|
||||
fn head(&self) -> Result<Tip, Error>;
|
||||
|
||||
/// Save the provided block in store
|
||||
fn save_block(&self, b: &Block) -> Option<Error>;
|
||||
|
||||
/// Save the provided tip as the current head of our chain
|
||||
fn save_head(&self, t: &Tip) -> Option<Error>;
|
||||
|
||||
/// Save the provided tip without setting it as head
|
||||
fn save_tip(&self, t: &Tip) -> Option<Error>;
|
||||
}
|
||||
Reference in New Issue
Block a user