diff --git a/db/src/indexed_block.rs b/db/src/indexed_block.rs index 8d20d45f..eda7c915 100644 --- a/db/src/indexed_block.rs +++ b/db/src/indexed_block.rs @@ -1,5 +1,6 @@ use chain; use primitives::hash::H256; +use serialization::Serializable; pub struct IndexedBlock { header: chain::BlockHeader, @@ -73,6 +74,19 @@ impl IndexedBlock { self.transactions.clone(), ) } + + pub fn size(&self) -> usize { + // todo: optimize + self.to_block().serialized_size() + } + + pub fn merkle_root(&self) -> H256 { + chain::merkle_root(&self.transaction_hashes) + } + + pub fn is_final(&self, height: u32) -> bool { + self.transactions.iter().all(|t| t.is_final(height, self.header.time)) + } } pub struct IndexedTransactions<'a> { diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index b317951b..08fe39af 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -1,10 +1,11 @@ //! Bitcoin chain verifier use std::collections::BTreeSet; -use db::{self, BlockRef, BlockLocation}; +use db::{self, BlockRef, BlockLocation, IndexedBlock}; use network::Magic; use super::{Verify, VerificationResult, Chain, Error, TransactionError, ContinueVerify}; use {chain, utils}; +use primitives::H256; const BLOCK_MAX_FUTURE: i64 = 2 * 60 * 60; // 2 hours const COINBASE_MATURITY: u32 = 100; // 2 hours @@ -56,7 +57,7 @@ impl ChainVerifier { self } - fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> { + fn ordered_verify(&self, block: &db::IndexedBlock, at_height: u32) -> Result<(), Error> { if !block.is_final(at_height) { return Err(Error::NonFinalBlock); } @@ -73,11 +74,15 @@ impl ChainVerifier { } } - let coinbase_spends = block.transactions()[0].total_spends(); + let coinbase_spends = block.transactions() + .nth(0) + .expect("block emptyness should be checked at this point") + .1 + .total_spends(); // bip30 - for (tx_index, tx) in block.transactions.iter().enumerate() { - if let Some(meta) = self.store.transaction_meta(&tx.hash()) { + for (tx_index, (tx_hash, tx)) in block.transactions().enumerate() { + if let Some(meta) = self.store.transaction_meta(tx_hash) { if !meta.is_fully_spent() && !consensus_params.is_bip30_exception(&block_hash, at_height) { return Err(Error::Transaction(tx_index, TransactionError::UnspentTransactionWithTheSameHash)); } @@ -85,7 +90,7 @@ impl ChainVerifier { } let mut total_unspent = 0u64; - for (tx_index, tx) in block.transactions().iter().enumerate().skip(1) { + for (tx_index, (tx_hash, tx)) in block.transactions().enumerate().skip(1) { let mut total_claimed: u64 = 0; @@ -106,7 +111,13 @@ impl ChainVerifier { self.store.transaction(&input.previous_output.hash) // todo: optimize block decomposition vec -> hashmap - .or(block.transactions().iter().find(|tx| !tx.is_coinbase() && tx.hash() == input.previous_output.hash).cloned()) + .or( + block.transactions() + .skip(1) + .find(|&(hash, tx)| hash == &input.previous_output.hash) + .and_then(|(_, tx)| Some(tx)) + .cloned() + ) .ok_or( Error::Transaction(tx_index, TransactionError::UnknownReference(input.previous_output.hash.clone())) ) @@ -140,7 +151,8 @@ impl ChainVerifier { } fn verify_transaction(&self, - block: &chain::Block, + block: &db::IndexedBlock, + hash: &H256, transaction: &chain::Transaction, sequence: usize, ) -> Result { @@ -170,7 +182,10 @@ impl ChainVerifier { let store_parent_transaction = self.store.transaction(&input.previous_output.hash); let parent_transaction = store_parent_transaction .as_ref() - .or_else(|| block.transactions.iter().find(|t| t.hash() == input.previous_output.hash)) + .or_else( + || block.transactions().find(|&(hash, tx)| hash == &input.previous_output.hash) + .and_then(|(hash, tx)| Some(tx)) + ) .ok_or_else(|| TransactionError::Inconclusive(input.previous_output.hash.clone()))?; if parent_transaction.outputs.len() <= input.previous_output.index as usize { @@ -212,11 +227,11 @@ impl ChainVerifier { Ok(sigops) } - fn verify_block(&self, block: &chain::Block) -> VerificationResult { + fn verify_block(&self, block: &db::IndexedBlock) -> VerificationResult { let hash = block.hash(); // There should be at least 1 transaction - if block.transactions().is_empty() { + if block.transaction_count() == 0 { return Err(Error::Empty); } @@ -231,14 +246,18 @@ impl ChainVerifier { } 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); + if median_timestamp >= block.header().time { + trace!( + target: "verification", "median timestamp verification failed, median: {}, current: {}", + median_timestamp, + block.header().time + ); return Err(Error::Timestamp); } } // todo: serialized_size function is at least suboptimal - let size = ::serialization::Serializable::serialized_size(block); + let size = block.size(); if size > MAX_BLOCK_SIZE { return Err(Error::Size(size)) } @@ -248,25 +267,25 @@ impl ChainVerifier { return Err(Error::MerkleRoot); } + let first_tx = block.transactions().nth(0).expect("transaction count is checked above to be greater than 0").1; // check first transaction is a coinbase transaction - if !block.transactions()[0].is_coinbase() { + if !first_tx.is_coinbase() { return Err(Error::Coinbase) } - // check that coinbase has a valid signature - let coinbase = &block.transactions()[0]; // is_coinbase() = true above guarantees that there is at least one input - let coinbase_script_len = coinbase.inputs[0].script_sig.len(); + let coinbase_script_len = first_tx.inputs[0].script_sig.len(); if coinbase_script_len < 2 || coinbase_script_len > 100 { return Err(Error::CoinbaseSignatureLength(coinbase_script_len)); } // transaction verification including number of signature operations checking let mut block_sigops = 0; - for (idx, transaction) in block.transactions().iter().enumerate() { + for (idx, (tx_hash, transaction)) in block.transactions().enumerate() { block_sigops += try!( self.verify_transaction( block, + tx_hash, transaction, idx, ).map_err(|e| Error::Transaction(idx, e)) @@ -293,9 +312,9 @@ impl ChainVerifier { } } - fn median_timestamp(&self, block: &chain::Block) -> Option { + fn median_timestamp(&self, block: &db::IndexedBlock) -> Option { let mut timestamps = BTreeSet::new(); - let mut block_ref = block.block_header.previous_header_hash.clone().into(); + let mut block_ref = block.header().previous_header_hash.clone().into(); // TODO: optimize it, so it does not make 11 redundant queries each time for _ in 0..11 { let previous_header = match self.store.block_header(block_ref) { @@ -313,12 +332,12 @@ impl ChainVerifier { else { None } } - fn work_required(&self, block: &chain::Block, height: u32) -> Option { + fn work_required(&self, block: &db::IndexedBlock, height: u32) -> Option { if height == 0 { return None; } - let previous_ref = block.block_header.previous_header_hash.clone().into(); + let previous_ref = block.header().previous_header_hash.clone().into(); let previous_header = self.store.block_header(previous_ref).expect("self.height != 0; qed"); if utils::is_retarget_height(height) { @@ -341,12 +360,12 @@ impl ChainVerifier { } impl Verify for ChainVerifier { - fn verify(&self, block: &chain::Block) -> VerificationResult { + fn verify(&self, block: &db::IndexedBlock) -> VerificationResult { let result = self.verify_block(block); trace!( target: "verification", "Block {} (transactions: {}) verification finished. Result {:?}", block.hash().to_reversed_str(), - block.transactions().len(), + block.transaction_count(), result, ); result @@ -356,10 +375,10 @@ impl Verify for ChainVerifier { impl ContinueVerify for ChainVerifier { type State = usize; - fn continue_verify(&self, block: &chain::Block, state: usize) -> VerificationResult { + fn continue_verify(&self, block: &db::IndexedBlock, state: usize) -> VerificationResult { // verify transactions (except coinbase) - for (idx, transaction) in block.transactions().iter().enumerate().skip(state - 1) { - try!(self.verify_transaction(block, transaction, idx).map_err(|e| Error::Transaction(idx, e))); + for (idx, (tx_hash, transaction)) in block.transactions().enumerate().skip(state - 1) { + try!(self.verify_transaction(block, tx_hash, transaction, idx).map_err(|e| Error::Transaction(idx, e))); } let _parent = match self.store.block(BlockRef::Hash(block.header().previous_header_hash.clone())) { @@ -374,7 +393,7 @@ impl ContinueVerify for ChainVerifier { #[cfg(test)] mod tests { use std::sync::Arc; - use db::{TestStorage, Storage, Store, BlockStapler}; + use db::{TestStorage, Storage, Store, BlockStapler, IndexedBlock}; use network::Magic; use devtools::RandomTempPath; use {script, test_data}; @@ -387,7 +406,7 @@ mod tests { let b2 = test_data::block_h2(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet); - assert_eq!(Chain::Orphan, verifier.verify(&b2).unwrap()); + assert_eq!(Chain::Orphan, verifier.verify(&b2.into()).unwrap()); } #[test] @@ -395,7 +414,7 @@ mod tests { let storage = TestStorage::with_blocks(&vec![test_data::genesis()]); let b1 = test_data::block_h1(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet); - assert_eq!(Chain::Main, verifier.verify(&b1).unwrap()); + assert_eq!(Chain::Main, verifier.verify(&b1.into()).unwrap()); } #[test] @@ -408,7 +427,7 @@ mod tests { ); let b1 = test_data::block_h170(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet); - assert_eq!(Chain::Main, verifier.verify(&b1).unwrap()); + assert_eq!(Chain::Main, verifier.verify(&b1.into()).unwrap()); } #[test] @@ -425,7 +444,7 @@ mod tests { 1, TransactionError::Inconclusive("c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd3704".into()) )); - assert_eq!(should_be, verifier.verify(&b170)); + assert_eq!(should_be, verifier.verify(&b170.into())); } #[test] @@ -460,7 +479,7 @@ mod tests { TransactionError::Maturity, )); - assert_eq!(expected, verifier.verify(&block)); + assert_eq!(expected, verifier.verify(&block.into())); } #[test] @@ -493,7 +512,7 @@ mod tests { let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); let expected = Ok(Chain::Main); - assert_eq!(expected, verifier.verify(&block)); + assert_eq!(expected, verifier.verify(&block.into())); } @@ -532,7 +551,7 @@ mod tests { let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); let expected = Ok(Chain::Main); - assert_eq!(expected, verifier.verify(&block)); + assert_eq!(expected, verifier.verify(&block.into())); } #[test] @@ -570,7 +589,7 @@ mod tests { let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); let expected = Err(Error::Transaction(2, TransactionError::Overspend)); - assert_eq!(expected, verifier.verify(&block)); + assert_eq!(expected, verifier.verify(&block.into())); } #[test] @@ -615,7 +634,7 @@ mod tests { let expected = Ok(Chain::Main); - assert_eq!(expected, verifier.verify(&block)) + assert_eq!(expected, verifier.verify(&block.into())) } #[test] @@ -646,7 +665,7 @@ mod tests { builder_tx2 = builder_tx2.push_opcode(script::Opcode::OP_CHECKSIG) } - let block = test_data::block_builder() + let block: IndexedBlock = test_data::block_builder() .transaction().coinbase().build() .transaction() .input() @@ -661,12 +680,13 @@ mod tests { .build() .build() .merkled_header().parent(genesis.hash()).build() - .build(); + .build() + .into(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); let expected = Err(Error::MaximumSigops); - assert_eq!(expected, verifier.verify(&block)); + assert_eq!(expected, verifier.verify(&block.into())); } #[test] @@ -681,13 +701,14 @@ mod tests { .build(); storage.insert_block(&genesis).unwrap(); - let block = test_data::block_builder() + let block: IndexedBlock = test_data::block_builder() .transaction() .coinbase() .output().value(5000000001).build() .build() .merkled_header().parent(genesis.hash()).build() - .build(); + .build() + .into(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); @@ -696,6 +717,6 @@ mod tests { actual: 5000000001 }); - assert_eq!(expected, verifier.verify(&block)); + assert_eq!(expected, verifier.verify(&block.into())); } } diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 90b1f619..14bf2a9b 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -21,12 +21,11 @@ extern crate test_data; mod chain_verifier; mod compact; -mod queue; mod utils; +mod lookup; pub use primitives::{uint, hash}; -pub use queue::Queue; pub use chain_verifier::ChainVerifier; use primitives::hash::H256; @@ -116,11 +115,11 @@ pub type VerificationResult = Result; /// Interface for block verification pub trait Verify : Send + Sync { - fn verify(&self, block: &chain::Block) -> VerificationResult; + fn verify(&self, block: &db::IndexedBlock) -> VerificationResult; } /// Trait for verifier that can be interrupted and continue from the specific point pub trait ContinueVerify : Verify + Send + Sync { type State; - fn continue_verify(&self, block: &chain::Block, state: Self::State) -> VerificationResult; + fn continue_verify(&self, block: &db::IndexedBlock, state: Self::State) -> VerificationResult; } diff --git a/verification/src/lookup.rs b/verification/src/lookup.rs new file mode 100644 index 00000000..e86ca6c5 --- /dev/null +++ b/verification/src/lookup.rs @@ -0,0 +1,20 @@ +use db; +use chain; +use primitives::H256; +use std::collections::HashMap; + +struct BlockTransactionLookup<'a, 'b> { + store: &'a db::Store, + block: &'b db::IndexedBlock, + cache: HashMap, +} + +impl<'a, 'b> BlockTransactionLookup<'a, 'b> { + fn new(store: &'a db::Store, block: &'b db::IndexedBlock) -> BlockTransactionLookup<'a, 'b> { + BlockTransactionLookup { store: store, block: block, cache: HashMap::new() } + } + + fn find(&mut self, hash: &H256) -> Option<(&H256, &chain::Transaction)> { + None + } +}