HeaderAcceptor finished
This commit is contained in:
parent
0df90a85bd
commit
95c2fa7d8d
|
@ -1,18 +1,17 @@
|
||||||
use network::{Magic, ConsensusParams};
|
use network::{Magic, ConsensusParams};
|
||||||
use db::{SharedStore, PreviousTransactionOutputProvider, BlockHeaderProvider};
|
use db::{SharedStore, PreviousTransactionOutputProvider};
|
||||||
use sigops::{StoreWithUnretainedOutputs, transaction_sigops};
|
use sigops::{StoreWithUnretainedOutputs, transaction_sigops};
|
||||||
use utils::{work_required, block_reward_satoshi};
|
use utils::block_reward_satoshi;
|
||||||
use canon::CanonBlock;
|
use canon::CanonBlock;
|
||||||
use constants::MAX_BLOCK_SIGOPS;
|
use constants::MAX_BLOCK_SIGOPS;
|
||||||
use error::Error;
|
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
|
/// Flexible verification of ordered block
|
||||||
pub struct BlockAcceptor<'a> {
|
pub struct BlockAcceptor<'a> {
|
||||||
pub finality: BlockFinality<'a>,
|
pub finality: BlockFinality<'a>,
|
||||||
pub sigops: BlockSigops<'a>,
|
pub sigops: BlockSigops<'a>,
|
||||||
pub work: BlockWork<'a>,
|
|
||||||
pub coinbase_claim: BlockCoinbaseClaim<'a>,
|
pub coinbase_claim: BlockCoinbaseClaim<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +21,6 @@ impl<'a> BlockAcceptor<'a> {
|
||||||
BlockAcceptor {
|
BlockAcceptor {
|
||||||
finality: BlockFinality::new(block, height),
|
finality: BlockFinality::new(block, height),
|
||||||
sigops: BlockSigops::new(block, store.as_previous_transaction_output_provider(), params, MAX_BLOCK_SIGOPS),
|
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),
|
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> {
|
pub fn check(&self) -> Result<(), Error> {
|
||||||
try!(self.finality.check());
|
try!(self.finality.check());
|
||||||
try!(self.sigops.check());
|
try!(self.sigops.check());
|
||||||
try!(self.work.check());
|
|
||||||
try!(self.coinbase_claim.check());
|
try!(self.coinbase_claim.check());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -88,7 +85,7 @@ impl<'a> BlockRule for BlockSigops<'a> {
|
||||||
let store = StoreWithUnretainedOutputs::new(self.store, &*self.block);
|
let store = StoreWithUnretainedOutputs::new(self.store, &*self.block);
|
||||||
let bip16_active = self.block.header.raw.time >= self.consensus_params.bip16_time;
|
let bip16_active = self.block.header.raw.time >= self.consensus_params.bip16_time;
|
||||||
let sigops = self.block.transactions.iter()
|
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>();
|
.sum::<usize>();
|
||||||
|
|
||||||
if sigops > self.max_sigops {
|
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> {
|
pub struct BlockCoinbaseClaim<'a> {
|
||||||
block: CanonBlock<'a>,
|
block: CanonBlock<'a>,
|
||||||
store: &'a PreviousTransactionOutputProvider,
|
store: &'a PreviousTransactionOutputProvider,
|
||||||
|
@ -152,7 +118,7 @@ impl<'a> BlockRule for BlockCoinbaseClaim<'a> {
|
||||||
let total_outputs = self.block.transactions.iter()
|
let total_outputs = self.block.transactions.iter()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.flat_map(|tx| tx.raw.inputs.iter())
|
.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)
|
.map(|output| output.value)
|
||||||
.sum::<u64>();
|
.sum::<u64>();
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl<'a> ChainAcceptor<'a> {
|
||||||
pub fn new(store: &'a SharedStore, network: Magic, block: CanonBlock<'a>, height: u32) -> Self {
|
pub fn new(store: &'a SharedStore, network: Magic, block: CanonBlock<'a>, height: u32) -> Self {
|
||||||
ChainAcceptor {
|
ChainAcceptor {
|
||||||
block: BlockAcceptor::new(store, network, block, height),
|
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(),
|
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 canon::CanonHeader;
|
||||||
|
use constants::MIN_BLOCK_VERSION;
|
||||||
use error::Error;
|
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> {
|
pub struct HeaderAcceptor<'a> {
|
||||||
_tmp: CanonHeader<'a>,
|
pub version: HeaderVersion<'a>,
|
||||||
|
pub work: HeaderWork<'a>,
|
||||||
|
pub median_timestamp: HeaderMedianTimestamp<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> HeaderAcceptor<'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 {
|
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> {
|
pub fn check(&self) -> Result<(), Error> {
|
||||||
|
try!(self.version.check());
|
||||||
|
try!(self.work.check());
|
||||||
|
try!(self.median_timestamp.check());
|
||||||
Ok(())
|
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 MAX_BLOCK_SIGOPS: usize = 20_000;
|
||||||
pub const MIN_COINBASE_SIZE: usize = 2;
|
pub const MIN_COINBASE_SIZE: usize = 2;
|
||||||
pub const MAX_COINBASE_SIZE: usize = 100;
|
pub const MAX_COINBASE_SIZE: usize = 100;
|
||||||
|
pub const MIN_BLOCK_VERSION: u32 = 0;
|
||||||
|
|
|
@ -34,6 +34,8 @@ pub enum Error {
|
||||||
Size(usize),
|
Size(usize),
|
||||||
/// Block transactions are not final.
|
/// Block transactions are not final.
|
||||||
NonFinalBlock,
|
NonFinalBlock,
|
||||||
|
/// Old version block.
|
||||||
|
OldVersionBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
|
Loading…
Reference in New Issue