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 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> {

View File

@ -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<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(
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<usize, TransactionError> {
@ -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<u32> {
fn median_timestamp(&self, block: &db::IndexedBlock) -> Option<u32> {
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<u32> {
fn work_required(&self, block: &db::IndexedBlock, height: u32) -> Option<u32> {
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()));
}
}

View File

@ -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<Chain, Error>;
/// 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;
}

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
}
}