* `duration_ns: u64 -> duration: Duration` (#8457) * duration_ns: u64 -> duration: Duration * format on millis {:.2} -> {} * Keep all enacted blocks notify in order (#8524) * Keep all enacted blocks notify in order * Collect is unnecessary * Update ChainNotify to use ChainRouteType * Fix all ethcore fn defs * Wrap the type within ChainRoute * Fix private-tx and sync api * Fix secret_store API * Fix updater API * Fix rpc api * Fix informant api * Eagerly cache enacted/retracted and remove contain_enacted/retracted * Fix indent * tests: should use full expr form for struct constructor * Use into_enacted_retracted to further avoid copy * typo: not a function * rpc/tests: ChainRoute -> ChainRoute::new * Handle removed logs in filter changes and add geth compatibility field (#8796) * Add removed geth compatibility field in log * Fix mocked tests * Add field block hash in PollFilter * Store last block hash info for log filters * Implement canon route * Use canon logs for fetching reorg logs Light client removed logs fetching is disabled. It looks expensive. * Make sure removed flag is set * Address grumbles * Fixed AuthorityRound deadlock on shutdown, closes #8088 (#8803) * CI: Fix docker tags (#8822) * scripts: enable docker builds for beta and stable * scripts: docker latest should be beta not master * scripts: docker latest is master * ethcore: fix ancient block error msg handling (#8832) * Disable parallel verification and skip verifiying already imported txs. (#8834) * Reject transactions that are already in pool without verifying them. * Avoid verifying already imported transactions. * Fix concurrent access to signer queue (#8854) * Fix concurrent access to signer queue * Put request back to the queue if confirmation failed * typo: fix docs and rename functions to be more specific `request_notify` does not need to be public, and it's renamed to `notify_result`. `notify` is renamed to `notify_message`. * Change trace info "Transaction" -> "Request" * Don't allocate in expect_valid_rlp unless necessary (#8867) * don't allocate via format! in case there's no error * fix test? * fixed ipc leak, closes #8774 (#8876) * Add new ovh bootnodes and fix port for foundation bootnode 3.2 (#8886) * Add new ovh bootnodes and fix port for foundation bootnode 3.2 * Remove old bootnodes. * Remove duplicate 1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082 * Block 0 is valid in queries (#8891) Early exit for block nr 0 leads to spurious error about pruning: `…your node is running with state pruning…`. Fixes #7547, #8762 * Add ETC Cooperative-run load balanced parity node (#8892) * Minor fix in chain supplier and light provider (#8906) * fix chain supplier increment * fix light provider block_headers * Check whether we need resealing in miner and unwrap has_account in account_provider (#8853) * Remove unused Result wrap in has_account * Check whether we need to reseal for external transactions * Fix reference to has_account interface * typo: missing ) * Refactor duplicates to prepare_and_update_sealing * Fix build * Allow disabling local-by-default for transactions with new config entry (#8882) * Add tx_queue_allow_unknown_local config option - Previous commit messages: dispatcher checks if we have the sender account Add `tx_queue_allow_unknown_local` to MinerOptions Add `tx_queue_allow_unknown_local` to config fix order in MinerOptions to match Configuration add cli flag for tx_queue_allow_unknown_local Update refs to `tx_queue_allow_unknown_local` Add tx_queue_allow_unknown_local to config test revert changes to dispatcher Move tx_queue_allow_unknown_local to `import_own_transaction` Fix var name if statement should return the values derp de derp derp derp semicolons Reset dispatch file to how it was before fix compile issues + change from FLAG to ARG add test and use `into` import MinerOptions, clone the secret Fix tests? Compiler/linter issues fixed Fix linter msg - case of constants IT LIVES refactor to omit yucky explict return update comments Fix based on diff AccountProvider.has_account method * Refactor flag name + don't change import_own_tx behaviour fix arg name Note: force commit to try and get gitlab tests working again 😠 * Add fn to TestMinerService * Avoid race condition from trusted sources - refactor the miner tests a bit to cut down on code reuse - add `trusted` param to dispatch_transaction and import_claimed_local_transaction Add param to `import_claimed_local_transaction` Fix fn sig in tests
835 lines
23 KiB
Rust
835 lines
23 KiB
Rust
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
|
// This file is part of Parity.
|
|
|
|
// Parity 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.
|
|
|
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use ethereum_types::U256;
|
|
use transaction::{self, PendingTransaction};
|
|
use txpool;
|
|
|
|
use pool::{verifier, TransactionQueue, PrioritizationStrategy};
|
|
|
|
pub mod tx;
|
|
pub mod client;
|
|
|
|
use self::tx::{Tx, TxExt, PairExt};
|
|
use self::client::TestClient;
|
|
|
|
fn new_queue() -> TransactionQueue {
|
|
TransactionQueue::new(
|
|
txpool::Options {
|
|
max_count: 3,
|
|
max_per_sender: 3,
|
|
max_mem_usage: 50
|
|
},
|
|
verifier::Options {
|
|
minimal_gas_price: 1.into(),
|
|
block_gas_limit: 1_000_000.into(),
|
|
tx_gas_limit: 1_000_000.into(),
|
|
},
|
|
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: 50
|
|
},
|
|
verifier::Options {
|
|
minimal_gas_price: 1.into(),
|
|
block_gas_limit: 1_000_000.into(),
|
|
tx_gas_limit: 1_000_000.into(),
|
|
},
|
|
PrioritizationStrategy::GasPriceOnly,
|
|
);
|
|
let (tx1, tx2) = Tx::gas_price(2).signed_pair();
|
|
let sender = tx1.sender();
|
|
let nonce = tx1.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![Err(transaction::Error::LimitReached)]);
|
|
assert_eq!(txq.status().status.transaction_count, 1);
|
|
|
|
// then
|
|
assert_eq!(txq.next_nonce(TestClient::new(), &sender), Some(nonce + 1.into()));
|
|
|
|
// 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![Err(transaction::Error::LimitReached), Ok(())]);
|
|
assert_eq!(txq.status().status.transaction_count, 3);
|
|
// First inserted transacton got dropped because of limit
|
|
assert_eq!(txq.next_nonce(TestClient::new(), &sender), None);
|
|
}
|
|
|
|
#[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(), 0, 0, None);
|
|
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(), 0, 0, None);
|
|
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_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(), 0 ,0, None);
|
|
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, 0, 0, None);
|
|
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(), 0, 0, None);
|
|
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(), 0, 0, None);
|
|
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(), 0, 0, None);
|
|
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(), 0, 0, None);
|
|
assert_eq!(top.len(), 0);
|
|
let top = txq.pending(TestClient::new(), 1, 0, None);
|
|
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(), 0, 0, None).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(), 0, 0, None).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(), 0, 0, None).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), 0, 0, None).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(), 0, 0, None).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(), 0, 0, None).len(), 3);
|
|
|
|
// when
|
|
txq.remove(vec![&hash], true);
|
|
|
|
// then
|
|
assert_eq!(txq.status().status.transaction_count, 2);
|
|
assert_eq!(txq.pending(TestClient::new(), 0, 0, None).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: 50
|
|
},
|
|
verifier::Options {
|
|
minimal_gas_price: 1.into(),
|
|
block_gas_limit: 1_000_000.into(),
|
|
tx_gas_limit: 1_000_000.into(),
|
|
},
|
|
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(), 0, 0, None);
|
|
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(), 0, 0, None).len(), 2);
|
|
|
|
// when
|
|
txq.remove(vec![&hash], true);
|
|
assert_eq!(txq.status().status.transaction_count, 1);
|
|
assert_eq!(txq.pending(TestClient::new(), 0, 0, None).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(), 0, 0, None).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), Ok(())]);
|
|
assert_eq!(txq.status().status.transaction_count, 2);
|
|
assert_eq!(txq.pending(client.clone(), 0, 0, None)[0].signed().gas_price, U256::from(20));
|
|
assert_eq!(txq.pending(client.clone(), 0, 0, None)[1].signed().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.nonce;
|
|
|
|
// when
|
|
txq.import(TestClient::new(), vec![tx.local()]);
|
|
|
|
// then
|
|
assert_eq!(txq.next_nonce(TestClient::new(), &from), Some(nonce + 1.into()));
|
|
}
|
|
|
|
#[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(), 0, 0, None).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: 50
|
|
},
|
|
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(), 0, 0, None).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(), 0, 0, None).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(), 0, 0, None);
|
|
// This should invalidate the cache!
|
|
let limited = txq.pending(TestClient::new(), 0, 0, Some(123.into()));
|
|
|
|
|
|
// then
|
|
assert_eq!(all.len(), 3);
|
|
assert_eq!(limited.len(), 1);
|
|
}
|
|
|
|
#[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(), 0, 1, None).len(), 0);
|
|
assert_eq!(txq.pending(TestClient::new(), 0, 1000, None).len(), 0);
|
|
|
|
// This should invalidate the cache and trigger transaction ready.
|
|
// then
|
|
assert_eq!(txq.pending(TestClient::new(), 0, 1002, None).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: 50
|
|
},
|
|
verifier::Options {
|
|
minimal_gas_price: 1.into(),
|
|
block_gas_limit: 1_000_000.into(),
|
|
tx_gas_limit: 1_000_000.into(),
|
|
},
|
|
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: 50
|
|
},
|
|
verifier::Options {
|
|
minimal_gas_price: 1.into(),
|
|
block_gas_limit: 1_000_000.into(),
|
|
tx_gas_limit: 1_000_000.into(),
|
|
},
|
|
PrioritizationStrategy::GasPriceOnly,
|
|
);
|
|
let client = TestClient::new();
|
|
let tx1 = Tx::default().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);
|
|
}
|