From c63d64b4d268eaf9c07b336254e6296357794df4 Mon Sep 17 00:00:00 2001 From: jjy Date: Sat, 29 Dec 2018 15:14:26 +0800 Subject: [PATCH] feat: past blocks median time based header timestamp verification --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/lib.rs | 2 + shared/src/shared.rs | 21 ++++++++ shared/src/tests/mod.rs | 1 + shared/src/tests/shared.rs | 59 +++++++++++++++++++++++ spec/src/consensus.rs | 8 +++ sync/src/relayer/compact_block_process.rs | 6 ++- sync/src/synchronizer/headers_process.rs | 14 +++--- verification/src/error.rs | 4 +- verification/src/header_verifier.rs | 37 ++++++++------ verification/src/tests/dummy.rs | 4 ++ 12 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 shared/src/tests/mod.rs create mode 100644 shared/src/tests/shared.rs diff --git a/Cargo.lock b/Cargo.lock index 4748d84595..0b5686beb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,6 +584,7 @@ dependencies = [ "ckb-db 0.3.0-pre", "ckb-util 0.3.0-pre", "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "faketime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "lru-cache 0.1.0 (git+https://github.com/nervosnetwork/lru-cache)", "numext-fixed-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index a7264ad65a..4461ac4c3d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -22,3 +22,4 @@ fnv = "1.0.3" env_logger = "0.6" tempfile = "3.0" rand = "0.6" +faketime = "0.2" diff --git a/shared/src/lib.rs b/shared/src/lib.rs index e6e4df919e..89d64e3b4b 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -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; diff --git a/shared/src/shared.rs b/shared/src/shared.rs index 029c5492c0..f948dde97c 100644 --- a/shared/src/shared.rs +++ b/shared/src/shared.rs @@ -203,6 +203,8 @@ pub trait ChainProvider: Sync + Send { fn get_transaction_meta_at(&self, hash: &H256, parent: &H256) -> Option; + fn block_median_time(&self, hash: &H256) -> Option; + fn block_reward(&self, block_number: BlockNumber) -> Capacity; fn get_ancestor(&self, base: &H256, number: BlockNumber) -> Option
; @@ -276,6 +278,25 @@ impl ChainProvider for Shared { .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 { + 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() diff --git a/shared/src/tests/mod.rs b/shared/src/tests/mod.rs new file mode 100644 index 0000000000..33a50af6cf --- /dev/null +++ b/shared/src/tests/mod.rs @@ -0,0 +1 @@ +mod shared; diff --git a/shared/src/tests/shared.rs b/shared/src/tests/shared.rs new file mode 100644 index 0000000000..886b5257c1 --- /dev/null +++ b/shared/src/tests/shared.rs @@ -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> { + SharedBuilder::>::new_memory().build() +} + +fn insert_block_timestamps(store: &ChainKVStore, timestamps: &[u64]) -> Vec +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::>()); + assert_eq!( + shared + .block_median_time(&block_hashes.last().expect("last")) + .expect("median time"), + 17 + ); +} diff --git a/spec/src/consensus.rs b/spec/src/consensus.rs index 090d0cc4db..572634d2fd 100644 --- a/spec/src/consensus.rs +++ b/spec/src/consensus.rs @@ -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; @@ -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 @@ -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, } } } @@ -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 + } } diff --git a/sync/src/relayer/compact_block_process.rs b/sync/src/relayer/compact_block_process.rs index 4e3d7e4959..8ae8acea70 100644 --- a/sync/src/relayer/compact_block_process.rs +++ b/sync/src/relayer/compact_block_process.rs @@ -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 diff --git a/sync/src/synchronizer/headers_process.rs b/sync/src/synchronizer/headers_process.rs index 74d44b0885..61fee05b98 100644 --- a/sync/src/synchronizer/headers_process.rs +++ b/sync/src/synchronizer/headers_process.rs @@ -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() @@ -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(); diff --git a/verification/src/error.rs b/verification/src/error.rs index f27a149f28..e9791091db 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -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)] diff --git a/verification/src/header_verifier.rs b/verification/src/header_verifier.rs index 2b3f0db96b..2b57ea9452 100644 --- a/verification/src/header_verifier.rs +++ b/verification/src/header_verifier.rs @@ -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; @@ -16,21 +17,23 @@ pub trait HeaderResolver { fn calculate_difficulty(&self) -> Option; } -pub struct HeaderVerifier { +pub struct HeaderVerifier { pub pow: Arc, + chain_provider: P, _phantom: PhantomData, } -impl HeaderVerifier { - pub fn new(pow: Arc) -> Self { +impl HeaderVerifier { + pub fn new(chain_provider: P, pow: Arc) -> Self { HeaderVerifier { pow, + chain_provider, _phantom: PhantomData, } } } -impl Verifier for HeaderVerifier { +impl Verifier for HeaderVerifier { type Target = T; fn verify(&self, target: &T) -> Result<(), Error> { let header = target.header(); @@ -41,38 +44,44 @@ impl Verifier for HeaderVerifier { .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(), })); diff --git a/verification/src/tests/dummy.rs b/verification/src/tests/dummy.rs index c633fe464a..20b7caa1a6 100644 --- a/verification/src/tests/dummy.rs +++ b/verification/src/tests/dummy.rs @@ -94,6 +94,10 @@ impl ChainProvider for DummyChainProvider { panic!("Not implemented!"); } + fn block_median_time(&self, _hash: &H256) -> Option { + panic!("Not implemented!"); + } + fn calculate_difficulty(&self, _last: &Header) -> Option { panic!("Not implemented!"); }