Skip to content

Commit

Permalink
feat: past blocks median time based header timestamp verification
Browse files Browse the repository at this point in the history
  • Loading branch information
jjyr committed Dec 29, 2018
1 parent 41eadc5 commit c63d64b
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ fnv = "1.0.3"
env_logger = "0.6"
tempfile = "3.0"
rand = "0.6"
faketime = "0.2"
2 changes: 2 additions & 0 deletions shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ mod flat_serializer;
pub mod index;
pub mod shared;
pub mod store;
#[cfg(test)]
mod tests;

use ckb_db::batch::Col;

Expand Down
21 changes: 21 additions & 0 deletions shared/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ pub trait ChainProvider: Sync + Send {

fn get_transaction_meta_at(&self, hash: &H256, parent: &H256) -> Option<TransactionMeta>;

fn block_median_time(&self, hash: &H256) -> Option<u64>;

fn block_reward(&self, block_number: BlockNumber) -> Capacity;

fn get_ancestor(&self, base: &H256, number: BlockNumber) -> Option<Header>;
Expand Down Expand Up @@ -276,6 +278,25 @@ impl<CI: ChainIndex> ChainProvider for Shared<CI> {
.and_then(|ref root| self.store.get_transaction_meta(root, hash))
}

// get block median time from this hash
fn block_median_time(&self, hash: &H256) -> Option<u64> {
let count = self.consensus.median_time_block_count();
let mut block_times = Vec::with_capacity(count);
let mut current_header = self.block_header(hash);
for _ in 0..count {
let header = match current_header {
Some(ref header) => header,
None => break,
};
block_times.push(header.timestamp());
if block_times.len() < count {
current_header = self.block_header(header.parent_hash());
}
}
block_times.sort_by(|a, b| b.cmp(a));
block_times.get(block_times.len() / 2).cloned()
}

fn block_reward(&self, _block_number: BlockNumber) -> Capacity {
// TODO: block reward calculation algorithm
self.consensus.initial_block_reward()
Expand Down
1 change: 1 addition & 0 deletions shared/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod shared;
59 changes: 59 additions & 0 deletions shared/src/tests/shared.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::{
shared::{ChainProvider, Shared, SharedBuilder},
store::{ChainKVStore, ChainStore},
};
use ckb_core::{block::BlockBuilder, header::HeaderBuilder};
use ckb_db::{kvdb::KeyValueDB, memorydb::MemoryKeyValueDB};
use numext_fixed_hash::H256;

fn new_shared() -> Shared<ChainKVStore<MemoryKeyValueDB>> {
SharedBuilder::<ChainKVStore<MemoryKeyValueDB>>::new_memory().build()
}

fn insert_block_timestamps<T>(store: &ChainKVStore<T>, timestamps: &[u64]) -> Vec<H256>
where
T: KeyValueDB + 'static,
{
let mut blocks = Vec::with_capacity(timestamps.len());
let mut hashes = Vec::with_capacity(timestamps.len());
let mut parent_hash = H256::zero();
for timestamp in timestamps {
let header = HeaderBuilder::default()
.timestamp(*timestamp)
.parent_hash(parent_hash.clone())
.build();
parent_hash = header.hash();
hashes.push(parent_hash.clone());
blocks.push(BlockBuilder::default().header(header).build());
}
store
.save_with_batch(|batch| {
for b in blocks {
store.insert_block(batch, &b);
}
Ok(())
})
.expect("insert blocks");
hashes
}

#[test]
fn test_block_median_time() {
let shared = new_shared();
assert!(shared.block_median_time(&H256::zero()).is_none());
let now = faketime::unix_time_as_millis();
let block_hashes = insert_block_timestamps(shared.store(), &vec![now]);
assert_eq!(
shared
.block_median_time(&block_hashes[0])
.expect("median time"),
now
);
let block_hashes = insert_block_timestamps(shared.store(), &(0..=22).collect::<Vec<_>>());
assert_eq!(
shared
.block_median_time(&block_hashes.last().expect("last"))
.expect("median time"),
17
);
}
8 changes: 8 additions & 0 deletions spec/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub const MAX_UNCLE_AGE: usize = 6;
pub const TRANSACTION_PROPAGATION_TIME: BlockNumber = 1;
pub const TRANSACTION_PROPAGATION_TIMEOUT: BlockNumber = 10;
pub const CELLBASE_MATURITY: usize = 100;
// TODO: should consider with block time
pub const MEDIAN_TIME_BLOCK_COUNT: usize = 11;

//TODO:find best ORPHAN_RATE_TARGET
pub const ORPHAN_RATE_TARGET: f32 = 0.1;
Expand All @@ -36,6 +38,7 @@ pub struct Consensus {
// it must have at least `cellbase_maturity` confirmations;
// else reject this transaction.
pub cellbase_maturity: usize,
pub median_time_block_count: usize,
}

// genesis difficulty should not be zero
Expand All @@ -58,6 +61,7 @@ impl Default for Consensus {
pow: Pow::Dummy,
verification: true,
cellbase_maturity: CELLBASE_MATURITY,
median_time_block_count: MEDIAN_TIME_BLOCK_COUNT,
}
}
}
Expand Down Expand Up @@ -123,4 +127,8 @@ impl Consensus {
pub fn cellbase_maturity(&self) -> usize {
self.cellbase_maturity
}

pub fn median_time_block_count(&self) -> usize {
self.median_time_block_count
}
}
6 changes: 4 additions & 2 deletions sync/src/relayer/compact_block_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ where
{
let resolver =
HeaderResolverWrapper::new(&compact_block.header, self.relayer.shared.clone());
let header_verifier =
HeaderVerifier::new(Arc::clone(&self.relayer.shared.consensus().pow_engine()));
let header_verifier = HeaderVerifier::new(
self.relayer.shared.clone(),
Arc::clone(&self.relayer.shared.consensus().pow_engine()),
);

if header_verifier.verify(&resolver).is_ok() {
self.relayer
Expand Down
14 changes: 8 additions & 6 deletions sync/src/synchronizer/headers_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,10 @@ where
pub fn accept_first(&self, first: &Header) -> ValidationResult {
let parent = self.synchronizer.get_header(&first.parent_hash());
let resolver = VerifierResolver::new(parent.as_ref(), &first, &self.synchronizer);
let verifier = HeaderVerifier::new(Arc::clone(
&self.synchronizer.shared.consensus().pow_engine(),
));
let verifier = HeaderVerifier::new(
self.synchronizer.shared.clone(),
Arc::clone(&self.synchronizer.shared.consensus().pow_engine()),
);
let acceptor =
HeaderAcceptor::new(first, self.peer, &self.synchronizer, resolver, verifier);
acceptor.accept()
Expand Down Expand Up @@ -196,9 +197,10 @@ where
for window in headers.windows(2) {
if let [parent, header] = &window {
let resolver = VerifierResolver::new(Some(&parent), &header, &self.synchronizer);
let verifier = HeaderVerifier::new(Arc::clone(
&self.synchronizer.shared.consensus().pow_engine(),
));
let verifier = HeaderVerifier::new(
self.synchronizer.shared.clone(),
Arc::clone(&self.synchronizer.shared.consensus().pow_engine()),
);
let acceptor =
HeaderAcceptor::new(&header, self.peer, &self.synchronizer, resolver, verifier);
let result = acceptor.accept();
Expand Down
4 changes: 2 additions & 2 deletions verification/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ pub enum PowError {

#[derive(Debug, PartialEq, Clone, Copy, Eq)]
pub enum TimestampError {
ZeroBlockTime { min: u64, found: u64 },
FutureBlockTime { max: u64, found: u64 },
BlockTimeTooEarly { min: u64, found: u64 },
BlockTimeTooNew { max: u64, found: u64 },
}

#[derive(Debug, PartialEq, Clone, Copy, Eq)]
Expand Down
37 changes: 23 additions & 14 deletions verification/src/header_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::error::{DifficultyError, Error, NumberError, PowError, TimestampError
use crate::shared::ALLOWED_FUTURE_BLOCKTIME;
use ckb_core::header::Header;
use ckb_pow::PowEngine;
use ckb_shared::shared::ChainProvider;
use faketime::unix_time_as_millis;
use numext_fixed_uint::U256;
use std::marker::PhantomData;
Expand All @@ -16,21 +17,23 @@ pub trait HeaderResolver {
fn calculate_difficulty(&self) -> Option<U256>;
}

pub struct HeaderVerifier<T> {
pub struct HeaderVerifier<T, P> {
pub pow: Arc<dyn PowEngine>,
chain_provider: P,
_phantom: PhantomData<T>,
}

impl<T> HeaderVerifier<T> {
pub fn new(pow: Arc<dyn PowEngine>) -> Self {
impl<T, P: ChainProvider + Clone> HeaderVerifier<T, P> {
pub fn new(chain_provider: P, pow: Arc<dyn PowEngine>) -> Self {
HeaderVerifier {
pow,
chain_provider,
_phantom: PhantomData,
}
}
}

impl<T: HeaderResolver> Verifier for HeaderVerifier<T> {
impl<T: HeaderResolver, P: ChainProvider + Clone> Verifier for HeaderVerifier<T, P> {
type Target = T;
fn verify(&self, target: &T) -> Result<(), Error> {
let header = target.header();
Expand All @@ -41,38 +44,44 @@ impl<T: HeaderResolver> Verifier for HeaderVerifier<T> {
.parent()
.ok_or_else(|| Error::UnknownParent(header.parent_hash().clone()))?;
NumberVerifier::new(parent, header).verify()?;
TimestampVerifier::new(parent, header).verify()?;
TimestampVerifier::new(self.chain_provider.clone(), header).verify()?;
DifficultyVerifier::verify(target)?;
Ok(())
}
}

pub struct TimestampVerifier<'a> {
parent: &'a Header,
pub struct TimestampVerifier<'a, P> {
header: &'a Header,
chain_provider: P,
now: u64,
}

impl<'a> TimestampVerifier<'a> {
pub fn new(parent: &'a Header, header: &'a Header) -> Self {
impl<'a, P: ChainProvider> TimestampVerifier<'a, P> {
pub fn new(chain_provider: P, header: &'a Header) -> Self {
TimestampVerifier {
parent,
chain_provider,
header,
now: unix_time_as_millis(),
}
}

pub fn verify(&self) -> Result<(), Error> {
let min = self.parent.timestamp() + 1;
if self.header.timestamp() < min {
return Err(Error::Timestamp(TimestampError::ZeroBlockTime {
let min = match self
.chain_provider
.block_median_time(self.header.parent_hash())
{
Some(time) => time,
None => return Err(Error::UnknownParent(self.header.parent_hash().clone())),
};
if self.header.timestamp() <= min {
return Err(Error::Timestamp(TimestampError::BlockTimeTooEarly {
min,
found: self.header.timestamp(),
}));
}
let max = self.now + ALLOWED_FUTURE_BLOCKTIME;
if self.header.timestamp() > max {
return Err(Error::Timestamp(TimestampError::FutureBlockTime {
return Err(Error::Timestamp(TimestampError::BlockTimeTooNew {
max,
found: self.header.timestamp(),
}));
Expand Down
4 changes: 4 additions & 0 deletions verification/src/tests/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ impl ChainProvider for DummyChainProvider {
panic!("Not implemented!");
}

fn block_median_time(&self, _hash: &H256) -> Option<u64> {
panic!("Not implemented!");
}

fn calculate_difficulty(&self, _last: &Header) -> Option<U256> {
panic!("Not implemented!");
}
Expand Down

0 comments on commit c63d64b

Please sign in to comment.