// Copyright 2015-2020 Parity Technologies (UK) Ltd. // This file is part of OpenEthereum. // OpenEthereum is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // OpenEthereum is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with OpenEthereum. If not, see . use ethereum_types::U256; use hash::KECCAK_EMPTY; use txpool; use types::transaction::{self, PendingTransaction}; use pool::{verifier, PendingOrdering, PendingSettings, PrioritizationStrategy, TransactionQueue}; pub mod client; pub mod tx; use self::{ client::TestClient, tx::{PairExt, Tx, TxExt}, }; // max mem for 3 transaction, this is relative // to the global use allocator, the value is currently // set to reflect malloc usage. // 50 was enough when using jmalloc. const TEST_QUEUE_MAX_MEM: usize = 100; fn new_queue() -> TransactionQueue { TransactionQueue::new( txpool::Options { max_count: 3, max_per_sender: 3, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ) } #[test] fn should_return_correct_nonces_when_dropped_because_of_limit() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 3, max_per_sender: 1, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ); let (tx1, tx2) = Tx::gas_price(2).signed_pair(); let sender = tx1.sender(); let nonce = tx1.tx().nonce; // when let r1 = txq.import(TestClient::new(), vec![tx1].retracted()); let r2 = txq.import(TestClient::new(), vec![tx2].retracted()); assert_eq!(r1, vec![Ok(())]); assert_eq!(r2, vec![Err(transaction::Error::LimitReached)]); assert_eq!(txq.status().status.transaction_count, 1); // then assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(nonce + 1)); // when let tx1 = Tx::gas_price(2).signed(); let tx2 = Tx::gas_price(2).signed(); let sender = tx2.sender(); let tx3 = Tx::gas_price(1).signed(); let tx4 = Tx::gas_price(3).signed(); let res = txq.import(TestClient::new(), vec![tx1, tx2].retracted()); let res2 = txq.import(TestClient::new(), vec![tx3, tx4].retracted()); // then assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!( res2, vec![ // The error here indicates reaching the limit // and minimal effective gas price taken into account. Err(transaction::Error::TooCheapToReplace { prev: Some(2.into()), new: Some(1.into()) }), Ok(()) ] ); assert_eq!(txq.status().status.transaction_count, 3); // tx2 transaction got dropped because of limit // tx1 and tx1' are kept, because they have lower insertion_ids so they are preferred. assert_eq!(txq.next_nonce(TestClient::new(), &sender), None); } #[test] fn should_never_drop_local_transactions_from_different_senders() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 3, max_per_sender: 1, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ); let (tx1, tx2) = Tx::gas_price(2).signed_pair(); let sender = tx1.sender(); let nonce = tx1.tx().nonce; // when let r1 = txq.import(TestClient::new(), vec![tx1].local()); let r2 = txq.import(TestClient::new(), vec![tx2].local()); assert_eq!(r1, vec![Ok(())]); assert_eq!(r2, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); // then assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(nonce + 2)); // when let tx1 = Tx::gas_price(2).signed(); let tx2 = Tx::gas_price(2).signed(); let tx3 = Tx::gas_price(1).signed(); let tx4 = Tx::gas_price(3).signed(); let res = txq.import(TestClient::new(), vec![tx1, tx2].local()); let res2 = txq.import(TestClient::new(), vec![tx3, tx4].local()); // then assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(res2, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 6); assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(nonce + 2)); } #[test] fn should_handle_same_transaction_imported_twice_with_different_state_nonces() { // given let txq = new_queue(); let (tx, tx2) = Tx::default().signed_replacement(); let hash = tx2.hash(); let client = TestClient::new().with_nonce(122); // First insert one transaction to future let res = txq.import(client.clone(), vec![tx].local()); assert_eq!(res, vec![Ok(())]); // next_nonce === None -> transaction is in future assert_eq!(txq.next_nonce(client.clone(), &tx2.sender()), None); // now import second transaction to current let res = txq.import(TestClient::new(), vec![tx2.local()]); // and then there should be only one transaction in current (the one with higher gas_price) assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(top[0].hash, hash); } #[test] fn should_move_all_transactions_from_future() { // given let txq = new_queue(); let txs = Tx::default().signed_pair(); let (hash, hash2) = txs.hash(); let (tx, tx2) = txs; let client = TestClient::new().with_nonce(122); // First insert one transaction to future let res = txq.import(client.clone(), vec![tx.local()]); assert_eq!(res, vec![Ok(())]); // next_nonce === None -> transaction is in future assert_eq!(txq.next_nonce(client.clone(), &tx2.sender()), None); // now import second transaction to current let res = txq.import(client.clone(), vec![tx2.local()]); // then assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(top[0].hash, hash); assert_eq!(top[1].hash, hash2); } #[test] fn should_drop_transactions_from_senders_without_balance() { // given let txq = new_queue(); let tx = Tx::default().signed(); let client = TestClient::new().with_balance(1); // when let res = txq.import(client, vec![tx.local()]); // then assert_eq!( res, vec![Err(transaction::Error::InsufficientBalance { balance: U256::from(1), cost: U256::from(21_100), })] ); assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_not_import_transaction_below_min_gas_price_threshold_if_external() { // given let txq = new_queue(); let tx = Tx::default(); txq.set_verifier_options(verifier::Options { minimal_gas_price: 3.into(), ..Default::default() }); // when let res = txq.import(TestClient::new(), vec![tx.signed().unverified()]); // then assert_eq!( res, vec![Err(transaction::Error::InsufficientGasPrice { minimal: U256::from(3), got: U256::from(1), })] ); assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_import_transaction_below_min_gas_price_threshold_if_local() { // given let txq = new_queue(); let tx = Tx::default(); txq.set_verifier_options(verifier::Options { minimal_gas_price: 3.into(), ..Default::default() }); // when let res = txq.import(TestClient::new(), vec![tx.signed().local()]); // then assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_reject_transaction_from_non_eoa_if_non_eoa_sender_is_not_allowed() { // given let txq = new_queue(); let tx = Tx::default(); let code_hash = [ 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, ]; // when let res = txq.import( TestClient::new().with_code_hash(code_hash), vec![tx.signed().unverified()], ); // then assert_eq!(res, vec![Err(transaction::Error::SenderIsNotEOA)]); assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_import_transaction_from_non_eoa_if_non_eoa_sender_is_allowed() { // given let txq = new_queue(); let tx = Tx::default(); let code_hash = [ 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, 0x0c, 0x0a, 0x0f, 0x0e, ]; txq.set_verifier_options(verifier::Options { allow_non_eoa_sender: true, ..Default::default() }); // when let res = txq.import( TestClient::new().with_code_hash(code_hash), vec![tx.signed().unverified()], ); // then assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_import_transaction_if_account_code_hash_is_keccak_empty() { // given let txq = new_queue(); let tx = Tx::default(); // when let res = txq.import( TestClient::new().with_code_hash(KECCAK_EMPTY), vec![tx.signed().unverified()], ); // then assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_import_txs_from_same_sender() { // given let txq = new_queue(); let txs = Tx::default().signed_pair(); let (hash, hash2) = txs.hash(); // when txq.import(TestClient::new(), txs.local().into_vec()); // then let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(top[0].hash, hash); assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); } #[test] fn should_prioritize_local_transactions_within_same_nonce_height() { // given let txq = new_queue(); let tx = Tx::default().signed(); // the second one has same nonce but higher `gas_price` let tx2 = Tx::gas_price(2).signed(); let (hash, hash2) = (tx.hash(), tx2.hash()); let client = TestClient::new().with_local(&tx.sender()); // when // first insert the one with higher gas price let res = txq.import(client.clone(), vec![tx.local(), tx2.unverified()]); assert_eq!(res, vec![Ok(()), Ok(())]); // then let top = txq.pending(client, PendingSettings::all_prioritized(0, 0)); assert_eq!(top[0].hash, hash); // local should be first assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); } #[test] fn should_prioritize_reimported_transactions_within_same_nonce_height() { // given let txq = new_queue(); let tx = Tx::default().signed(); // the second one has same nonce but higher `gas_price` let tx2 = Tx::gas_price(2).signed(); let (hash, hash2) = (tx.hash(), tx2.hash()); // when // first insert local one with higher gas price // then the one with lower gas price, but from retracted block let res = txq.import(TestClient::new(), vec![tx2.unverified(), tx.retracted()]); assert_eq!(res, vec![Ok(()), Ok(())]); // then let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(top[0].hash, hash); // retracted should be first assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); } #[test] fn should_not_prioritize_local_transactions_with_different_nonce_height() { // given let txq = new_queue(); let txs = Tx::default().signed_pair(); let (hash, hash2) = txs.hash(); let (tx, tx2) = txs; // when let res = txq.import(TestClient::new(), vec![tx.unverified(), tx2.local()]); assert_eq!(res, vec![Ok(()), Ok(())]); // then let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(top[0].hash, hash); assert_eq!(top[1].hash, hash2); assert_eq!(top.len(), 2); } #[test] fn should_put_transaction_to_futures_if_gap_detected() { // given let txq = new_queue(); let (tx, _, tx2) = Tx::default().signed_triple(); let hash = tx.hash(); // when let res = txq.import(TestClient::new(), vec![tx, tx2].local()); // then assert_eq!(res, vec![Ok(()), Ok(())]); let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(top.len(), 1); assert_eq!(top[0].hash, hash); } #[test] fn should_handle_min_block() { // given let txq = new_queue(); let (tx, tx2) = Tx::default().signed_pair(); // when let res = txq.import( TestClient::new(), vec![ verifier::Transaction::Local(PendingTransaction::new( tx, transaction::Condition::Number(1).into(), )), tx2.local(), ], ); assert_eq!(res, vec![Ok(()), Ok(())]); // then let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(top.len(), 0); let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(1, 0)); assert_eq!(top.len(), 2); } #[test] fn should_correctly_update_futures_when_removing() { // given let txq = new_queue(); let txs = Tx::default().signed_pair(); let res = txq.import(TestClient::new().with_nonce(121), txs.local().into_vec()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); // when txq.cull(TestClient::new().with_nonce(125)); // should remove both transactions since they are stalled // then assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_move_transactions_if_gap_filled() { // given let txq = new_queue(); let (tx, tx1, tx2) = Tx::default().signed_triple(); let res = txq.import(TestClient::new(), vec![tx, tx2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 1 ); // when let res = txq.import(TestClient::new(), vec![tx1.local()]); assert_eq!(res, vec![Ok(())]); // then assert_eq!(txq.status().status.transaction_count, 3); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 3 ); } #[test] fn should_remove_transaction() { // given let txq = new_queue(); let (tx, _, tx2) = Tx::default().signed_triple(); let res = txq.import(TestClient::default(), vec![tx, tx2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 1 ); // when txq.cull(TestClient::new().with_nonce(124)); assert_eq!(txq.status().status.transaction_count, 1); assert_eq!( txq.pending( TestClient::new().with_nonce(125), PendingSettings::all_prioritized(0, 0) ) .len(), 1 ); txq.cull(TestClient::new().with_nonce(126)); // then assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_move_transactions_to_future_if_gap_introduced() { // given let txq = new_queue(); let (tx, tx2) = Tx::default().signed_pair(); let hash = tx.hash(); let tx3 = Tx::default().signed(); let res = txq.import(TestClient::new(), vec![tx3, tx2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 1 ); let res = txq.import(TestClient::new(), vec![tx].local()); assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 3); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 3 ); // when txq.remove(vec![&hash], true); // then assert_eq!(txq.status().status.transaction_count, 2); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 1 ); } #[test] fn should_clear_queue() { // given let txq = new_queue(); let txs = Tx::default().signed_pair(); // add txq.import(TestClient::new(), txs.local().into_vec()); assert_eq!(txq.status().status.transaction_count, 2); // when txq.clear(); // then assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_prefer_current_transactions_when_hitting_the_limit() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 1, max_per_sender: 2, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ); let (tx, tx2) = Tx::default().signed_pair(); let hash = tx.hash(); let sender = tx.sender(); let res = txq.import(TestClient::new(), vec![tx2.unverified()]); assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); // when let res = txq.import(TestClient::new(), vec![tx.unverified()]); // then assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); let top = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(top.len(), 1); assert_eq!(top[0].hash, hash); assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(124.into())); } #[test] fn should_drop_transactions_with_old_nonces() { let txq = new_queue(); let tx = Tx::default().signed(); // when let res = txq.import(TestClient::new().with_nonce(125), vec![tx.unverified()]); // then assert_eq!(res, vec![Err(transaction::Error::Old)]); assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_not_insert_same_transaction_twice() { // given let txq = new_queue(); let (_tx1, tx2) = Tx::default().signed_pair(); let res = txq.import(TestClient::new(), vec![tx2.clone().local()]); assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); // when let res = txq.import(TestClient::new(), vec![tx2.local()]); // then assert_eq!(res, vec![Err(transaction::Error::AlreadyImported)]); assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_accept_same_transaction_twice_if_removed() { // given let txq = new_queue(); let txs = Tx::default().signed_pair(); let (tx1, _) = txs.clone(); let (hash, _) = txs.hash(); let res = txq.import(TestClient::new(), txs.local().into_vec()); assert_eq!(res, vec![Ok(()), Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 2 ); // when txq.remove(vec![&hash], true); assert_eq!(txq.status().status.transaction_count, 1); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 0 ); let res = txq.import(TestClient::new(), vec![tx1].local()); assert_eq!(res, vec![Ok(())]); // then assert_eq!(txq.status().status.transaction_count, 2); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 2 ); } #[test] fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { // given let txq = new_queue(); let (tx, tx2) = Tx::gas_price(20).signed_replacement(); let (tx3, tx4) = Tx::gas_price(1).signed_replacement(); let client = TestClient::new().with_balance(1_000_000); // when let res = txq.import(client.clone(), vec![tx, tx3].local()); assert_eq!(res, vec![Ok(()), Ok(())]); let res = txq.import(client.clone(), vec![tx2, tx4].local()); // then assert_eq!( res, vec![ Err(transaction::Error::TooCheapToReplace { prev: None, new: None }), Ok(()) ] ); assert_eq!(txq.status().status.transaction_count, 2); assert_eq!( txq.pending(client.clone(), PendingSettings::all_prioritized(0, 0))[0] .signed() .tx() .gas_price, U256::from(20) ); assert_eq!( txq.pending(client.clone(), PendingSettings::all_prioritized(0, 0))[1] .signed() .tx() .gas_price, U256::from(2) ); } #[test] fn should_return_none_when_transaction_from_given_address_does_not_exist() { // given let txq = new_queue(); // then assert_eq!(txq.next_nonce(TestClient::new(), &Default::default()), None); } #[test] fn should_return_correct_nonce_when_transactions_from_given_address_exist() { // given let txq = new_queue(); let tx = Tx::default().signed(); let from = tx.sender(); let nonce = tx.tx().nonce; // when txq.import(TestClient::new(), vec![tx.local()]); // then assert_eq!(txq.next_nonce(TestClient::new(), &from), Some(nonce + 1)); } #[test] fn should_return_valid_last_nonce_after_cull() { // given let txq = new_queue(); let (tx1, _, tx2) = Tx::default().signed_triple(); let sender = tx1.sender(); // when // Second should go to future let res = txq.import(TestClient::new(), vec![tx1, tx2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); // Now block is imported let client = TestClient::new().with_nonce(124); txq.cull(client.clone()); // tx2 should be not be promoted to current assert_eq!( txq.pending(client.clone(), PendingSettings::all_prioritized(0, 0)) .len(), 0 ); // then assert_eq!(txq.next_nonce(client.clone(), &sender), None); assert_eq!( txq.next_nonce(client.with_nonce(125), &sender), Some(126.into()) ); } #[test] fn should_return_true_if_there_is_local_transaction_pending() { // given let txq = new_queue(); let (tx1, tx2) = Tx::default().signed_pair(); assert_eq!(txq.has_local_pending_transactions(), false); let client = TestClient::new().with_local(&tx1.sender()); // when let res = txq.import(client.clone(), vec![tx1.unverified(), tx2.local()]); assert_eq!(res, vec![Ok(()), Ok(())]); // then assert_eq!(txq.has_local_pending_transactions(), true); } #[test] fn should_reject_transactions_below_base_gas() { // given let txq = new_queue(); let tx = Tx::default().signed(); // when let res = txq.import( TestClient::new().with_gas_required(100_001), vec![tx].local(), ); // then assert_eq!( res, vec![Err(transaction::Error::InsufficientGas { minimal: 100_001.into(), got: 21_000.into(), })] ); } #[test] fn should_remove_out_of_date_transactions_occupying_queue() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 105, max_per_sender: 3, max_mem_usage: 5_000_000, }, verifier::Options { minimal_gas_price: 10.into(), ..Default::default() }, PrioritizationStrategy::GasPriceOnly, ); // that transaction will be occupying the queue let (_, tx) = Tx::default().signed_pair(); let res = txq.import(TestClient::new(), vec![tx.local()]); assert_eq!(res, vec![Ok(())]); // This should not clear the transaction (yet) txq.cull(TestClient::new()); assert_eq!(txq.status().status.transaction_count, 1); // Now insert at least 100 transactions to have the other one marked as future. for _ in 0..34 { let (tx1, tx2, tx3) = Tx::default().signed_triple(); txq.import(TestClient::new(), vec![tx1, tx2, tx3].local()); } assert_eq!(txq.status().status.transaction_count, 103); // when txq.cull(TestClient::new()); // then assert_eq!(txq.status().status.transaction_count, 102); } #[test] fn should_accept_local_transactions_below_min_gas_price() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 3, max_per_sender: 3, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 10.into(), ..Default::default() }, PrioritizationStrategy::GasPriceOnly, ); let tx = Tx::gas_price(1).signed(); // when let res = txq.import(TestClient::new(), vec![tx.local()]); assert_eq!(res, vec![Ok(())]); // then assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 1 ); } #[test] fn should_accept_local_service_transaction() { // given let txq = new_queue(); let tx = Tx::gas_price(0).signed(); // when let res = txq.import(TestClient::new().with_local(&tx.sender()), vec![tx.local()]); assert_eq!(res, vec![Ok(())]); // then assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)) .len(), 1 ); } #[test] fn should_not_accept_external_service_transaction_if_sender_not_certified() { // given let txq = new_queue(); let tx1 = Tx::gas_price(0).signed().unverified(); let tx2 = Tx::gas_price(0).signed().retracted(); let tx3 = Tx::gas_price(0).signed().unverified(); // when let res = txq.import(TestClient::new(), vec![tx1, tx2]); assert_eq!( res, vec![ Err(transaction::Error::InsufficientGasPrice { minimal: 1.into(), got: 0.into(), }), Err(transaction::Error::InsufficientGasPrice { minimal: 1.into(), got: 0.into(), }), ] ); // then let res = txq.import(TestClient::new().with_service_transaction(), vec![tx3]); assert_eq!(res, vec![Ok(())]); } #[test] fn should_not_return_transactions_over_nonce_cap() { // given let txq = new_queue(); let (tx1, tx2, tx3) = Tx::default().signed_triple(); let res = txq.import(TestClient::new(), vec![tx1, tx2, tx3].local()); assert_eq!(res, vec![Ok(()), Ok(()), Ok(())]); // when let all = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); // This should invalidate the cache! let limited = txq.pending( TestClient::new(), PendingSettings { block_number: 0, current_timestamp: 0, nonce_cap: Some(123.into()), max_len: usize::max_value(), ordering: PendingOrdering::Priority, includable_boundary: Default::default(), }, ); // then assert_eq!(all.len(), 3); assert_eq!(limited.len(), 1); } #[test] fn should_return_cached_pending_even_if_unordered_is_requested() { // given let txq = new_queue(); let tx1 = Tx::default().signed(); let (tx2_1, tx2_2) = Tx::default().signed_pair(); let tx2_1_hash = tx2_1.hash(); let res = txq.import(TestClient::new(), vec![tx1].unverified()); assert_eq!(res, vec![Ok(())]); let res = txq.import(TestClient::new(), vec![tx2_1, tx2_2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); // when let all = txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 0)); assert_eq!(all[0].hash, tx2_1_hash); assert_eq!(all.len(), 3); // This should not invalidate the cache! let limited = txq.pending( TestClient::new(), PendingSettings { block_number: 0, current_timestamp: 0, nonce_cap: None, max_len: 3, ordering: PendingOrdering::Unordered, includable_boundary: Default::default(), }, ); // then assert_eq!(all, limited); } #[test] fn should_return_unordered_and_not_populate_the_cache() { // given let txq = new_queue(); let tx1 = Tx::default().signed(); let (tx2_1, tx2_2) = Tx::default().signed_pair(); let res = txq.import(TestClient::new(), vec![tx1].unverified()); assert_eq!(res, vec![Ok(())]); let res = txq.import(TestClient::new(), vec![tx2_1, tx2_2].local()); assert_eq!(res, vec![Ok(()), Ok(())]); // when // This should not invalidate the cache! let limited = txq.pending( TestClient::new(), PendingSettings { block_number: 0, current_timestamp: 0, nonce_cap: None, max_len: usize::max_value(), ordering: PendingOrdering::Unordered, includable_boundary: Default::default(), }, ); // then assert_eq!(limited.len(), 3); assert!(!txq.is_pending_cached()); } #[test] fn should_clear_cache_after_timeout_for_local() { // given let txq = new_queue(); let (tx, tx2) = Tx::default().signed_pair(); let res = txq.import( TestClient::new(), vec![ verifier::Transaction::Local(PendingTransaction::new( tx, transaction::Condition::Timestamp(1000).into(), )), tx2.local(), ], ); assert_eq!(res, vec![Ok(()), Ok(())]); // This should populate cache and set timestamp to 1 // when assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 1)) .len(), 0 ); assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 1000)) .len(), 0 ); // This should invalidate the cache and trigger transaction ready. // then assert_eq!( txq.pending(TestClient::new(), PendingSettings::all_prioritized(0, 1002)) .len(), 2 ); } #[test] fn should_reject_big_transaction() { let txq = new_queue(); let big_tx = Tx::default().big_one(); let res = txq.import( TestClient::new(), vec![verifier::Transaction::Local(PendingTransaction::new( big_tx, transaction::Condition::Timestamp(1000).into(), ))], ); assert_eq!(res, vec![Err(transaction::Error::TooBig)]); } #[test] fn should_include_local_transaction_to_a_full_pool() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 1, max_per_sender: 2, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ); let tx1 = Tx::gas_price(10_000).signed().unverified(); let tx2 = Tx::gas_price(1).signed().local(); let res = txq.import(TestClient::new().with_balance(1_000_000_000), vec![tx1]); assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); // when let res = txq.import(TestClient::new(), vec![tx2]); assert_eq!(res, vec![Ok(())]); // then assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_avoid_verifying_transaction_already_in_pool() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 1, max_per_sender: 2, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ); let client = TestClient::new().with_balance(1_000_000_000); let tx1 = Tx::gas_price(2).signed().unverified(); let res = txq.import(client.clone(), vec![tx1.clone()]); assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); assert!(client.was_verification_triggered()); // when let client = TestClient::new(); let res = txq.import(client.clone(), vec![tx1]); assert_eq!(res, vec![Err(transaction::Error::AlreadyImported)]); assert!(!client.was_verification_triggered()); // then assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_avoid_reverifying_recently_rejected_transactions() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 1, max_per_sender: 2, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ); let client = TestClient::new(); let tx1 = Tx::gas_price(10_000).signed().unverified(); let res = txq.import(client.clone(), vec![tx1.clone()]); assert_eq!( res, vec![Err(transaction::Error::InsufficientBalance { balance: 0xf67c.into(), cost: 0xc8458e4.into(), })] ); assert_eq!(txq.status().status.transaction_count, 0); assert!(client.was_verification_triggered()); // when let client = TestClient::new(); let res = txq.import(client.clone(), vec![tx1]); assert_eq!( res, vec![Err(transaction::Error::InsufficientBalance { balance: 0xf67c.into(), cost: 0xc8458e4.into(), })] ); assert!(!client.was_verification_triggered()); // then assert_eq!(txq.status().status.transaction_count, 0); } #[test] fn should_reject_early_in_case_gas_price_is_less_than_min_effective() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 1, max_per_sender: 2, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ); let client = TestClient::new().with_balance(1_000_000_000); let tx1 = Tx::gas_price(2).signed().unverified(); let res = txq.import(client.clone(), vec![tx1]); assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); assert!(client.was_verification_triggered()); // when let client = TestClient::new(); let tx1 = Tx::default().signed().unverified(); let res = txq.import(client.clone(), vec![tx1]); assert_eq!( res, vec![Err(transaction::Error::TooCheapToReplace { prev: Some(2.into()), new: Some(1.into()), })] ); assert!(!client.was_verification_triggered()); // then assert_eq!(txq.status().status.transaction_count, 1); } #[test] fn should_not_reject_early_in_case_gas_price_is_less_than_min_effective() { // given let txq = TransactionQueue::new( txpool::Options { max_count: 1, max_per_sender: 2, max_mem_usage: TEST_QUEUE_MAX_MEM, }, verifier::Options { minimal_gas_price: 1.into(), block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: true, block_base_fee: None, allow_non_eoa_sender: false, }, PrioritizationStrategy::GasPriceOnly, ); // when let tx1 = Tx::gas_price(2).signed(); let client = TestClient::new().with_local(&tx1.sender()); let res = txq.import(client.clone(), vec![tx1.unverified()]); // then assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 1); assert!(client.was_verification_triggered()); // when let tx1 = Tx::gas_price(1).signed(); let client = TestClient::new().with_local(&tx1.sender()); let res = txq.import(client.clone(), vec![tx1.unverified()]); // then assert_eq!(res, vec![Ok(())]); assert_eq!(txq.status().status.transaction_count, 2); assert!(client.was_verification_triggered()); }