From 34a582b8e9dd5ac3eee2097be5159b814627efbe Mon Sep 17 00:00:00 2001 From: NikVolf Date: Tue, 22 Nov 2016 18:26:00 +0300 Subject: [PATCH 1/2] 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 2/2] 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)); + } }