diff --git a/network/src/consensus.rs b/network/src/consensus.rs index fc545ca0..a75e6ca2 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -191,6 +191,13 @@ impl ConsensusFork { } } + pub fn min_transaction_size(&self, median_time_past: u32) -> usize { + match *self { + ConsensusFork::BitcoinCash(ref fork) if median_time_past >= fork.magnetic_anomaly_time => 100, + _ => 0, + } + } + pub fn max_transaction_size(&self) -> usize { // BitcoinCash: according to REQ-5: max size of tx is still 1_000_000 // SegWit: size * 4 <= 4_000_000 ===> max size of tx is still 1_000_000 @@ -328,6 +335,14 @@ mod tests { assert_eq!(ConsensusFork::BitcoinCash(BitcoinCashConsensusParams::new(Network::Mainnet)).max_transaction_size(), 1_000_000); } + #[test] + fn test_consensus_fork_min_transaction_size() { + assert_eq!(ConsensusFork::BitcoinCore.min_transaction_size(0), 0); + assert_eq!(ConsensusFork::BitcoinCore.min_transaction_size(2000000000), 0); + assert_eq!(ConsensusFork::BitcoinCash(BitcoinCashConsensusParams::new(Network::Mainnet)).min_transaction_size(0), 0); + assert_eq!(ConsensusFork::BitcoinCash(BitcoinCashConsensusParams::new(Network::Mainnet)).min_transaction_size(2000000000), 100); + } + #[test] fn test_consensus_fork_max_block_sigops() { assert_eq!(ConsensusFork::BitcoinCore.max_block_sigops(0, 1_000_000), 20_000); diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 6dfbe561..ffa9392d 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -1,5 +1,6 @@ use primitives::hash::H256; use primitives::bytes::Bytes; +use ser::Serializable; use storage::{TransactionMetaProvider, TransactionOutputProvider}; use network::{ConsensusParams, ConsensusFork}; use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion}; @@ -13,6 +14,7 @@ use error::TransactionError; use VerificationLevel; pub struct TransactionAcceptor<'a> { + pub size: TransactionSize<'a>, pub premature_witness: TransactionPrematureWitness<'a>, pub bip30: TransactionBip30<'a>, pub missing_inputs: TransactionMissingInputs<'a>, @@ -44,6 +46,7 @@ impl<'a> TransactionAcceptor<'a> { let tx_ordering = consensus.fork.transaction_ordering(median_time_past); let missing_input_tx_index = transaction_index_for_output_check(tx_ordering,transaction_index); TransactionAcceptor { + size: TransactionSize::new(transaction, consensus, median_time_past), premature_witness: TransactionPrematureWitness::new(transaction, deployments), bip30: TransactionBip30::new_for_sync(transaction, meta_store, consensus, block_hash, height), missing_inputs: TransactionMissingInputs::new(transaction, output_store, missing_input_tx_index), @@ -56,6 +59,7 @@ impl<'a> TransactionAcceptor<'a> { } pub fn check(&self) -> Result<(), TransactionError> { + try!(self.size.check()); try!(self.premature_witness.check()); try!(self.bip30.check()); try!(self.missing_inputs.check()); @@ -69,6 +73,7 @@ impl<'a> TransactionAcceptor<'a> { } pub struct MemoryPoolTransactionAcceptor<'a> { + pub size: TransactionSize<'a>, pub missing_inputs: TransactionMissingInputs<'a>, pub maturity: TransactionMaturity<'a>, pub overspent: TransactionOverspent<'a>, @@ -95,6 +100,7 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> { let transaction_index = 0; let max_block_sigops = consensus.fork.max_block_sigops(height, consensus.fork.max_block_size(height, median_time_past)); MemoryPoolTransactionAcceptor { + size: TransactionSize::new(transaction, consensus, median_time_past), missing_inputs: TransactionMissingInputs::new(transaction, output_store, transaction_index), maturity: TransactionMaturity::new(transaction, meta_store, height), overspent: TransactionOverspent::new(transaction, output_store), @@ -108,6 +114,7 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> { pub fn check(&self) -> Result<(), TransactionError> { // Bip30 is not checked because we don't need to allow tx pool acceptance of an unspent duplicate. // Tx pool validation is not strinctly a matter of consensus. + try!(self.size.check()); try!(self.missing_inputs.check()); try!(self.maturity.check()); try!(self.overspent.check()); @@ -498,6 +505,30 @@ impl<'a> TransactionPrematureWitness<'a> { } } +pub struct TransactionSize<'a> { + transaction: CanonTransaction<'a>, + min_transaction_size: usize, +} + +impl<'a> TransactionSize<'a> { + fn new(transaction: CanonTransaction<'a>, consensus: &'a ConsensusParams, median_time_past: u32) -> Self { + let min_transaction_size = consensus.fork.min_transaction_size(median_time_past); + TransactionSize { + transaction: transaction, + min_transaction_size, + } + } + + fn check(&self) -> Result<(), TransactionError> { + let size = self.transaction.raw.serialized_size(); + if size < self.min_transaction_size { + Err(TransactionError::MinSize) + } else { + Ok(()) + } + } +} + #[cfg(test)] mod tests { use chain::{IndexedTransaction, Transaction, TransactionOutput}; @@ -505,7 +536,7 @@ mod tests { use script::Builder; use canon::CanonTransaction; use error::TransactionError; - use super::TransactionReturnReplayProtection; + use super::{TransactionReturnReplayProtection, TransactionSize}; #[test] fn return_replay_protection_works() { @@ -533,4 +564,29 @@ mod tests { let checker = TransactionReturnReplayProtection::new(CanonTransaction::new(&transaction), &consensus, 100); assert_eq!(checker.check(), Ok(())); } + + #[test] + fn transaction_size_works() { + let small_tx = Transaction::default(); + let big_tx: Transaction = "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000".into(); + let small_tx = IndexedTransaction::new(small_tx.hash(), small_tx); + let big_tx = IndexedTransaction::new(big_tx.hash(), big_tx); + let small_tx = CanonTransaction::new(&small_tx); + let big_tx = CanonTransaction::new(&big_tx); + + let unrestricted_consensus = ConsensusParams::new(Network::Unitest, ConsensusFork::BitcoinCore); + let restricted_consensus = ConsensusParams::new(Network::Unitest, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams::new(Network::Unitest))); + + // no restrictions + let checker = TransactionSize::new(small_tx, &unrestricted_consensus, 10000000); + assert_eq!(checker.check(), Ok(())); + + // big + restricted + let checker = TransactionSize::new(big_tx, &restricted_consensus, 2000000000); + assert_eq!(checker.check(), Ok(())); + + // small + restricted + let checker = TransactionSize::new(small_tx, &restricted_consensus, 2000000000); + assert_eq!(checker.check(), Err(TransactionError::MinSize)); + } } diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 246dce83..21ba951d 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -361,16 +361,20 @@ mod tests { let tx1: Transaction = test_data::TransactionBuilder::with_version(4) .add_input(&input_tx, 0) - .add_output(40) + .add_output(10).add_output(10).add_output(10) + .add_output(5).add_output(5).add_output(5) .into(); let tx2: Transaction = test_data::TransactionBuilder::with_version(1) .add_input(&tx1, 0) - .add_output(30) + .add_output(1).add_output(1).add_output(1) + .add_output(2).add_output(2).add_output(2) .into(); + assert!(tx1.hash() > tx2.hash()); + let block = test_data::block_builder() .transaction() .coinbase() - .output().value(2).build() + .output().value(2).script_pubkey_with_sigops(100).build() .build() .with_transaction(tx2) .with_transaction(tx1) diff --git a/verification/src/error.rs b/verification/src/error.rs index 849ce404..ccc7757f 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -80,6 +80,8 @@ pub enum TransactionError { CoinbaseSignatureLength(usize), /// Transaction size exceeds block size limit MaxSize, + /// Transaction size is below min size limit + MinSize, /// Transaction has more sigops than it's allowed MaxSigops, /// Transaction is a part of memory pool, but is a coinbase