From 95c2fa7d8dae720f785886832d8295e5e2e961eb Mon Sep 17 00:00:00 2001 From: debris Date: Sun, 11 Dec 2016 19:10:00 +0100 Subject: [PATCH] HeaderAcceptor finished --- verification/src/accept_block.rs | 44 ++--------- verification/src/accept_chain.rs | 2 +- verification/src/accept_header.rs | 127 +++++++++++++++++++++++++++++- verification/src/constants.rs | 1 + verification/src/error.rs | 2 + 5 files changed, 133 insertions(+), 43 deletions(-) diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index f153f6ff..dcf584d6 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -1,18 +1,17 @@ use network::{Magic, ConsensusParams}; -use db::{SharedStore, PreviousTransactionOutputProvider, BlockHeaderProvider}; +use db::{SharedStore, PreviousTransactionOutputProvider}; use sigops::{StoreWithUnretainedOutputs, transaction_sigops}; -use utils::{work_required, block_reward_satoshi}; +use utils::block_reward_satoshi; use canon::CanonBlock; use constants::MAX_BLOCK_SIGOPS; use error::Error; -const EXPECT_ORDERED: &'static str = "Block ancestors expected to be found in database"; +const EXPECT_CANON: &'static str = "Block ancestors expected to be found in canon chain"; /// Flexible verification of ordered block pub struct BlockAcceptor<'a> { pub finality: BlockFinality<'a>, pub sigops: BlockSigops<'a>, - pub work: BlockWork<'a>, pub coinbase_claim: BlockCoinbaseClaim<'a>, } @@ -22,7 +21,6 @@ impl<'a> BlockAcceptor<'a> { BlockAcceptor { finality: BlockFinality::new(block, height), sigops: BlockSigops::new(block, store.as_previous_transaction_output_provider(), params, MAX_BLOCK_SIGOPS), - work: BlockWork::new(block, store.as_block_header_provider(), height, network), coinbase_claim: BlockCoinbaseClaim::new(block, store.as_previous_transaction_output_provider(), height), } } @@ -30,7 +28,6 @@ impl<'a> BlockAcceptor<'a> { pub fn check(&self) -> Result<(), Error> { try!(self.finality.check()); try!(self.sigops.check()); - try!(self.work.check()); try!(self.coinbase_claim.check()); Ok(()) } @@ -88,7 +85,7 @@ impl<'a> BlockRule for BlockSigops<'a> { let store = StoreWithUnretainedOutputs::new(self.store, &*self.block); let bip16_active = self.block.header.raw.time >= self.consensus_params.bip16_time; let sigops = self.block.transactions.iter() - .map(|tx| transaction_sigops(&tx.raw, &store, bip16_active).expect(EXPECT_ORDERED)) + .map(|tx| transaction_sigops(&tx.raw, &store, bip16_active).expect(EXPECT_CANON)) .sum::(); if sigops > self.max_sigops { @@ -99,37 +96,6 @@ impl<'a> BlockRule for BlockSigops<'a> { } } -pub struct BlockWork<'a> { - block: CanonBlock<'a>, - store: &'a BlockHeaderProvider, - height: u32, - network: Magic, -} - -impl<'a> BlockWork<'a> { - fn new(block: CanonBlock<'a>, store: &'a BlockHeaderProvider, height: u32, network: Magic) -> Self { - BlockWork { - block: block, - store: store, - height: height, - network: network, - } - } -} - -impl<'a> BlockRule for BlockWork<'a> { - fn check(&self) -> Result<(), Error> { - let previous_header_hash = self.block.header.raw.previous_header_hash.clone(); - let time = self.block.header.raw.time; - let work = work_required(previous_header_hash, time, self.height, self.store, self.network); - if work == self.block.header.raw.bits { - Ok(()) - } else { - Err(Error::Difficulty) - } - } -} - pub struct BlockCoinbaseClaim<'a> { block: CanonBlock<'a>, store: &'a PreviousTransactionOutputProvider, @@ -152,7 +118,7 @@ impl<'a> BlockRule for BlockCoinbaseClaim<'a> { let total_outputs = self.block.transactions.iter() .skip(1) .flat_map(|tx| tx.raw.inputs.iter()) - .map(|input| store.previous_transaction_output(&input.previous_output).expect(EXPECT_ORDERED)) + .map(|input| store.previous_transaction_output(&input.previous_output).expect(EXPECT_CANON)) .map(|output| output.value) .sum::(); diff --git a/verification/src/accept_chain.rs b/verification/src/accept_chain.rs index 4f448129..e2680dd2 100644 --- a/verification/src/accept_chain.rs +++ b/verification/src/accept_chain.rs @@ -17,7 +17,7 @@ impl<'a> ChainAcceptor<'a> { pub fn new(store: &'a SharedStore, network: Magic, block: CanonBlock<'a>, height: u32) -> Self { ChainAcceptor { block: BlockAcceptor::new(store, network, block, height), - header: HeaderAcceptor::new(block.header()), + header: HeaderAcceptor::new(store, network, block.header(), height), transactions: block.transactions().into_iter().map(TransactionAcceptor::new).collect(), } } diff --git a/verification/src/accept_header.rs b/verification/src/accept_header.rs index 95ae3fe4..2d954186 100644 --- a/verification/src/accept_header.rs +++ b/verification/src/accept_header.rs @@ -1,18 +1,139 @@ +use std::cmp; +use std::collections::BTreeSet; +use network::Magic; +use db::{SharedStore, BlockHeaderProvider}; use canon::CanonHeader; +use constants::MIN_BLOCK_VERSION; use error::Error; +use utils::work_required; + +const EXPECT_CANON: &'static str = "Block ancestors expected to be found in canon chain"; pub struct HeaderAcceptor<'a> { - _tmp: CanonHeader<'a>, + pub version: HeaderVersion<'a>, + pub work: HeaderWork<'a>, + pub median_timestamp: HeaderMedianTimestamp<'a>, } impl<'a> HeaderAcceptor<'a> { - pub fn new(header: CanonHeader<'a>) -> Self { + pub fn new(store: &'a SharedStore, network: Magic, header: CanonHeader<'a>, height: u32) -> Self { HeaderAcceptor { - _tmp: header, + // TODO: check last 1000 blocks instead of hardcoding the value + version: HeaderVersion::new(header, MIN_BLOCK_VERSION), + work: HeaderWork::new(header, store.as_block_header_provider(), height, network), + median_timestamp: HeaderMedianTimestamp::new(header, store.as_block_header_provider(), height, network), } } pub fn check(&self) -> Result<(), Error> { + try!(self.version.check()); + try!(self.work.check()); + try!(self.median_timestamp.check()); Ok(()) } } + +pub trait HeaderRule { + fn check(&self) -> Result<(), Error>; +} + +pub struct HeaderVersion<'a> { + header: CanonHeader<'a>, + min_version: u32, +} + +impl<'a> HeaderVersion<'a> { + fn new(header: CanonHeader<'a>, min_version: u32) -> Self { + HeaderVersion { + header: header, + min_version: min_version, + } + } +} + +impl<'a> HeaderRule for HeaderVersion<'a> { + fn check(&self) -> Result<(), Error> { + if self.header.raw.version < self.min_version { + Err(Error::OldVersionBlock) + } else { + Ok(()) + } + } +} + +pub struct HeaderWork<'a> { + header: CanonHeader<'a>, + store: &'a BlockHeaderProvider, + height: u32, + network: Magic, +} + +impl<'a> HeaderWork<'a> { + fn new(header: CanonHeader<'a>, store: &'a BlockHeaderProvider, height: u32, network: Magic) -> Self { + HeaderWork { + header: header, + store: store, + height: height, + network: network, + } + } +} + +impl<'a> HeaderRule for HeaderWork<'a> { + fn check(&self) -> Result<(), Error> { + let previous_header_hash = self.header.raw.previous_header_hash.clone(); + let time = self.header.raw.time; + let work = work_required(previous_header_hash, time, self.height, self.store, self.network); + if work == self.header.raw.bits { + Ok(()) + } else { + Err(Error::Difficulty) + } + } +} + +pub struct HeaderMedianTimestamp<'a> { + header: CanonHeader<'a>, + store: &'a BlockHeaderProvider, + height: u32, + network: Magic, +} + +impl<'a> HeaderMedianTimestamp<'a> { + fn new(header: CanonHeader<'a>, store: &'a BlockHeaderProvider, height: u32, network: Magic) -> Self { + HeaderMedianTimestamp { + header: header, + store: store, + height: height, + network: network, + } + } +} + +impl<'a> HeaderRule for HeaderMedianTimestamp<'a> { + fn check(&self) -> Result<(), Error> { + // TODO: timestamp validation on testnet is broken + if self.height == 0 || self.network == Magic::Testnet { + return Ok(()); + } + + let ancestors = cmp::min(11, self.height); + let mut timestamps = BTreeSet::new(); + let mut block_ref = self.header.raw.previous_header_hash.clone().into(); + + for _ in 0..ancestors { + let previous_header = self.store.block_header(block_ref).expect(EXPECT_CANON); + timestamps.insert(previous_header.time); + block_ref = previous_header.previous_header_hash.into(); + } + + let timestamps = timestamps.into_iter().collect::>(); + let median = timestamps[timestamps.len() / 2]; + + if self.header.raw.time <= median { + Err(Error::Timestamp) + } else { + Ok(()) + } + } +} diff --git a/verification/src/constants.rs b/verification/src/constants.rs index 20d21795..a1c212e8 100644 --- a/verification/src/constants.rs +++ b/verification/src/constants.rs @@ -6,3 +6,4 @@ pub const MAX_BLOCK_SIZE: usize = 1_000_000; pub const MAX_BLOCK_SIGOPS: usize = 20_000; pub const MIN_COINBASE_SIZE: usize = 2; pub const MAX_COINBASE_SIZE: usize = 100; +pub const MIN_BLOCK_VERSION: u32 = 0; diff --git a/verification/src/error.rs b/verification/src/error.rs index ce573156..3bbfb1e6 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -34,6 +34,8 @@ pub enum Error { Size(usize), /// Block transactions are not final. NonFinalBlock, + /// Old version block. + OldVersionBlock, } #[derive(Debug, PartialEq)]