diff --git a/db/src/indexed_block.rs b/db/src/indexed_block.rs index 15cf15d7..fd74c465 100644 --- a/db/src/indexed_block.rs +++ b/db/src/indexed_block.rs @@ -20,7 +20,22 @@ impl PreviousTransactionOutputProvider for IndexedBlock { impl TransactionOutputObserver for IndexedBlock { fn is_spent(&self, prevout: &OutPoint) -> Option { - self.previous_transaction_output(prevout).map(|_output| false) + // if previous transaction output appears more than once than we can safely + // tell that it's spent (double spent) + // TODO: optimize it + let spends = self.transactions.iter() + .flat_map(|tx| &tx.raw.inputs) + .filter(|input| &input.previous_output == prevout) + .take(2) + .count(); + + match spends { + 0 => None, + 1 => Some(false), + 2 => Some(true), + _ => unreachable!("spends <= 2; self.take(2); qed"), + } + //self.previous_transaction_output(prevout).map(|_output| false) } } diff --git a/db/src/transaction_meta_provider.rs b/db/src/transaction_meta_provider.rs index 54c79d0f..a13beaac 100644 --- a/db/src/transaction_meta_provider.rs +++ b/db/src/transaction_meta_provider.rs @@ -2,7 +2,7 @@ use primitives::hash::H256; use chain::OutPoint; use transaction_meta::TransactionMeta; -pub trait TransactionOutputObserver { +pub trait TransactionOutputObserver: Send + Sync { fn is_spent(&self, prevout: &OutPoint) -> Option; } diff --git a/sync/src/synchronization_verifier.rs b/sync/src/synchronization_verifier.rs index 33b916d4..b6c7204b 100644 --- a/sync/src/synchronization_verifier.rs +++ b/sync/src/synchronization_verifier.rs @@ -178,7 +178,7 @@ fn execute_verification_task { let time: u32 = get_time().sec as u32; - match verifier.verify_transaction(tx_output_provider, height, time, &transaction, 1) { + match verifier.verify_mempool_transaction(tx_output_provider, height, time, &transaction) { Ok(_) => sink.on_transaction_verification_success(transaction), Err(e) => sink.on_transaction_verification_error(&format!("{:?}", e), &transaction.hash()), } diff --git a/verification/src/accept_chain.rs b/verification/src/accept_chain.rs index 7f6f0579..a70c617f 100644 --- a/verification/src/accept_chain.rs +++ b/verification/src/accept_chain.rs @@ -22,7 +22,16 @@ impl<'a> ChainAcceptor<'a> { header: HeaderAcceptor::new(store.as_block_header_provider(), network, block.header(), height), transactions: block.transactions() .into_iter() - .map(|tx| TransactionAcceptor::new(store.as_transaction_meta_provider(), prevouts, network, tx, block.hash(), height, block.header.raw.time)) + .map(|tx| TransactionAcceptor::new( + store.as_transaction_meta_provider(), + prevouts, + block.raw(), + network, + tx, + block.hash(), + height, + block.header.raw.time + )) .collect(), } } diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 1a4d642a..fa5a6eab 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -1,5 +1,5 @@ use primitives::hash::H256; -use db::{TransactionMetaProvider, PreviousTransactionOutputProvider}; +use db::{TransactionMetaProvider, PreviousTransactionOutputProvider, TransactionOutputObserver}; use network::{Magic, ConsensusParams}; use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner}; use duplex_store::{DuplexTransactionOutputProvider}; @@ -13,6 +13,7 @@ pub struct TransactionAcceptor<'a> { pub missing_inputs: TransactionMissingInputs<'a>, pub maturity: TransactionMaturity<'a>, pub overspent: TransactionOverspent<'a>, + pub double_spent: TransactionDoubleSpend<'a>, pub eval: TransactionEval<'a>, } @@ -23,6 +24,8 @@ impl<'a> TransactionAcceptor<'a> { // previous transaction outputs // in case of block validation, that's database and currently processed block prevout_store: DuplexTransactionOutputProvider<'a>, + // in case of block validation, that's database and currently processed block + spent_store: &'a TransactionOutputObserver, network: Magic, transaction: CanonTransaction<'a>, block_hash: &'a H256, @@ -35,6 +38,7 @@ impl<'a> TransactionAcceptor<'a> { missing_inputs: TransactionMissingInputs::new(transaction, prevout_store), maturity: TransactionMaturity::new(transaction, meta_store, height), overspent: TransactionOverspent::new(transaction, prevout_store), + double_spent: TransactionDoubleSpend::new(transaction, spent_store), eval: TransactionEval::new(transaction, prevout_store, params, height, time), } } @@ -42,9 +46,9 @@ impl<'a> TransactionAcceptor<'a> { pub fn check(&self) -> Result<(), TransactionError> { try!(self.bip30.check()); try!(self.missing_inputs.check()); - // TODO: double spends try!(self.maturity.check()); try!(self.overspent.check()); + try!(self.double_spent.check()); try!(self.eval.check()); Ok(()) } @@ -54,9 +58,9 @@ impl<'a> TransactionAcceptor<'a> { pub fn check_with_eval(&self, eval: bool) -> Result<(), TransactionError> { try!(self.bip30.check()); try!(self.missing_inputs.check()); - // TODO: double spends try!(self.maturity.check()); try!(self.overspent.check()); + try!(self.double_spent.check()); if eval { try!(self.eval.check()); } @@ -70,6 +74,7 @@ pub struct MemoryPoolTransactionAcceptor<'a> { pub maturity: TransactionMaturity<'a>, pub overspent: TransactionOverspent<'a>, pub sigops: TransactionSigops<'a>, + pub double_spent: TransactionDoubleSpend<'a>, pub eval: TransactionEval<'a>, } @@ -79,6 +84,8 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> { meta_store: &'a TransactionMetaProvider, // in case of memory pool it should be db and memory pool prevout_store: DuplexTransactionOutputProvider<'a>, + // in case of memory pool it should be db and memory pool + spent_store: &'a TransactionOutputObserver, network: Magic, transaction: CanonTransaction<'a>, height: u32, @@ -91,6 +98,7 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> { maturity: TransactionMaturity::new(transaction, meta_store, height), overspent: TransactionOverspent::new(transaction, prevout_store), sigops: TransactionSigops::new(transaction, prevout_store, params.clone(), MAX_BLOCK_SIGOPS, time), + double_spent: TransactionDoubleSpend::new(transaction, spent_store), eval: TransactionEval::new(transaction, prevout_store, params, height, time), } } @@ -98,10 +106,10 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> { pub fn check(&self) -> Result<(), TransactionError> { try!(self.bip30.check()); try!(self.missing_inputs.check()); - // TODO: double spends try!(self.maturity.check()); try!(self.overspent.check()); try!(self.sigops.check()); + try!(self.double_spent.check()); try!(self.eval.check()); Ok(()) } @@ -353,3 +361,34 @@ impl<'a> TransactionRule for TransactionEval<'a> { Ok(()) } } + +pub struct TransactionDoubleSpend<'a> { + transaction: CanonTransaction<'a>, + store: &'a TransactionOutputObserver, +} + +impl<'a> TransactionDoubleSpend<'a> { + fn new(transaction: CanonTransaction<'a>, store: &'a TransactionOutputObserver) -> Self { + TransactionDoubleSpend { + transaction: transaction, + store: store, + } + } +} + +impl<'a> TransactionRule for TransactionDoubleSpend<'a> { + fn check(&self) -> Result<(), TransactionError> { + let double_spent_input = self.transaction.raw.inputs.iter() + .find(|input| self.store.is_spent(&input.previous_output).unwrap_or(false)); + + match double_spent_input { + Some(input) => { + Err(TransactionError::UsingSpentOutput( + input.previous_output.hash.clone(), + input.previous_output.index + )) + }, + None => Ok(()) + } + } +} diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index ea6fee48..31aceb03 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -6,7 +6,7 @@ use network::Magic; use error::{Error, TransactionError}; use {Verify, chain}; use canon::{CanonBlock, CanonTransaction}; -use duplex_store::DuplexTransactionOutputProvider; +use duplex_store::{DuplexTransactionOutputProvider, NoopStore}; use verify_chain::ChainVerifier; use verify_header::HeaderVerifier; use verify_transaction::MemoryPoolTransactionVerifier; @@ -95,13 +95,12 @@ impl BackwardsCompatibleChainVerifier { header_verifier.check_with_pow(!self.skip_pow) } - pub fn verify_transaction( + pub fn verify_mempool_transaction( &self, prevout_provider: &T, height: u32, time: u32, transaction: &chain::Transaction, - _sequence: usize ) -> Result<(), TransactionError> where T: PreviousTransactionOutputProvider + TransactionOutputObserver { let indexed_tx = transaction.clone().into(); // let's do preverification first @@ -110,10 +109,12 @@ impl BackwardsCompatibleChainVerifier { let canon_tx = CanonTransaction::new(&indexed_tx); // now let's do full verification - let prevouts = DuplexTransactionOutputProvider::new(self.store.as_previous_transaction_output_provider(), prevout_provider); + let noop = NoopStore; + let prevouts = DuplexTransactionOutputProvider::new(prevout_provider, &noop); let tx_acceptor = MemoryPoolTransactionAcceptor::new( self.store.as_transaction_meta_provider(), prevouts, + prevout_provider, self.network, canon_tx, height, diff --git a/verification/src/duplex_store.rs b/verification/src/duplex_store.rs index 4944a769..4c30076d 100644 --- a/verification/src/duplex_store.rs +++ b/verification/src/duplex_store.rs @@ -25,3 +25,11 @@ impl<'a> PreviousTransactionOutputProvider for DuplexTransactionOutputProvider<' .or_else(|| self.second.previous_transaction_output(prevout)) } } + +pub struct NoopStore; + +impl PreviousTransactionOutputProvider for NoopStore { + fn previous_transaction_output(&self, _prevout: &OutPoint) -> Option { + None + } +}