diff --git a/chain/src/transaction.rs b/chain/src/transaction.rs index 1f28a05c..d9ad952b 100644 --- a/chain/src/transaction.rs +++ b/chain/src/transaction.rs @@ -258,7 +258,14 @@ impl Transaction { } pub fn total_spends(&self) -> u64 { - self.outputs.iter().map(|output| output.value).sum() + let mut result = 0u64; + for output in self.outputs.iter() { + if u64::max_value() - result < output.value { + return u64::max_value(); + } + result += output.value; + } + result } } diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index 9dfaaf59..2bc309ab 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -5,7 +5,7 @@ use work::block_reward_satoshi; use duplex_store::DuplexTransactionOutputProvider; use canon::CanonBlock; use constants::MAX_BLOCK_SIGOPS; -use error::Error; +use error::{Error, TransactionError}; /// Flexible verification of ordered block pub struct BlockAcceptor<'a> { @@ -114,22 +114,40 @@ impl<'a> BlockCoinbaseClaim<'a> { impl<'a> BlockRule for BlockCoinbaseClaim<'a> { fn check(&self) -> Result<(), Error> { let store = DuplexTransactionOutputProvider::new(self.store, &*self.block); - let available = self.block.transactions.iter() - .skip(1) - .flat_map(|tx| tx.raw.inputs.iter()) - .map(|input| store.previous_transaction_output(&input.previous_output).map(|o| o.value).unwrap_or(0)) - .sum::(); - let spends = self.block.transactions.iter() - .skip(1) - .map(|tx| tx.raw.total_spends()) - .sum::(); + let mut fees: u64 = 0; + + for (tx_idx, tx) in self.block.transactions.iter().skip(1).enumerate() { + // (1) Total sum of all referenced outputs + let mut incoming: u64 = 0; + for input in tx.raw.inputs.iter() { + let (sum, overflow) = incoming.overflowing_add( + store.previous_transaction_output(&input.previous_output).map(|o| o.value).unwrap_or(0)); + if overflow { + return Err(Error::ReferencedInputsSumOverflow); + } + incoming = sum; + } + + // (2) Total sum of all outputs + let spends = tx.raw.total_spends(); + + // Difference between (1) and (2) + let (difference, overflow) = incoming.overflowing_sub(spends); + if overflow { + return Err(Error::Transaction(tx_idx + 1, TransactionError::Overspend)) + } + + // Adding to total fees (with possible overflow) + let (sum, overflow) = fees.overflowing_add(difference); + if overflow { + return Err(Error::TransactionFeesOverflow) + } + + fees = sum; + } let claim = self.block.transactions[0].raw.total_spends(); - let (fees, overflow) = available.overflowing_sub(spends); - if overflow { - return Err(Error::TransactionFeesOverflow); - } let (reward, overflow) = fees.overflowing_add(block_reward_satoshi(self.height)); if overflow { diff --git a/verification/src/error.rs b/verification/src/error.rs index 72b2f8f0..8a28bec9 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -40,6 +40,8 @@ pub enum Error { TransactionFeeAndRewardOverflow, /// Sum of the transaction fees in block exceeds u64::max TransactionFeesOverflow, + /// Sum of all referenced outputs in block transactions resulted in the overflow + ReferencedInputsSumOverflow, } #[derive(Debug, PartialEq)]