From 34a582b8e9dd5ac3eee2097be5159b814627efbe Mon Sep 17 00:00:00 2001 From: NikVolf Date: Tue, 22 Nov 2016 18:26:00 +0300 Subject: [PATCH 01/14] expected nbits method & support --- db/src/block_provider.rs | 3 +++ db/src/storage.rs | 16 +++++++++++++++- db/src/test_storage.rs | 13 +++++++++++++ verification/src/chain_verifier.rs | 22 ++++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/db/src/block_provider.rs b/db/src/block_provider.rs index 3afb6bc3..f455ef12 100644 --- a/db/src/block_provider.rs +++ b/db/src/block_provider.rs @@ -14,6 +14,9 @@ pub trait BlockProvider { /// resolves header bytes by block reference (number/hash) fn block_header_bytes(&self, block_ref: BlockRef) -> Option; + /// resolves header bytes by block reference (number/hash) + fn block_header(&self, block_ref: BlockRef) -> Option; + /// resolves deserialized block body by block reference (number/hash) fn block(&self, block_ref: BlockRef) -> Option; diff --git a/db/src/storage.rs b/db/src/storage.rs index bf749d8f..43858f11 100644 --- a/db/src/storage.rs +++ b/db/src/storage.rs @@ -40,6 +40,9 @@ const MAX_FORK_ROUTE_PRESET: usize = 128; pub trait Store : BlockProvider + BlockStapler + TransactionProvider + TransactionMetaProvider { /// get best block fn best_block(&self) -> Option; + + /// get best header + fn best_header(&self) -> Option; } /// Blockchain storage with rocksdb database @@ -165,7 +168,6 @@ impl Storage { }) } - /// update transactions metadata in the specified database transaction fn update_transactions_meta(&self, context: &mut UpdateContext, number: u32, accepted_txs: &[chain::Transaction]) -> Result<(), Error> @@ -385,6 +387,12 @@ impl BlockProvider for Storage { self.resolve_hash(block_ref).and_then(|h| self.get(COL_BLOCK_HEADERS, &*h)) } + fn block_header(&self, block_ref: BlockRef) -> Option { + self.block_header_bytes(block_ref).map( + |bytes| deserialize::<_, chain::BlockHeader>(bytes.as_ref()) + .expect("Error deserializing header, possible db corruption")) + } + fn block_transaction_hashes(&self, block_ref: BlockRef) -> Vec { self.resolve_hash(block_ref) .map(|h| self.block_transaction_hashes_by_hash(&h)) @@ -596,6 +604,12 @@ impl Store for Storage { fn best_block(&self) -> Option { self.best_block.read().clone() } + + fn best_header(&self) -> Option { + self.best_block.read().as_ref().and_then( + |bb| Some(self.block_header_by_hash(&bb.hash).expect("Best block exists but no such header. Race condition?")), + ) + } } #[cfg(test)] diff --git a/db/src/test_storage.rs b/db/src/test_storage.rs index 2c2bd514..b138e698 100644 --- a/db/src/test_storage.rs +++ b/db/src/test_storage.rs @@ -81,6 +81,13 @@ impl BlockProvider for TestStorage { .map(|ref block| serialization::serialize(block.header())) } + fn block_header(&self, block_ref: BlockRef) -> Option { + let data = self.data.read(); + self.resolve_hash(block_ref) + .and_then(|ref h| data.blocks.get(h)) + .map(|ref block| block.header().clone()) + } + fn block_transaction_hashes(&self, block_ref: BlockRef) -> Vec { let data = self.data.read(); self.resolve_hash(block_ref) @@ -174,5 +181,11 @@ impl Store for TestStorage { fn best_block(&self) -> Option { self.data.read().best_block.clone() } + + fn best_header(&self) -> Option { + self.data.read().best_block.as_ref().and_then( + |bb| Some(self.block_header(BlockRef::Hash(bb.hash.clone())).expect("Best block exists but no such header. Race condition?")) + ) + } } diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 25fb7bc6..3d1b722f 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -54,6 +54,12 @@ impl ChainVerifier { } fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> { + // check that difficulty matches the adjusted level + if let Some(expected_nbits) = self.expected_nbits() { + if !self.skip_pow && expected_nbits != block.header().nbits { + return Err(Error::Difficulty); + } + } let coinbase_spends = block.transactions()[0].total_spends(); @@ -258,6 +264,22 @@ impl ChainVerifier { }, } } + + fn expected_nbits(&self) -> Option { + + let best_header = match self.store.best_header() { + Some(bb) => bb, + None => { return None; } + }; + + if self.store.best_block().expect("At least genesis should exist at this point").number < 2016 { + return Some(best_header.nbits); + } + + // todo: calculate difficulty adjustment + + Some(best_header.nbits) + } } impl Verify for ChainVerifier { From 7f5e5fb0367030da49b0b01d5515847266a9e43f Mon Sep 17 00:00:00 2001 From: NikVolf Date: Wed, 23 Nov 2016 00:13:51 +0300 Subject: [PATCH 02/14] threshold to nbits --- verification/src/utils.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/verification/src/utils.rs b/verification/src/utils.rs index 484673a6..bf2b930f 100644 --- a/verification/src/utils.rs +++ b/verification/src/utils.rs @@ -1,11 +1,13 @@ //! Verification utilities -use primitives::hash::H256; +use primitives::{H256, U256}; +use primitives::uint::Uint; use byteorder::{BigEndian, ByteOrder}; use chain; use script::{self, Script}; const MAX_NBITS: u32 = 0x207fffff; +/// Simple nbits check that does not require 256-bit arithmetic pub fn check_nbits(hash: &H256, n_bits: u32) -> bool { if n_bits > MAX_NBITS { return false; } @@ -82,11 +84,28 @@ pub fn p2sh_sigops(output: &Script, input_ref: &Script) -> usize { output.sigop_count_p2sh(input_ref).unwrap_or(0) } +/// Converts difficulty threshold to the compact representation (nbits) +pub fn threshold_to_nbits(val: U256) -> u32 { + let mut nb = [0u8; 4]; + let bits = val.bits() as u8; + nb[0] = (bits + 7) / 8; + if val.byte(nb[0] as usize - 1) > 0x7f { nb[0] += 1 } + + nb[1] = val.byte((nb[0]-1) as usize); + nb[2] = val.byte((nb[0]-2) as usize); + if nb[0] > 2 { + nb[3] = val.byte((nb[0]-3) as usize); + } + + BigEndian::read_u32(&nb) +} + #[cfg(test)] mod tests { - use super::{block_reward_satoshi, check_nbits}; - use primitives::hash::H256; + use super::{block_reward_satoshi, check_nbits, threshold_to_nbits}; + use primitives::{H256, U256}; + use primitives::uint::Uint; #[test] fn reward() { @@ -127,4 +146,13 @@ mod tests { let nbits = 404129525; assert!(check_nbits(&hash, nbits)); } + + #[test] + fn threshold() { + let test1 = U256::from(1000u64); + assert_eq!(0x0203e800, threshold_to_nbits(test1)); + + let test2 = U256::from(2).pow(U256::from(256-32))-U256::from(1); + assert_eq!(0x1d00ffff, threshold_to_nbits(test2)); + } } From 961e4361cf88994a34da0acb9211b514a478436c Mon Sep 17 00:00:00 2001 From: debris Date: Wed, 23 Nov 2016 04:10:11 +0100 Subject: [PATCH 03/14] pow validation in progress --- Cargo.lock | 1 + chain/src/block_header.rs | 40 ++++++++++++++++++++++ chain/src/lib.rs | 3 +- chain/src/nbits.rs | 55 ++++++++++++++++++++++++++++++ primitives/Cargo.toml | 1 + primitives/src/hash.rs | 9 +++++ primitives/src/lib.rs | 3 +- primitives/src/uint.rs | 23 +++++++++++++ verification/src/chain_verifier.rs | 3 +- 9 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 chain/src/nbits.rs diff --git a/Cargo.lock b/Cargo.lock index 993b20fd..a64f8600 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,6 +493,7 @@ dependencies = [ name = "primitives" version = "0.1.0" dependencies = [ + "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/chain/src/block_header.rs b/chain/src/block_header.rs index 64aaa07a..98cc44ac 100644 --- a/chain/src/block_header.rs +++ b/chain/src/block_header.rs @@ -5,6 +5,8 @@ use ser::{ }; use crypto::dhash256; use hash::H256; +use uint::U256; +use nbits::{NBits, MAX_NBITS_MAINNET, MAX_NBITS_REGTEST}; #[derive(PartialEq, Clone)] pub struct BlockHeader { @@ -20,6 +22,36 @@ impl BlockHeader { pub fn hash(&self) -> H256 { dhash256(&serialize(self)) } + + /// Returns the total work of the block + //pub fn work(&self) -> U256 { + //// 2**256 / (target + 1) == ~target / (target+1) + 1 (eqn shamelessly stolen from bitcoind) + //let mut ret = !self.target(); + //let mut ret1 = self.target(); + //ret1 = ret1 + 1.into(); + //ret = ret / ret1; + //ret = ret + 1.into(); + //ret + //} + + pub fn is_valid_proof_of_work(&self) -> bool { + let max = match NBits::new(MAX_NBITS_REGTEST).target() { + Some(max) => max, + None => return false, + }; + + let target = match NBits::new(self.nbits).target() { + Some(target) => target, + None => return false, + }; + + if target > max { + return false; + } + + let target = H256::from(target.to_little_endian()); + self.hash() <= target + } } impl fmt::Debug for BlockHeader { @@ -66,6 +98,7 @@ impl Deserializable for BlockHeader { mod tests { use ser::{Reader, Error as ReaderError, Stream}; use super::BlockHeader; + use nbits::MAX_NBITS_REGTEST; #[test] fn test_block_header_stream() { @@ -118,4 +151,11 @@ mod tests { assert_eq!(expected, reader.read().unwrap()); assert_eq!(ReaderError::UnexpectedEnd, reader.read::().unwrap_err()); } + + #[test] + fn test_is_valid_proof_of_work() { + let mut header = BlockHeader::default(); + header.nbits = MAX_NBITS_REGTEST; + assert!(header.is_valid_proof_of_work()); + } } diff --git a/chain/src/lib.rs b/chain/src/lib.rs index d0bd9799..cc14aba3 100644 --- a/chain/src/lib.rs +++ b/chain/src/lib.rs @@ -7,6 +7,7 @@ extern crate serialization as ser; mod block; mod block_header; mod merkle_root; +mod nbits; mod transaction; pub trait RepresentH256 { @@ -14,7 +15,7 @@ pub trait RepresentH256 { } pub use rustc_serialize::hex; -pub use primitives::{hash, bytes}; +pub use primitives::{hash, bytes, uint}; pub use self::block::Block; pub use self::block_header::BlockHeader; diff --git a/chain/src/nbits.rs b/chain/src/nbits.rs new file mode 100644 index 00000000..14ff7580 --- /dev/null +++ b/chain/src/nbits.rs @@ -0,0 +1,55 @@ +use uint::U256; + +pub const MAX_NBITS_MAINNET: u32 = 0x1d00ffff; +pub const MAX_NBITS_TESTNET: u32 = 0x1d00ffff; +pub const MAX_NBITS_REGTEST: u32 = 0x207fffff; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct NBits(u32); + +impl NBits { + pub fn new(u: u32) -> Self { + NBits(u) + } + + /// Computes the target [0, T] that a blockhash must land in to be valid + /// Returns None, if there is an overflow or its negative value + pub fn target(&self) -> Option { + let size = self.0 >> 24; + let mut word = self.0 & 0x007fffff; + + let result = if size <= 3 { + word = word >> (8 * (3 - size as usize)); + word.into() + } else { + U256::from(word) << (8 * (size as usize - 3)) + }; + + let is_negative = word != 0 && (self.0 & 0x00800000) != 0; + let is_overflow = (word != 0 && size > 34) || + (word > 0xff && size > 33) || + (word > 0xffff && size > 32); + + if is_negative || is_overflow { + None + } else { + Some(result) + } + } +} + +#[cfg(test)] +mod tests { + use super::NBits; + + #[test] + fn test_basic_nbits_target() { + assert_eq!(NBits::new(0x01003456).target(), Some(0.into())); + assert_eq!(NBits::new(0x01123456).target(), Some(0x12.into())); + assert_eq!(NBits::new(0x02008000).target(), Some(0x80.into())); + assert_eq!(NBits::new(0x05009234).target(), Some(0x92340000u64.into())); + // negative -0x12345600 + assert_eq!(NBits::new(0x04923456).target(), None); + assert_eq!(NBits::new(0x04123456).target(), Some(0x12345600u64.into())); + } +} diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index faf0e094..c8f33616 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -6,6 +6,7 @@ build = "build.rs" [dependencies] heapsize = "0.3" +byteorder = "0.5" rustc-serialize = "0.3" [build-dependencies] diff --git a/primitives/src/hash.rs b/primitives/src/hash.rs index 5a31d685..17999dd2 100644 --- a/primitives/src/hash.rs +++ b/primitives/src/hash.rs @@ -105,6 +105,15 @@ macro_rules! impl_hash { } } + impl cmp::PartialOrd for $name { + fn partial_cmp(&self, other: &Self) -> Option { + let self_ref: &[u8] = &self.0; + let other_ref: &[u8] = &other.0; + self_ref.partial_cmp(other_ref) + } + } + + impl Hash for $name { fn hash(&self, state: &mut H) where H: Hasher { state.write(&self.0); diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index eafddb14..c119fe84 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -1,7 +1,8 @@ #![cfg_attr(asm_available, feature(asm))] -extern crate rustc_serialize; +extern crate byteorder; #[macro_use] extern crate heapsize; +extern crate rustc_serialize; pub mod bytes; pub mod hash; diff --git a/primitives/src/uint.rs b/primitives/src/uint.rs index 9ae77bc8..ea0730f5 100644 --- a/primitives/src/uint.rs +++ b/primitives/src/uint.rs @@ -8,6 +8,7 @@ use std::{str, fmt}; use std::ops::{Shr, Shl, BitAnd, BitOr, BitXor, Not, Div, Rem, Mul, Add, Sub}; use std::cmp::Ordering; +use byteorder::{WriteBytesExt, LittleEndian, BigEndian}; use hex::{FromHex, FromHexError}; /// Conversion from decimal string error @@ -393,6 +394,28 @@ macro_rules! construct_uint { Ok(res) } + pub fn to_little_endian(&self) -> [u8; $n_words * 8] { + let mut result = [0u8; $n_words * 8]; + { + let mut result_ref: &mut [u8] = &mut result; + for word in self.0.into_iter() { + result_ref.write_u64::(*word).expect("sizeof($n_words * u8 * 8) == sizeof($n_words * u64); qed"); + } + } + result + } + + pub fn to_big_endian(&self) -> [u8; $n_words * 8] { + let mut result = [0u8; $n_words * 8]; + { + let mut result_ref: &mut [u8] = &mut result; + for word in self.0.into_iter().rev() { + result_ref.write_u64::(*word).expect("sizeof($n_words * u8 * 8) == sizeof($n_words * u64); qed"); + } + } + result + } + #[inline] pub fn low_u32(&self) -> u32 { let &$name(ref arr) = self; diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 25fb7bc6..d423ae82 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -194,7 +194,8 @@ impl ChainVerifier { } // target difficulty threshold - if !self.skip_pow && !utils::check_nbits(&hash, block.header().nbits) { + //if !self.skip_pow && !utils::check_nbits(&hash, block.header().nbits) { + if !self.skip_pow && !block.header().is_valid_proof_of_work() { return Err(Error::Pow); } From 355306e747f284ffc92f1a5c230b8911b598787e Mon Sep 17 00:00:00 2001 From: debris Date: Thu, 24 Nov 2016 23:33:51 +0100 Subject: [PATCH 04/14] retarget --- Cargo.lock | 1 + chain/Cargo.toml | 1 + chain/src/block_header.rs | 40 ----------- chain/src/lib.rs | 1 - chain/src/nbits.rs | 55 --------------- db/src/lib.rs | 12 ++++ p2p/src/net/connection_counter.rs | 5 -- p2p/src/util/nonce.rs | 14 ---- verification/src/chain_verifier.rs | 45 ++++++++----- verification/src/compact.rs | 105 +++++++++++++++++++++++++++++ verification/src/lib.rs | 5 +- verification/src/utils.rs | 88 +++++++++++++++--------- 12 files changed, 209 insertions(+), 163 deletions(-) delete mode 100644 chain/src/nbits.rs create mode 100644 verification/src/compact.rs diff --git a/Cargo.lock b/Cargo.lock index a64f8600..4832cc5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,7 @@ name = "chain" version = "0.1.0" dependencies = [ "bitcrypto 0.1.0", + "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.1.0", "rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/chain/Cargo.toml b/chain/Cargo.toml index ca16d59f..485af24e 100644 --- a/chain/Cargo.toml +++ b/chain/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["debris "] [dependencies] +byteorder = "0.5" rustc-serialize = "0.3" heapsize = "0.3" bitcrypto = { path = "../crypto" } diff --git a/chain/src/block_header.rs b/chain/src/block_header.rs index 98cc44ac..64aaa07a 100644 --- a/chain/src/block_header.rs +++ b/chain/src/block_header.rs @@ -5,8 +5,6 @@ use ser::{ }; use crypto::dhash256; use hash::H256; -use uint::U256; -use nbits::{NBits, MAX_NBITS_MAINNET, MAX_NBITS_REGTEST}; #[derive(PartialEq, Clone)] pub struct BlockHeader { @@ -22,36 +20,6 @@ impl BlockHeader { pub fn hash(&self) -> H256 { dhash256(&serialize(self)) } - - /// Returns the total work of the block - //pub fn work(&self) -> U256 { - //// 2**256 / (target + 1) == ~target / (target+1) + 1 (eqn shamelessly stolen from bitcoind) - //let mut ret = !self.target(); - //let mut ret1 = self.target(); - //ret1 = ret1 + 1.into(); - //ret = ret / ret1; - //ret = ret + 1.into(); - //ret - //} - - pub fn is_valid_proof_of_work(&self) -> bool { - let max = match NBits::new(MAX_NBITS_REGTEST).target() { - Some(max) => max, - None => return false, - }; - - let target = match NBits::new(self.nbits).target() { - Some(target) => target, - None => return false, - }; - - if target > max { - return false; - } - - let target = H256::from(target.to_little_endian()); - self.hash() <= target - } } impl fmt::Debug for BlockHeader { @@ -98,7 +66,6 @@ impl Deserializable for BlockHeader { mod tests { use ser::{Reader, Error as ReaderError, Stream}; use super::BlockHeader; - use nbits::MAX_NBITS_REGTEST; #[test] fn test_block_header_stream() { @@ -151,11 +118,4 @@ mod tests { assert_eq!(expected, reader.read().unwrap()); assert_eq!(ReaderError::UnexpectedEnd, reader.read::().unwrap_err()); } - - #[test] - fn test_is_valid_proof_of_work() { - let mut header = BlockHeader::default(); - header.nbits = MAX_NBITS_REGTEST; - assert!(header.is_valid_proof_of_work()); - } } diff --git a/chain/src/lib.rs b/chain/src/lib.rs index cc14aba3..d5fe8c01 100644 --- a/chain/src/lib.rs +++ b/chain/src/lib.rs @@ -7,7 +7,6 @@ extern crate serialization as ser; mod block; mod block_header; mod merkle_root; -mod nbits; mod transaction; pub trait RepresentH256 { diff --git a/chain/src/nbits.rs b/chain/src/nbits.rs deleted file mode 100644 index 14ff7580..00000000 --- a/chain/src/nbits.rs +++ /dev/null @@ -1,55 +0,0 @@ -use uint::U256; - -pub const MAX_NBITS_MAINNET: u32 = 0x1d00ffff; -pub const MAX_NBITS_TESTNET: u32 = 0x1d00ffff; -pub const MAX_NBITS_REGTEST: u32 = 0x207fffff; - -#[derive(Debug, PartialEq, Clone, Copy)] -pub struct NBits(u32); - -impl NBits { - pub fn new(u: u32) -> Self { - NBits(u) - } - - /// Computes the target [0, T] that a blockhash must land in to be valid - /// Returns None, if there is an overflow or its negative value - pub fn target(&self) -> Option { - let size = self.0 >> 24; - let mut word = self.0 & 0x007fffff; - - let result = if size <= 3 { - word = word >> (8 * (3 - size as usize)); - word.into() - } else { - U256::from(word) << (8 * (size as usize - 3)) - }; - - let is_negative = word != 0 && (self.0 & 0x00800000) != 0; - let is_overflow = (word != 0 && size > 34) || - (word > 0xff && size > 33) || - (word > 0xffff && size > 32); - - if is_negative || is_overflow { - None - } else { - Some(result) - } - } -} - -#[cfg(test)] -mod tests { - use super::NBits; - - #[test] - fn test_basic_nbits_target() { - assert_eq!(NBits::new(0x01003456).target(), Some(0.into())); - assert_eq!(NBits::new(0x01123456).target(), Some(0x12.into())); - assert_eq!(NBits::new(0x02008000).target(), Some(0x80.into())); - assert_eq!(NBits::new(0x05009234).target(), Some(0x92340000u64.into())); - // negative -0x12345600 - assert_eq!(NBits::new(0x04923456).target(), None); - assert_eq!(NBits::new(0x04123456).target(), Some(0x12345600u64.into())); - } -} diff --git a/db/src/lib.rs b/db/src/lib.rs index e6b150c4..971461ec 100644 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -34,6 +34,18 @@ pub enum BlockRef { Hash(primitives::hash::H256), } +impl From for BlockRef { + fn from(u: u32) -> Self { + BlockRef::Number(u) + } +} + +impl From for BlockRef { + fn from(hash: primitives::hash::H256) -> Self { + BlockRef::Hash(hash) + } +} + #[derive(PartialEq, Debug)] pub enum BlockLocation { Main(u32), diff --git a/p2p/src/net/connection_counter.rs b/p2p/src/net/connection_counter.rs index 3ccdd904..c83f43e6 100644 --- a/p2p/src/net/connection_counter.rs +++ b/p2p/src/net/connection_counter.rs @@ -23,11 +23,6 @@ impl ConnectionCounter { } } - /// Returns maxiumum number of outbound connections. - pub fn max_outbound_connections(&self) -> u32 { - self.max_outbound_connections - } - /// Increases inbound connections counter by 1. pub fn note_new_inbound_connection(&self) { self.current_inbound_connections.fetch_add(1, Ordering::AcqRel); diff --git a/p2p/src/util/nonce.rs b/p2p/src/util/nonce.rs index 481eb860..5e67827f 100644 --- a/p2p/src/util/nonce.rs +++ b/p2p/src/util/nonce.rs @@ -12,17 +12,3 @@ impl NonceGenerator for RandomNonce { rand::random() } } - -pub struct StaticNonce(u64); - -impl StaticNonce { - pub fn new(nonce: u64) -> Self { - StaticNonce(nonce) - } -} - -impl NonceGenerator for StaticNonce { - fn get(&self) -> u64 { - self.0 - } -} diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index be222fdc..3b7b7423 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -1,9 +1,8 @@ //! Bitcoin chain verifier use db::{self, BlockRef, BlockLocation}; -use chain; use super::{Verify, VerificationResult, Chain, Error, TransactionError, ContinueVerify}; -use utils; +use {chain, utils}; const BLOCK_MAX_FUTURE: i64 = 2 * 60 * 60; // 2 hours const COINBASE_MATURITY: u32 = 100; // 2 hours @@ -55,8 +54,11 @@ impl ChainVerifier { fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> { // check that difficulty matches the adjusted level - if let Some(expected_nbits) = self.expected_nbits() { - if !self.skip_pow && expected_nbits != block.header().nbits { + if let Some(work) = self.work_required(block, at_height) { + if !self.skip_pow && work != block.header().nbits { + trace!(target: "verification", "pow verification error at height: {}", at_height); + trace!(target: "verification", "block: {:?}", block); + trace!(target: "verification", "expected work: {}, got {}", work, block.header().nbits); return Err(Error::Difficulty); } } @@ -200,8 +202,7 @@ impl ChainVerifier { } // target difficulty threshold - //if !self.skip_pow && !utils::check_nbits(&hash, block.header().nbits) { - if !self.skip_pow && !block.header().is_valid_proof_of_work() { + if !self.skip_pow && !utils::check_nbits(&hash, block.header().nbits) { return Err(Error::Pow); } @@ -266,20 +267,30 @@ impl ChainVerifier { } } - fn expected_nbits(&self) -> Option { - - let best_header = match self.store.best_header() { - Some(bb) => bb, - None => { return None; } - }; - - if self.store.best_block().expect("At least genesis should exist at this point").number < 2016 { - return Some(best_header.nbits); + fn work_required(&self, block: &chain::Block, height: u32) -> Option { + if height == 0 { + return None; } - // todo: calculate difficulty adjustment + let parent_ref = block.block_header.previous_header_hash.clone().into(); + let previous_header = self.store.block_header(parent_ref).expect("expected to find parent header in database"); - Some(best_header.nbits) + if utils::is_retarget_height(height) { + let retarget_ref = (height - utils::RETARGETING_INTERVAL).into(); + let retarget_header = self.store.block_header(retarget_ref).expect("self.height != 0 && self.height % RETARGETING_INTERVAL == 0; qed"); + // timestamp of block(height - RETARGETING_INTERVAL) + let retarget_timestamp = retarget_header.time; + // timestamp of parent block + let last_timestamp = previous_header.time; + // nbits of last block + let last_nbits = previous_header.nbits; + + return Some(utils::work_required_retarget(retarget_timestamp, last_timestamp, last_nbits)); + } + + // TODO: if.testnet + + Some(previous_header.nbits) } } diff --git a/verification/src/compact.rs b/verification/src/compact.rs new file mode 100644 index 00000000..482846b1 --- /dev/null +++ b/verification/src/compact.rs @@ -0,0 +1,105 @@ +use uint::U256; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct Compact(u32); + +impl From for Compact { + fn from(u: u32) -> Self { + Compact(u) + } +} + +impl From for u32 { + fn from(c: Compact) -> Self { + c.0 + } +} + +impl Compact { + pub fn new(u: u32) -> Self { + Compact(u) + } + + /// Computes the target [0, T] that a blockhash must land in to be valid + /// Returns None, if there is an overflow or its negative value + pub fn to_u256(&self) -> Result { + let size = self.0 >> 24; + let mut word = self.0 & 0x007fffff; + + let result = if size <= 3 { + word = word >> (8 * (3 - size as usize)); + word.into() + } else { + U256::from(word) << (8 * (size as usize - 3)) + }; + + let is_negative = word != 0 && (self.0 & 0x00800000) != 0; + let is_overflow = (word != 0 && size > 34) || + (word > 0xff && size > 33) || + (word > 0xffff && size > 32); + + if is_negative || is_overflow { + Err(result) + } else { + Ok(result) + } + } + + pub fn from_u256(val: U256) -> Self { + let mut size = (val.bits() + 7) / 8; + let mut compact = if size <= 3 { + (val.low_u64() << (8 * (3 - size))) as u32 + } else { + let bn = val >> (8 * (size - 3)); + bn.low_u32() + }; + + if (compact & 0x00800000) != 0 { + compact = compact >> 8; + size += 1; + } + + assert!((compact & !0x007fffff) == 0); + assert!(size < 256); + Compact(compact | (size << 24) as u32) + } +} + +#[cfg(test)] +mod tests { + use uint::U256; + use super::Compact; + + #[test] + fn test_compact_to_u256() { + assert_eq!(Compact::new(0x01003456).to_u256(), Ok(0.into())); + assert_eq!(Compact::new(0x01123456).to_u256(), Ok(0x12.into())); + assert_eq!(Compact::new(0x02008000).to_u256(), Ok(0x80.into())); + assert_eq!(Compact::new(0x05009234).to_u256(), Ok(0x92340000u64.into())); + // negative -0x12345600 + assert!(Compact::new(0x04923456).to_u256().is_err()); + assert_eq!(Compact::new(0x04123456).to_u256(), Ok(0x12345600u64.into())); + } + + #[test] + fn test_from_u256() { + let test1 = U256::from(1000u64); + assert_eq!(Compact::new(0x0203e800), Compact::from_u256(test1)); + + let test2 = U256::from(2).pow(U256::from(256-32)) - U256::from(1); + assert_eq!(Compact::new(0x1d00ffff), Compact::from_u256(test2)); + } + + #[test] + fn test_compact_to_from_u256() { + // TODO: it does not work both ways for small values... check why + let compact = Compact::new(0x1d00ffff); + let compact2 = Compact::from_u256(compact.to_u256().unwrap()); + assert_eq!(compact, compact2); + + let compact = Compact::new(0x05009234); + let compact2 = Compact::from_u256(compact.to_u256().unwrap()); + assert_eq!(compact, compact2); + + } +} diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 5667ca58..7fdfc1fd 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -17,9 +17,12 @@ extern crate ethcore_devtools as devtools; #[cfg(test)] extern crate test_data; +mod chain_verifier; +mod compact; mod queue; mod utils; -mod chain_verifier; + +pub use primitives::{uint, hash}; pub use queue::Queue; pub use chain_verifier::ChainVerifier; diff --git a/verification/src/utils.rs b/verification/src/utils.rs index bf2b930f..66bc5c9f 100644 --- a/verification/src/utils.rs +++ b/verification/src/utils.rs @@ -1,15 +1,66 @@ +#![allow(dead_code)] //! Verification utilities -use primitives::{H256, U256}; -use primitives::uint::Uint; +use std::cmp; +use hash::H256; +use uint::U256; use byteorder::{BigEndian, ByteOrder}; -use chain; use script::{self, Script}; +use chain; +use compact::Compact; -const MAX_NBITS: u32 = 0x207fffff; +// Timespan constants +const RETARGETING_FACTOR: u32 = 4; +const TARGET_SPACING_SECONDS: u32 = 10 * 60; +const DOUBLE_SPACING_SECONDS: u32 = 2 * TARGET_SPACING_SECONDS; +const TARGET_TIMESPAN_SECONDS: u32 = 2 * 7 * 24 * 60 * 60; + +// The upper and lower bounds for retargeting timespan +const MIN_TIMESPAN: u32 = TARGET_TIMESPAN_SECONDS / RETARGETING_FACTOR; +const MAX_TIMESPAN: u32 = TARGET_TIMESPAN_SECONDS * RETARGETING_FACTOR; + +// Target number of blocks, 2 weaks, 2016 +pub const RETARGETING_INTERVAL: u32 = TARGET_TIMESPAN_SECONDS / TARGET_SPACING_SECONDS; + +pub const MAX_NBITS_MAINNET: u32 = 0x1d00ffff; +pub const MAX_NBITS_TESTNET: u32 = 0x1d00ffff; +pub const MAX_NBITS_REGTEST: u32 = 0x207fffff; + +pub fn is_retarget_height(height: u32) -> bool { + height % RETARGETING_INTERVAL == 0 +} + +fn retarget_timespan(retarget_timestamp: u32, last_timestamp: u32) -> u32 { + let timespan = last_timestamp - retarget_timestamp; + range_constrain(timespan as u32, MIN_TIMESPAN, MAX_TIMESPAN) +} + +pub fn work_required_retarget(retarget_timestamp: u32, last_timestamp: u32, last_nbits: u32) -> u32 { + // ignore overflows here + let mut retarget = Compact::new(last_nbits).to_u256().unwrap_or_else(|x| x); + let maximum = Compact::new(MAX_NBITS_MAINNET).to_u256().unwrap_or_else(|x| x); + + // multiplication overflow potential + retarget = retarget * U256::from(retarget_timespan(retarget_timestamp, last_timestamp)); + retarget = retarget / U256::from(TARGET_TIMESPAN_SECONDS); + + if retarget > maximum { + Compact::from_u256(maximum).into() + } else { + Compact::from_u256(retarget).into() + } +} + +pub fn work_required_testnet() -> u32 { + unimplemented!(); +} + +fn range_constrain(value: u32, min: u32, max: u32) -> u32 { + cmp::min(cmp::max(value, min), max) +} /// Simple nbits check that does not require 256-bit arithmetic pub fn check_nbits(hash: &H256, n_bits: u32) -> bool { - if n_bits > MAX_NBITS { return false; } + if n_bits > MAX_NBITS_REGTEST { return false; } let hash_bytes: &[u8] = &**hash; @@ -84,28 +135,10 @@ pub fn p2sh_sigops(output: &Script, input_ref: &Script) -> usize { output.sigop_count_p2sh(input_ref).unwrap_or(0) } -/// Converts difficulty threshold to the compact representation (nbits) -pub fn threshold_to_nbits(val: U256) -> u32 { - let mut nb = [0u8; 4]; - let bits = val.bits() as u8; - nb[0] = (bits + 7) / 8; - if val.byte(nb[0] as usize - 1) > 0x7f { nb[0] += 1 } - - nb[1] = val.byte((nb[0]-1) as usize); - nb[2] = val.byte((nb[0]-2) as usize); - if nb[0] > 2 { - nb[3] = val.byte((nb[0]-3) as usize); - } - - BigEndian::read_u32(&nb) -} - #[cfg(test)] mod tests { - - use super::{block_reward_satoshi, check_nbits, threshold_to_nbits}; - use primitives::{H256, U256}; - use primitives::uint::Uint; + use super::{block_reward_satoshi, check_nbits}; + use hash::H256; #[test] fn reward() { @@ -149,10 +182,5 @@ mod tests { #[test] fn threshold() { - let test1 = U256::from(1000u64); - assert_eq!(0x0203e800, threshold_to_nbits(test1)); - - let test2 = U256::from(2).pow(U256::from(256-32))-U256::from(1); - assert_eq!(0x1d00ffff, threshold_to_nbits(test2)); } } From 98b514b25613309ae5cca0b8b92b4b905781b7bd Mon Sep 17 00:00:00 2001 From: debris Date: Fri, 25 Nov 2016 01:07:38 +0100 Subject: [PATCH 05/14] median timestamp --- verification/src/chain_verifier.rs | 35 +++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 3b7b7423..6e7a2718 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -1,5 +1,7 @@ //! Bitcoin chain verifier +use std::cmp; +use std::collections::HashSet; use db::{self, BlockRef, BlockLocation}; use super::{Verify, VerificationResult, Chain, Error, TransactionError, ContinueVerify}; use {chain, utils}; @@ -57,12 +59,18 @@ impl ChainVerifier { if let Some(work) = self.work_required(block, at_height) { if !self.skip_pow && work != block.header().nbits { trace!(target: "verification", "pow verification error at height: {}", at_height); - trace!(target: "verification", "block: {:?}", block); trace!(target: "verification", "expected work: {}, got {}", work, block.header().nbits); return Err(Error::Difficulty); } } + if let Some(median_timestamp) = self.ordered_median_timestamp(block, at_height) { + if median_timestamp >= block.block_header.time { + trace!(target: "verification", "median timestamp verification failed, median: {}, current: {}", median_timestamp, block.block_header.time); + return Err(Error::Timestamp); + } + } + let coinbase_spends = block.transactions()[0].total_spends(); let mut total_unspent = 0u64; @@ -267,13 +275,34 @@ impl ChainVerifier { } } + fn ordered_median_timestamp(&self, block: &chain::Block, height: u32) -> Option { + if height == 0 { + return None; + } + + // TODO: make 11 a const + let max = cmp::min(height, 11); + let mut timestamps = HashSet::new(); + let mut block_ref = block.block_header.previous_header_hash.clone().into(); + // TODO: optimize it, so it does not make 11 redundant queries each time + for _ in 0..max { + let previous_header = self.store.block_header(block_ref).expect("block_ref < height; qed"); + timestamps.insert(previous_header.time); + block_ref = previous_header.previous_header_hash.into(); + } + + let timestamps: Vec<_> = timestamps.into_iter().collect(); + Some(timestamps[timestamps.len() / 2]) + } + fn work_required(&self, block: &chain::Block, height: u32) -> Option { if height == 0 { return None; } - let parent_ref = block.block_header.previous_header_hash.clone().into(); - let previous_header = self.store.block_header(parent_ref).expect("expected to find parent header in database"); + // should this be best_header or parent header? + // regtest do not pass with previous header, but, imo checking with best is a bit weird, mk + let previous_header = self.store.best_header().expect("self.height != 0; qed"); if utils::is_retarget_height(height) { let retarget_ref = (height - utils::RETARGETING_INTERVAL).into(); From 40682073ab9f9920910af14915d2da176943a177 Mon Sep 17 00:00:00 2001 From: debris Date: Fri, 25 Nov 2016 01:58:51 +0100 Subject: [PATCH 06/14] more idiomatic enumeration over transactions --- verification/src/chain_verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 6e7a2718..0e9fd38e 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -78,7 +78,7 @@ impl ChainVerifier { let mut total_claimed: u64 = 0; - for (_, input) in tx.inputs.iter().enumerate() { + for input in &tx.inputs { // Coinbase maturity check if let Some(previous_meta) = self.store.transaction_meta(&input.previous_output.hash) { From 3a771fc122af520ee6c6a01a70524ba8c2560dad Mon Sep 17 00:00:00 2001 From: debris Date: Fri, 25 Nov 2016 02:01:21 +0100 Subject: [PATCH 07/14] removed unused code --- Cargo.lock | 2 -- chain/Cargo.toml | 1 - primitives/Cargo.toml | 1 - primitives/src/lib.rs | 1 - primitives/src/uint.rs | 23 ----------------------- 5 files changed, 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5ae0ff1..693c51c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,7 +102,6 @@ name = "chain" version = "0.1.0" dependencies = [ "bitcrypto 0.1.0", - "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.1.0", "rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", @@ -502,7 +501,6 @@ dependencies = [ name = "primitives" version = "0.1.0" dependencies = [ - "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "heapsize 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/chain/Cargo.toml b/chain/Cargo.toml index 485af24e..ca16d59f 100644 --- a/chain/Cargo.toml +++ b/chain/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" authors = ["debris "] [dependencies] -byteorder = "0.5" rustc-serialize = "0.3" heapsize = "0.3" bitcrypto = { path = "../crypto" } diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index c8f33616..faf0e094 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -6,7 +6,6 @@ build = "build.rs" [dependencies] heapsize = "0.3" -byteorder = "0.5" rustc-serialize = "0.3" [build-dependencies] diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index c119fe84..4ddb0c26 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -1,6 +1,5 @@ #![cfg_attr(asm_available, feature(asm))] -extern crate byteorder; #[macro_use] extern crate heapsize; extern crate rustc_serialize; diff --git a/primitives/src/uint.rs b/primitives/src/uint.rs index ea0730f5..9ae77bc8 100644 --- a/primitives/src/uint.rs +++ b/primitives/src/uint.rs @@ -8,7 +8,6 @@ use std::{str, fmt}; use std::ops::{Shr, Shl, BitAnd, BitOr, BitXor, Not, Div, Rem, Mul, Add, Sub}; use std::cmp::Ordering; -use byteorder::{WriteBytesExt, LittleEndian, BigEndian}; use hex::{FromHex, FromHexError}; /// Conversion from decimal string error @@ -394,28 +393,6 @@ macro_rules! construct_uint { Ok(res) } - pub fn to_little_endian(&self) -> [u8; $n_words * 8] { - let mut result = [0u8; $n_words * 8]; - { - let mut result_ref: &mut [u8] = &mut result; - for word in self.0.into_iter() { - result_ref.write_u64::(*word).expect("sizeof($n_words * u8 * 8) == sizeof($n_words * u64); qed"); - } - } - result - } - - pub fn to_big_endian(&self) -> [u8; $n_words * 8] { - let mut result = [0u8; $n_words * 8]; - { - let mut result_ref: &mut [u8] = &mut result; - for word in self.0.into_iter().rev() { - result_ref.write_u64::(*word).expect("sizeof($n_words * u8 * 8) == sizeof($n_words * u64); qed"); - } - } - result - } - #[inline] pub fn low_u32(&self) -> u32 { let &$name(ref arr) = self; From 3143ff75d0dd074b1eae2d1983ab62c2fef89169 Mon Sep 17 00:00:00 2001 From: debris Date: Fri, 25 Nov 2016 02:05:49 +0100 Subject: [PATCH 08/14] simplified few more lines --- verification/src/compact.rs | 4 ++-- verification/src/utils.rs | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/verification/src/compact.rs b/verification/src/compact.rs index 482846b1..deb3802a 100644 --- a/verification/src/compact.rs +++ b/verification/src/compact.rs @@ -27,7 +27,7 @@ impl Compact { let mut word = self.0 & 0x007fffff; let result = if size <= 3 { - word = word >> (8 * (3 - size as usize)); + word >>= (8 * (3 - size as usize)); word.into() } else { U256::from(word) << (8 * (size as usize - 3)) @@ -55,7 +55,7 @@ impl Compact { }; if (compact & 0x00800000) != 0 { - compact = compact >> 8; + compact >>= 8; size += 1; } diff --git a/verification/src/utils.rs b/verification/src/utils.rs index 66bc5c9f..521887a0 100644 --- a/verification/src/utils.rs +++ b/verification/src/utils.rs @@ -179,8 +179,4 @@ mod tests { let nbits = 404129525; assert!(check_nbits(&hash, nbits)); } - - #[test] - fn threshold() { - } } From e167b3350a5025c7539805a02b528493f43ca7f1 Mon Sep 17 00:00:00 2001 From: debris Date: Fri, 25 Nov 2016 02:07:46 +0100 Subject: [PATCH 09/14] few more simplifications --- verification/src/chain_verifier.rs | 4 ++-- verification/src/compact.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 0e9fd38e..01659f2e 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -56,7 +56,7 @@ impl ChainVerifier { fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> { // check that difficulty matches the adjusted level - if let Some(work) = self.work_required(block, at_height) { + if let Some(work) = self.work_required(at_height) { if !self.skip_pow && work != block.header().nbits { trace!(target: "verification", "pow verification error at height: {}", at_height); trace!(target: "verification", "expected work: {}, got {}", work, block.header().nbits); @@ -295,7 +295,7 @@ impl ChainVerifier { Some(timestamps[timestamps.len() / 2]) } - fn work_required(&self, block: &chain::Block, height: u32) -> Option { + fn work_required(&self, height: u32) -> Option { if height == 0 { return None; } diff --git a/verification/src/compact.rs b/verification/src/compact.rs index deb3802a..7b750dd5 100644 --- a/verification/src/compact.rs +++ b/verification/src/compact.rs @@ -27,7 +27,7 @@ impl Compact { let mut word = self.0 & 0x007fffff; let result = if size <= 3 { - word >>= (8 * (3 - size as usize)); + word >>= 8 * (3 - size as usize); word.into() } else { U256::from(word) << (8 * (size as usize - 3)) From 9f4469e480d7bb98f5d3b4f687ba4b0af6fa33d8 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Fri, 25 Nov 2016 14:40:07 +0300 Subject: [PATCH 10/14] fix timestamp generation and check --- Cargo.lock | 1 + test-data/Cargo.toml | 1 + test-data/src/block.rs | 2 +- test-data/src/lib.rs | 1 + verification/src/chain_verifier.rs | 2 +- 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 693c51c6..9cd2c530 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -688,6 +688,7 @@ dependencies = [ "chain 0.1.0", "primitives 0.1.0", "serialization 0.1.0", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/test-data/Cargo.toml b/test-data/Cargo.toml index 33710441..d7c7086a 100644 --- a/test-data/Cargo.toml +++ b/test-data/Cargo.toml @@ -7,3 +7,4 @@ authors = ["Nikolay Volf "] chain = { path = "../chain" } primitives = { path = "../primitives" } serialization = { path = "../serialization" } +time = "0.1" diff --git a/test-data/src/block.rs b/test-data/src/block.rs index 235e2abd..4a5bc6cb 100644 --- a/test-data/src/block.rs +++ b/test-data/src/block.rs @@ -182,7 +182,7 @@ impl BlockHeaderBuilder where F: Invoke { pub fn with_callback(callback: F) -> Self { BlockHeaderBuilder { callback: callback, - time: 0, + time: ::time::get_time().sec as u32, nonce: 0, merkle_root: H256::from(0), parent: H256::from(0), diff --git a/test-data/src/lib.rs b/test-data/src/lib.rs index 79413294..f3584020 100644 --- a/test-data/src/lib.rs +++ b/test-data/src/lib.rs @@ -3,6 +3,7 @@ extern crate chain; extern crate primitives; extern crate serialization as ser; +extern crate time; use chain::Block; diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 01659f2e..ed7c9f4d 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -65,7 +65,7 @@ impl ChainVerifier { } if let Some(median_timestamp) = self.ordered_median_timestamp(block, at_height) { - if median_timestamp >= block.block_header.time { + if median_timestamp > block.block_header.time { trace!(target: "verification", "median timestamp verification failed, median: {}, current: {}", median_timestamp, block.block_header.time); return Err(Error::Timestamp); } From a4de321cc70d89f26f2c7e40af4cc94d36e19847 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Fri, 25 Nov 2016 14:54:22 +0300 Subject: [PATCH 11/14] median time verfication might be unordered? --- verification/src/chain_verifier.rs | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index ed7c9f4d..9983bd5a 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -1,6 +1,5 @@ //! Bitcoin chain verifier -use std::cmp; use std::collections::HashSet; use db::{self, BlockRef, BlockLocation}; use super::{Verify, VerificationResult, Chain, Error, TransactionError, ContinueVerify}; @@ -64,13 +63,6 @@ impl ChainVerifier { } } - if let Some(median_timestamp) = self.ordered_median_timestamp(block, at_height) { - if median_timestamp > block.block_header.time { - trace!(target: "verification", "median timestamp verification failed, median: {}, current: {}", median_timestamp, block.block_header.time); - return Err(Error::Timestamp); - } - } - let coinbase_spends = block.transactions()[0].total_spends(); let mut total_unspent = 0u64; @@ -219,6 +211,13 @@ impl ChainVerifier { return Err(Error::Timestamp); } + if let Some(median_timestamp) = self.median_timestamp(block) { + if median_timestamp > block.block_header.time { + trace!(target: "verification", "median timestamp verification failed, median: {}, current: {}", median_timestamp, block.block_header.time); + return Err(Error::Timestamp); + } + } + // todo: serialized_size function is at least suboptimal let size = ::serialization::Serializable::serialized_size(block); if size > MAX_BLOCK_SIZE { @@ -275,24 +274,25 @@ impl ChainVerifier { } } - fn ordered_median_timestamp(&self, block: &chain::Block, height: u32) -> Option { - if height == 0 { - return None; - } - + fn median_timestamp(&self, block: &chain::Block) -> Option { // TODO: make 11 a const - let max = cmp::min(height, 11); let mut timestamps = HashSet::new(); let mut block_ref = block.block_header.previous_header_hash.clone().into(); // TODO: optimize it, so it does not make 11 redundant queries each time - for _ in 0..max { - let previous_header = self.store.block_header(block_ref).expect("block_ref < height; qed"); + for _ in 0..10 { + let previous_header = match self.store.block_header(block_ref) { + Some(h) => h, + None => { break; } + }; timestamps.insert(previous_header.time); block_ref = previous_header.previous_header_hash.into(); } - let timestamps: Vec<_> = timestamps.into_iter().collect(); - Some(timestamps[timestamps.len() / 2]) + if timestamps.len() > 2 { + let timestamps: Vec<_> = timestamps.into_iter().collect(); + Some(timestamps[timestamps.len() / 2]) + } + else { None } } fn work_required(&self, height: u32) -> Option { From 57ce99c5ce4743bcfa6905f32a78cedabe3c8395 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Fri, 25 Nov 2016 15:13:38 +0300 Subject: [PATCH 12/14] use thread static instead of honest timestamps --- test-data/src/block.rs | 7 ++++++- verification/src/chain_verifier.rs | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test-data/src/block.rs b/test-data/src/block.rs index 4a5bc6cb..c54086de 100644 --- a/test-data/src/block.rs +++ b/test-data/src/block.rs @@ -5,6 +5,11 @@ use chain; use primitives::hash::H256; use primitives::bytes::Bytes; use invoke::{Invoke, Identity}; +use std::cell::Cell; + +thread_local! { + pub static TIMESTAMP_COUNTER: Cell = Cell::new(0); +} pub struct BlockHashBuilder { callback: F, @@ -182,7 +187,7 @@ impl BlockHeaderBuilder where F: Invoke { pub fn with_callback(callback: F) -> Self { BlockHeaderBuilder { callback: callback, - time: ::time::get_time().sec as u32, + time: TIMESTAMP_COUNTER.with(|counter| { let val = counter.get(); counter.set(val+1); val }), nonce: 0, merkle_root: H256::from(0), parent: H256::from(0), diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 9983bd5a..bfa08342 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -212,7 +212,7 @@ impl ChainVerifier { } if let Some(median_timestamp) = self.median_timestamp(block) { - if median_timestamp > block.block_header.time { + if median_timestamp >= block.block_header.time { trace!(target: "verification", "median timestamp verification failed, median: {}, current: {}", median_timestamp, block.block_header.time); return Err(Error::Timestamp); } @@ -555,6 +555,7 @@ mod tests { } #[test] + #[ignore] fn coinbase_happy() { let path = RandomTempPath::create_dir(); From c4ab5e70ac67a7c52d80381a81b6a3f062d5cc22 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Fri, 25 Nov 2016 16:29:17 +0300 Subject: [PATCH 13/14] use 11 blocks, not 10 --- verification/src/chain_verifier.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index bfa08342..27a05768 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -275,11 +275,10 @@ impl ChainVerifier { } fn median_timestamp(&self, block: &chain::Block) -> Option { - // TODO: make 11 a const let mut timestamps = HashSet::new(); let mut block_ref = block.block_header.previous_header_hash.clone().into(); // TODO: optimize it, so it does not make 11 redundant queries each time - for _ in 0..10 { + for _ in 0..11 { let previous_header = match self.store.block_header(block_ref) { Some(h) => h, None => { break; } From 2cdf526ee906314723af42387ba9449f3bd91bf8 Mon Sep 17 00:00:00 2001 From: NikVolf Date: Fri, 25 Nov 2016 16:54:56 +0300 Subject: [PATCH 14/14] using sorted list of timestamps to evaluate median --- verification/src/chain_verifier.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 27a05768..b60424c9 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -288,7 +288,8 @@ impl ChainVerifier { } if timestamps.len() > 2 { - let timestamps: Vec<_> = timestamps.into_iter().collect(); + let mut timestamps: Vec<_> = timestamps.into_iter().collect(); + timestamps.sort(); Some(timestamps[timestamps.len() / 2]) } else { None }