diff --git a/Cargo.lock b/Cargo.lock index 8424d47d..25813108 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,6 @@ dependencies = [ "chain 0.1.0", "db 0.1.0", "ethcore-devtools 1.3.0", - "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "network 0.1.0", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/db/src/transaction_provider.rs b/db/src/transaction_provider.rs index 0603a92b..2d51269e 100644 --- a/db/src/transaction_provider.rs +++ b/db/src/transaction_provider.rs @@ -28,5 +28,6 @@ pub trait AsTransactionProvider { pub trait PreviousTransactionOutputProvider { fn previous_transaction_output(&self, prevout: &chain::OutPoint) -> Option; + // TODO: this should not be here, cause it requires meta data fn is_spent(&self, prevout: &chain::OutPoint) -> bool; } diff --git a/miner/src/fee.rs b/miner/src/fee.rs index ae13e350..c2045096 100644 --- a/miner/src/fee.rs +++ b/miner/src/fee.rs @@ -1,20 +1,18 @@ use chain::Transaction; +use ser::Serializable; use db::TransactionProvider; pub fn transaction_fee(store: &TransactionProvider, transaction: &Transaction) -> u64 { - let inputs_sum = transaction.inputs.iter() - .fold(0, |accumulator, input| { - let input_transaction = store.transaction(&input.previous_output.hash) - .expect("transaction must be verified by caller"); - accumulator + input_transaction.outputs[input.previous_output.index as usize].value - }); + let inputs_sum = transaction.inputs.iter().map(|input| { + let input_transaction = store.transaction(&input.previous_output.hash) + .expect("transaction must be verified by caller"); + input_transaction.outputs[input.previous_output.index as usize].value + }).sum::(); let outputs_sum = transaction.outputs.iter().map(|output| output.value).sum(); inputs_sum.saturating_sub(outputs_sum) } pub fn transaction_fee_rate(store: &TransactionProvider, transaction: &Transaction) -> u64 { - use ser::Serializable; - transaction_fee(store, transaction) / transaction.serialized_size() as u64 } diff --git a/verification/Cargo.toml b/verification/Cargo.toml index 318c720c..78064724 100644 --- a/verification/Cargo.toml +++ b/verification/Cargo.toml @@ -6,7 +6,6 @@ authors = ["Nikolay Volf "] [dependencies] byteorder = "0.5" parking_lot = "0.3" -linked-hash-map = "0.3" time = "0.1" log = "0.3" scoped-pool = "1.0" diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index d3ae471e..6372ef5a 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -5,8 +5,8 @@ use scoped_pool::Pool; use hash::H256; use db::{self, BlockLocation, PreviousTransactionOutputProvider, BlockHeaderProvider}; use network::{Magic, ConsensusParams}; -use script::Script; use error::{Error, TransactionError}; +use sigops::{StoreWithUnretainedOutputs, transaction_sigops}; use {Verify, chain, utils}; const BLOCK_MAX_FUTURE: i64 = 2 * 60 * 60; // 2 hours @@ -72,49 +72,14 @@ impl ChainVerifier { height >= self.consensus_params.bip65_height } - /// Returns previous transaction output. - /// NOTE: This function expects all previous blocks to be already in database. - fn previous_transaction_output(&self, prevout_provider: &T, prevout: &chain::OutPoint) -> Option - where T: PreviousTransactionOutputProvider { - self.store.transaction(&prevout.hash) - .and_then(|tx| tx.outputs.into_iter().nth(prevout.index as usize)) - .or_else(|| prevout_provider.previous_transaction_output(prevout)) - } - - /// Returns number of transaction signature operations. - /// NOTE: This function expects all previous blocks to be already in database. - fn transaction_sigops(&self, block: &db::IndexedBlock, transaction: &chain::Transaction, bip16_active: bool) -> usize { - let output_sigops: usize = transaction.outputs.iter().map(|output| { - let output_script: Script = output.script_pubkey.clone().into(); - output_script.sigops_count(false) - }).sum(); - - if transaction.is_coinbase() { - return output_sigops; - } - - let input_sigops: usize = transaction.inputs.iter().map(|input| { - let input_script: Script = input.script_sig.clone().into(); - let mut sigops = input_script.sigops_count(false); - if bip16_active { - let previous_output = self.previous_transaction_output(block, &input.previous_output) - .expect("missing tx, out of order verification or malformed db"); - let prevout_script: Script = previous_output.script_pubkey.into(); - sigops += input_script.pay_to_script_hash_sigops(&prevout_script); - } - sigops - }).sum(); - - input_sigops + output_sigops - } - /// Returns number of block signature operations. /// NOTE: This function expects all previous blocks to be already in database. fn block_sigops(&self, block: &db::IndexedBlock) -> usize { // strict pay-to-script-hash signature operations count toward block // signature operations limit is enforced with BIP16 + let store = StoreWithUnretainedOutputs::new(self.store.clone(), block); let bip16_active = self.verify_p2sh(block.header().time); - block.transactions().map(|(_, tx)| self.transaction_sigops(block, tx, bip16_active)).sum() + block.transactions().map(|(_, tx)| transaction_sigops(tx, &store, bip16_active)).sum() } fn ordered_verify(&self, block: &db::IndexedBlock, at_height: u32) -> Result<(), Error> { @@ -161,6 +126,7 @@ impl ChainVerifier { } } + let unretained_store = StoreWithUnretainedOutputs::new(self.store.clone(), block); let mut total_unspent = 0u64; for (tx_index, (_, tx)) in block.transactions().enumerate().skip(1) { let mut total_claimed: u64 = 0; @@ -175,7 +141,7 @@ impl ChainVerifier { } } - let previous_output = self.previous_transaction_output(block, &input.previous_output) + let previous_output = unretained_store.previous_transaction_output(&input.previous_output) .expect("missing tx, out of order verification or malformed db"); total_claimed += previous_output.value; @@ -223,10 +189,11 @@ impl ChainVerifier { // must not be coinbase (sequence = 0 is returned above) if transaction.is_coinbase() { return Err(TransactionError::MisplacedCoinbase(sequence)); } + let unretained_store = StoreWithUnretainedOutputs::new(self.store.clone(), prevout_provider); for (input_index, input) in transaction.inputs().iter().enumerate() { // signature verification let signer: TransactionInputSigner = transaction.clone().into(); - let paired_output = match self.previous_transaction_output(prevout_provider, &input.previous_output) { + let paired_output = match unretained_store.previous_transaction_output(&input.previous_output) { Some(output) => output, _ => return Err(TransactionError::UnknownReference(input.previous_output.hash.clone())) }; diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 801039c2..c364ea15 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -2,10 +2,10 @@ extern crate byteorder; extern crate parking_lot; -extern crate linked_hash_map; extern crate time; #[macro_use] extern crate log; +extern crate scoped_pool; extern crate db; extern crate chain; @@ -13,7 +13,6 @@ extern crate network; extern crate primitives; extern crate serialization; extern crate script; -extern crate scoped_pool; #[cfg(test)] extern crate ethcore_devtools as devtools; @@ -22,6 +21,7 @@ extern crate test_data; mod chain_verifier; mod error; +mod sigops; mod task; mod utils; @@ -29,6 +29,7 @@ pub use primitives::{uint, hash, compact}; pub use chain_verifier::{Chain, ChainVerifier, VerificationResult}; pub use error::{Error, TransactionError}; +pub use sigops::{transaction_sigops, StoreWithUnretainedOutputs}; pub use utils::{work_required, is_valid_proof_of_work, is_valid_proof_of_work_hash, block_reward_satoshi}; /// Interface for block verification diff --git a/verification/src/sigops.rs b/verification/src/sigops.rs new file mode 100644 index 00000000..e7b9f6ec --- /dev/null +++ b/verification/src/sigops.rs @@ -0,0 +1,58 @@ +use chain::{Transaction, TransactionOutput, OutPoint}; +use db::{PreviousTransactionOutputProvider, SharedStore}; +use script::Script; + +pub struct StoreWithUnretainedOutputs<'a, T> where T: 'a { + store: SharedStore, + outputs: &'a T, +} + +impl<'a, T> StoreWithUnretainedOutputs<'a, T> where T: PreviousTransactionOutputProvider { + pub fn new(store: SharedStore, outputs: &'a T) -> Self { + StoreWithUnretainedOutputs { + store: store, + outputs: outputs, + } + } +} + +impl<'a, T> PreviousTransactionOutputProvider for StoreWithUnretainedOutputs<'a, T> where T: PreviousTransactionOutputProvider { + fn previous_transaction_output(&self, prevout: &OutPoint) -> Option { + self.store.transaction(&prevout.hash) + .and_then(|tx| tx.outputs.into_iter().nth(prevout.index as usize)) + .or_else(|| self.outputs.previous_transaction_output(prevout)) + } + + fn is_spent(&self, _prevout: &OutPoint) -> bool { + unimplemented!(); + } +} + +pub fn transaction_sigops( + transaction: &Transaction, + store: &PreviousTransactionOutputProvider, + bip16_active: bool +) -> usize { + let output_sigops: usize = transaction.outputs.iter().map(|output| { + let output_script: Script = output.script_pubkey.clone().into(); + output_script.sigops_count(false) + }).sum(); + + if transaction.is_coinbase() { + return output_sigops; + } + + let input_sigops: usize = transaction.inputs.iter().map(|input| { + let input_script: Script = input.script_sig.clone().into(); + let mut sigops = input_script.sigops_count(false); + if bip16_active { + let previous_output = store.previous_transaction_output(&input.previous_output) + .expect("missing tx, out of order verification or malformed db"); + let prevout_script: Script = previous_output.script_pubkey.into(); + sigops += input_script.pay_to_script_hash_sigops(&prevout_script); + } + sigops + }).sum(); + + input_sigops + output_sigops +}