verificatino uses indexed blokcs

This commit is contained in:
NikVolf 2016-11-27 23:49:51 +03:00
parent 6fee34ad66
commit 67e64a5391
4 changed files with 102 additions and 48 deletions

View File

@ -1,5 +1,6 @@
use chain; use chain;
use primitives::hash::H256; use primitives::hash::H256;
use serialization::Serializable;
pub struct IndexedBlock { pub struct IndexedBlock {
header: chain::BlockHeader, header: chain::BlockHeader,
@ -73,6 +74,19 @@ impl IndexedBlock {
self.transactions.clone(), 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> { pub struct IndexedTransactions<'a> {

View File

@ -1,10 +1,11 @@
//! Bitcoin chain verifier //! Bitcoin chain verifier
use std::collections::BTreeSet; use std::collections::BTreeSet;
use db::{self, BlockRef, BlockLocation}; use db::{self, BlockRef, BlockLocation, IndexedBlock};
use network::Magic; use network::Magic;
use super::{Verify, VerificationResult, Chain, Error, TransactionError, ContinueVerify}; use super::{Verify, VerificationResult, Chain, Error, TransactionError, ContinueVerify};
use {chain, utils}; use {chain, utils};
use primitives::H256;
const BLOCK_MAX_FUTURE: i64 = 2 * 60 * 60; // 2 hours const BLOCK_MAX_FUTURE: i64 = 2 * 60 * 60; // 2 hours
const COINBASE_MATURITY: u32 = 100; // 2 hours const COINBASE_MATURITY: u32 = 100; // 2 hours
@ -56,7 +57,7 @@ impl ChainVerifier {
self 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) { if !block.is_final(at_height) {
return Err(Error::NonFinalBlock); 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 // bip30
for (tx_index, tx) in block.transactions.iter().enumerate() { for (tx_index, (tx_hash, tx)) in block.transactions().enumerate() {
if let Some(meta) = self.store.transaction_meta(&tx.hash()) { if let Some(meta) = self.store.transaction_meta(tx_hash) {
if !meta.is_fully_spent() && !consensus_params.is_bip30_exception(&block_hash, at_height) { if !meta.is_fully_spent() && !consensus_params.is_bip30_exception(&block_hash, at_height) {
return Err(Error::Transaction(tx_index, TransactionError::UnspentTransactionWithTheSameHash)); return Err(Error::Transaction(tx_index, TransactionError::UnspentTransactionWithTheSameHash));
} }
@ -85,7 +90,7 @@ impl ChainVerifier {
} }
let mut total_unspent = 0u64; 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; let mut total_claimed: u64 = 0;
@ -106,7 +111,13 @@ impl ChainVerifier {
self.store.transaction(&input.previous_output.hash) self.store.transaction(&input.previous_output.hash)
// todo: optimize block decomposition vec<transaction> -> hashmap<h256, transaction> // todo: optimize block decomposition vec<transaction> -> hashmap<h256, transaction>
.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( .ok_or(
Error::Transaction(tx_index, TransactionError::UnknownReference(input.previous_output.hash.clone())) Error::Transaction(tx_index, TransactionError::UnknownReference(input.previous_output.hash.clone()))
) )
@ -140,7 +151,8 @@ impl ChainVerifier {
} }
fn verify_transaction(&self, fn verify_transaction(&self,
block: &chain::Block, block: &db::IndexedBlock,
hash: &H256,
transaction: &chain::Transaction, transaction: &chain::Transaction,
sequence: usize, sequence: usize,
) -> Result<usize, TransactionError> { ) -> Result<usize, TransactionError> {
@ -170,7 +182,10 @@ impl ChainVerifier {
let store_parent_transaction = self.store.transaction(&input.previous_output.hash); let store_parent_transaction = self.store.transaction(&input.previous_output.hash);
let parent_transaction = store_parent_transaction let parent_transaction = store_parent_transaction
.as_ref() .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()))?; .ok_or_else(|| TransactionError::Inconclusive(input.previous_output.hash.clone()))?;
if parent_transaction.outputs.len() <= input.previous_output.index as usize { if parent_transaction.outputs.len() <= input.previous_output.index as usize {
@ -212,11 +227,11 @@ impl ChainVerifier {
Ok(sigops) Ok(sigops)
} }
fn verify_block(&self, block: &chain::Block) -> VerificationResult { fn verify_block(&self, block: &db::IndexedBlock) -> VerificationResult {
let hash = block.hash(); let hash = block.hash();
// There should be at least 1 transaction // There should be at least 1 transaction
if block.transactions().is_empty() { if block.transaction_count() == 0 {
return Err(Error::Empty); return Err(Error::Empty);
} }
@ -231,14 +246,18 @@ impl ChainVerifier {
} }
if let Some(median_timestamp) = self.median_timestamp(block) { if let Some(median_timestamp) = self.median_timestamp(block) {
if median_timestamp >= block.block_header.time { if median_timestamp >= block.header().time {
trace!(target: "verification", "median timestamp verification failed, median: {}, current: {}", median_timestamp, block.block_header.time); trace!(
target: "verification", "median timestamp verification failed, median: {}, current: {}",
median_timestamp,
block.header().time
);
return Err(Error::Timestamp); return Err(Error::Timestamp);
} }
} }
// todo: serialized_size function is at least suboptimal // todo: serialized_size function is at least suboptimal
let size = ::serialization::Serializable::serialized_size(block); let size = block.size();
if size > MAX_BLOCK_SIZE { if size > MAX_BLOCK_SIZE {
return Err(Error::Size(size)) return Err(Error::Size(size))
} }
@ -248,25 +267,25 @@ impl ChainVerifier {
return Err(Error::MerkleRoot); 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 // check first transaction is a coinbase transaction
if !block.transactions()[0].is_coinbase() { if !first_tx.is_coinbase() {
return Err(Error::Coinbase) return Err(Error::Coinbase)
} }
// check that coinbase has a valid signature // check that coinbase has a valid signature
let coinbase = &block.transactions()[0];
// is_coinbase() = true above guarantees that there is at least one input // 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 { if coinbase_script_len < 2 || coinbase_script_len > 100 {
return Err(Error::CoinbaseSignatureLength(coinbase_script_len)); return Err(Error::CoinbaseSignatureLength(coinbase_script_len));
} }
// transaction verification including number of signature operations checking // transaction verification including number of signature operations checking
let mut block_sigops = 0; 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!( block_sigops += try!(
self.verify_transaction( self.verify_transaction(
block, block,
tx_hash,
transaction, transaction,
idx, idx,
).map_err(|e| Error::Transaction(idx, e)) ).map_err(|e| Error::Transaction(idx, e))
@ -293,9 +312,9 @@ impl ChainVerifier {
} }
} }
fn median_timestamp(&self, block: &chain::Block) -> Option<u32> { fn median_timestamp(&self, block: &db::IndexedBlock) -> Option<u32> {
let mut timestamps = BTreeSet::new(); 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 // TODO: optimize it, so it does not make 11 redundant queries each time
for _ in 0..11 { for _ in 0..11 {
let previous_header = match self.store.block_header(block_ref) { let previous_header = match self.store.block_header(block_ref) {
@ -313,12 +332,12 @@ impl ChainVerifier {
else { None } else { None }
} }
fn work_required(&self, block: &chain::Block, height: u32) -> Option<u32> { fn work_required(&self, block: &db::IndexedBlock, height: u32) -> Option<u32> {
if height == 0 { if height == 0 {
return None; 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"); let previous_header = self.store.block_header(previous_ref).expect("self.height != 0; qed");
if utils::is_retarget_height(height) { if utils::is_retarget_height(height) {
@ -341,12 +360,12 @@ impl ChainVerifier {
} }
impl Verify for 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); let result = self.verify_block(block);
trace!( trace!(
target: "verification", "Block {} (transactions: {}) verification finished. Result {:?}", target: "verification", "Block {} (transactions: {}) verification finished. Result {:?}",
block.hash().to_reversed_str(), block.hash().to_reversed_str(),
block.transactions().len(), block.transaction_count(),
result, result,
); );
result result
@ -356,10 +375,10 @@ impl Verify for ChainVerifier {
impl ContinueVerify for ChainVerifier { impl ContinueVerify for ChainVerifier {
type State = usize; 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) // verify transactions (except coinbase)
for (idx, transaction) in block.transactions().iter().enumerate().skip(state - 1) { for (idx, (tx_hash, transaction)) in block.transactions().enumerate().skip(state - 1) {
try!(self.verify_transaction(block, transaction, idx).map_err(|e| Error::Transaction(idx, e))); 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())) { let _parent = match self.store.block(BlockRef::Hash(block.header().previous_header_hash.clone())) {
@ -374,7 +393,7 @@ impl ContinueVerify for ChainVerifier {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use db::{TestStorage, Storage, Store, BlockStapler}; use db::{TestStorage, Storage, Store, BlockStapler, IndexedBlock};
use network::Magic; use network::Magic;
use devtools::RandomTempPath; use devtools::RandomTempPath;
use {script, test_data}; use {script, test_data};
@ -387,7 +406,7 @@ mod tests {
let b2 = test_data::block_h2(); let b2 = test_data::block_h2();
let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet); 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] #[test]
@ -395,7 +414,7 @@ mod tests {
let storage = TestStorage::with_blocks(&vec![test_data::genesis()]); let storage = TestStorage::with_blocks(&vec![test_data::genesis()]);
let b1 = test_data::block_h1(); let b1 = test_data::block_h1();
let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet); 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] #[test]
@ -408,7 +427,7 @@ mod tests {
); );
let b1 = test_data::block_h170(); let b1 = test_data::block_h170();
let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet); 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] #[test]
@ -425,7 +444,7 @@ mod tests {
1, 1,
TransactionError::Inconclusive("c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd3704".into()) TransactionError::Inconclusive("c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd3704".into())
)); ));
assert_eq!(should_be, verifier.verify(&b170)); assert_eq!(should_be, verifier.verify(&b170.into()));
} }
#[test] #[test]
@ -460,7 +479,7 @@ mod tests {
TransactionError::Maturity, TransactionError::Maturity,
)); ));
assert_eq!(expected, verifier.verify(&block)); assert_eq!(expected, verifier.verify(&block.into()));
} }
#[test] #[test]
@ -493,7 +512,7 @@ mod tests {
let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip();
let expected = Ok(Chain::Main); 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 verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip();
let expected = Ok(Chain::Main); let expected = Ok(Chain::Main);
assert_eq!(expected, verifier.verify(&block)); assert_eq!(expected, verifier.verify(&block.into()));
} }
#[test] #[test]
@ -570,7 +589,7 @@ mod tests {
let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip();
let expected = Err(Error::Transaction(2, TransactionError::Overspend)); let expected = Err(Error::Transaction(2, TransactionError::Overspend));
assert_eq!(expected, verifier.verify(&block)); assert_eq!(expected, verifier.verify(&block.into()));
} }
#[test] #[test]
@ -615,7 +634,7 @@ mod tests {
let expected = Ok(Chain::Main); let expected = Ok(Chain::Main);
assert_eq!(expected, verifier.verify(&block)) assert_eq!(expected, verifier.verify(&block.into()))
} }
#[test] #[test]
@ -646,7 +665,7 @@ mod tests {
builder_tx2 = builder_tx2.push_opcode(script::Opcode::OP_CHECKSIG) 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().coinbase().build()
.transaction() .transaction()
.input() .input()
@ -661,12 +680,13 @@ mod tests {
.build() .build()
.build() .build()
.merkled_header().parent(genesis.hash()).build() .merkled_header().parent(genesis.hash()).build()
.build(); .build()
.into();
let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip();
let expected = Err(Error::MaximumSigops); let expected = Err(Error::MaximumSigops);
assert_eq!(expected, verifier.verify(&block)); assert_eq!(expected, verifier.verify(&block.into()));
} }
#[test] #[test]
@ -681,13 +701,14 @@ mod tests {
.build(); .build();
storage.insert_block(&genesis).unwrap(); storage.insert_block(&genesis).unwrap();
let block = test_data::block_builder() let block: IndexedBlock = test_data::block_builder()
.transaction() .transaction()
.coinbase() .coinbase()
.output().value(5000000001).build() .output().value(5000000001).build()
.build() .build()
.merkled_header().parent(genesis.hash()).build() .merkled_header().parent(genesis.hash()).build()
.build(); .build()
.into();
let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip(); let verifier = ChainVerifier::new(Arc::new(storage), Magic::Testnet).pow_skip().signatures_skip();
@ -696,6 +717,6 @@ mod tests {
actual: 5000000001 actual: 5000000001
}); });
assert_eq!(expected, verifier.verify(&block)); assert_eq!(expected, verifier.verify(&block.into()));
} }
} }

View File

@ -21,12 +21,11 @@ extern crate test_data;
mod chain_verifier; mod chain_verifier;
mod compact; mod compact;
mod queue;
mod utils; mod utils;
mod lookup;
pub use primitives::{uint, hash}; pub use primitives::{uint, hash};
pub use queue::Queue;
pub use chain_verifier::ChainVerifier; pub use chain_verifier::ChainVerifier;
use primitives::hash::H256; use primitives::hash::H256;
@ -116,11 +115,11 @@ pub type VerificationResult = Result<Chain, Error>;
/// Interface for block verification /// Interface for block verification
pub trait Verify : Send + Sync { 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 /// Trait for verifier that can be interrupted and continue from the specific point
pub trait ContinueVerify : Verify + Send + Sync { pub trait ContinueVerify : Verify + Send + Sync {
type State; type State;
fn continue_verify(&self, block: &chain::Block, state: Self::State) -> VerificationResult; fn continue_verify(&self, block: &db::IndexedBlock, state: Self::State) -> VerificationResult;
} }

View File

@ -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<H256, chain::Transaction>,
}
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
}
}