HeaderAcceptor finished
This commit is contained in:
parent
0df90a85bd
commit
95c2fa7d8d
|
@ -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::<usize>();
|
||||
|
||||
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::<u64>();
|
||||
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<Vec<_>>();
|
||||
let median = timestamps[timestamps.len() / 2];
|
||||
|
||||
if self.header.raw.time <= median {
|
||||
Err(Error::Timestamp)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -34,6 +34,8 @@ pub enum Error {
|
|||
Size(usize),
|
||||
/// Block transactions are not final.
|
||||
NonFinalBlock,
|
||||
/// Old version block.
|
||||
OldVersionBlock,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
|
Loading…
Reference in New Issue