diff --git a/codechain/config/mod.rs b/codechain/config/mod.rs index a75df5cdeb..e4ea5538d6 100644 --- a/codechain/config/mod.rs +++ b/codechain/config/mod.rs @@ -20,7 +20,7 @@ use std::fs; use std::str::{self, FromStr}; use std::time::Duration; -use ccore::{MinerOptions, StratumConfig, TimeGapParams}; +use ccore::{MemPoolFees, MinerOptions, StratumConfig, TimeGapParams}; use cidr::IpCidr; use ckey::PlatformAddress; use clap; @@ -75,6 +75,23 @@ impl Config { None => unreachable!(), }; + let mem_pool_fees = MemPoolFees::create_from_options( + self.mining.min_pay_transaction_cost, + self.mining.min_set_regular_key_transaction_cost, + self.mining.min_create_shard_transaction_cost, + self.mining.min_set_shard_owners_transaction_cost, + self.mining.min_set_shard_users_transaction_cost, + self.mining.min_wrap_ccc_transaction_cost, + self.mining.min_custom_transaction_cost, + self.mining.min_store_transaction_cost, + self.mining.min_remove_transaction_cost, + self.mining.min_asset_mint_cost, + self.mining.min_asset_transfer_cost, + self.mining.min_asset_scheme_change_cost, + self.mining.min_asset_supply_increase_cost, + self.mining.min_asset_unwrap_ccc_cost, + ); + Ok(MinerOptions { mem_pool_size: self.mining.mem_pool_size.unwrap(), mem_pool_memory_limit: match self.mining.mem_pool_mem_limit.unwrap() { @@ -91,6 +108,7 @@ impl Config { reseal_max_period: Duration::from_millis(self.mining.reseal_max_period.unwrap()), no_reseal_timer: self.mining.no_reseal_timer.unwrap(), work_queue_size: self.mining.work_queue_size.unwrap(), + mem_pool_fees, }) } @@ -230,6 +248,20 @@ pub struct Mining { pub work_queue_size: Option, pub allowed_past_gap: Option, pub allowed_future_gap: Option, + pub min_pay_transaction_cost: Option, + pub min_set_regular_key_transaction_cost: Option, + pub min_create_shard_transaction_cost: Option, + pub min_set_shard_owners_transaction_cost: Option, + pub min_set_shard_users_transaction_cost: Option, + pub min_wrap_ccc_transaction_cost: Option, + pub min_custom_transaction_cost: Option, + pub min_store_transaction_cost: Option, + pub min_remove_transaction_cost: Option, + pub min_asset_mint_cost: Option, + pub min_asset_transfer_cost: Option, + pub min_asset_scheme_change_cost: Option, + pub min_asset_supply_increase_cost: Option, + pub min_asset_unwrap_ccc_cost: Option, } #[derive(Deserialize)] @@ -372,6 +404,7 @@ impl Operating { } impl Mining { + #[allow(clippy::cognitive_complexity)] pub fn merge(&mut self, other: &Mining) { if other.author.is_some() { self.author = other.author; @@ -409,6 +442,48 @@ impl Mining { if other.work_queue_size.is_some() { self.work_queue_size = other.work_queue_size; } + if other.min_pay_transaction_cost.is_some() { + self.min_pay_transaction_cost = other.min_pay_transaction_cost; + } + if other.min_set_regular_key_transaction_cost.is_some() { + self.min_set_regular_key_transaction_cost = other.min_set_regular_key_transaction_cost; + } + if other.min_create_shard_transaction_cost.is_some() { + self.min_create_shard_transaction_cost = other.min_create_shard_transaction_cost; + } + if other.min_set_shard_owners_transaction_cost.is_some() { + self.min_set_shard_owners_transaction_cost = other.min_set_shard_owners_transaction_cost; + } + if other.min_set_shard_users_transaction_cost.is_some() { + self.min_set_shard_users_transaction_cost = other.min_set_shard_users_transaction_cost; + } + if other.min_wrap_ccc_transaction_cost.is_some() { + self.min_wrap_ccc_transaction_cost = other.min_wrap_ccc_transaction_cost; + } + if other.min_custom_transaction_cost.is_some() { + self.min_custom_transaction_cost = other.min_custom_transaction_cost; + } + if other.min_store_transaction_cost.is_some() { + self.min_store_transaction_cost = other.min_store_transaction_cost; + } + if other.min_remove_transaction_cost.is_some() { + self.min_remove_transaction_cost = other.min_remove_transaction_cost; + } + if other.min_asset_mint_cost.is_some() { + self.min_asset_mint_cost = other.min_asset_mint_cost; + } + if other.min_asset_transfer_cost.is_some() { + self.min_asset_transfer_cost = other.min_asset_transfer_cost; + } + if other.min_asset_scheme_change_cost.is_some() { + self.min_asset_scheme_change_cost = other.min_asset_scheme_change_cost; + } + if other.min_asset_supply_increase_cost.is_some() { + self.min_asset_supply_increase_cost = other.min_asset_supply_increase_cost; + } + if other.min_asset_unwrap_ccc_cost.is_some() { + self.min_asset_unwrap_ccc_cost = other.min_asset_unwrap_ccc_cost; + } } pub fn overwrite_with(&mut self, matches: &clap::ArgMatches) -> Result<(), String> { diff --git a/core/src/lib.rs b/core/src/lib.rs index 26cf864d8f..d4002cbbb6 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -93,7 +93,7 @@ pub use crate::client::{ pub use crate::consensus::{EngineType, TimeGapParams}; pub use crate::db::{COL_STATE, NUM_COLUMNS}; pub use crate::error::{BlockImportError, Error, ImportError}; -pub use crate::miner::{Miner, MinerOptions, MinerService, Stratum, StratumConfig, StratumError}; +pub use crate::miner::{MemPoolFees, Miner, MinerOptions, MinerService, Stratum, StratumConfig, StratumError}; pub use crate::scheme::Scheme; pub use crate::service::ClientService; pub use crate::transaction::{ diff --git a/core/src/miner/mem_pool.rs b/core/src/miner/mem_pool.rs index 8729639c0e..6fcfa35c7a 100644 --- a/core/src/miner/mem_pool.rs +++ b/core/src/miner/mem_pool.rs @@ -27,8 +27,8 @@ use table::Table; use super::backup; use super::mem_pool_types::{ - AccountDetails, CurrentQueue, FutureQueue, MemPoolInput, MemPoolItem, MemPoolStatus, PoolingInstant, QueueTag, - TransactionOrder, TransactionOrderWithTag, TxOrigin, TxTimelock, + AccountDetails, CurrentQueue, FutureQueue, MemPoolFees, MemPoolInput, MemPoolItem, MemPoolStatus, PoolingInstant, + QueueTag, TransactionOrder, TransactionOrderWithTag, TxOrigin, TxTimelock, }; use super::TransactionImportResult; use crate::client::{AccountData, BlockChainTrait}; @@ -73,8 +73,8 @@ impl From for Error { } pub struct MemPool { - /// Fee threshold for transactions that can be imported to this pool (defaults to 0) - minimal_fee: u64, + /// Fee threshold for transactions that can be imported to this pool + minimum_fees: MemPoolFees, /// A value which is used to check whether a new transaciton can replace a transaction in the memory pool with the same signer and seq. /// If the fee of the new transaction is `new_fee` and the fee of the transaction in the memory pool is `old_fee`, /// then `new_fee > old_fee + old_fee >> mem_pool_fee_bump_shift` should be satisfied to replace. @@ -114,9 +114,15 @@ pub struct MemPool { impl MemPool { /// Create new instance of this Queue with specified limits - pub fn with_limits(limit: usize, memory_limit: usize, fee_bump_shift: usize, db: Arc) -> Self { + pub fn with_limits( + limit: usize, + memory_limit: usize, + fee_bump_shift: usize, + db: Arc, + minimum_fees: MemPoolFees, + ) -> Self { MemPool { - minimal_fee: 0, + minimum_fees, fee_bump_shift, max_block_number_period_in_pool: DEFAULT_POOLING_PERIOD, current: CurrentQueue::new(), @@ -201,17 +207,6 @@ impl MemPool { self.queue_count_limit } - /// Get the minimal fee. - pub fn minimal_fee(&self) -> u64 { - self.minimal_fee - } - - /// Sets new fee threshold for incoming transactions. - /// Any transaction already imported to the pool is not affected. - pub fn set_minimal_fee(&mut self, min_fee: u64) { - self.minimal_fee = min_fee; - } - /// Get one more than the lowest fee in the pool iff the pool is /// full, otherwise 0. pub fn effective_minimum_fee(&self) -> u64 { @@ -781,17 +776,18 @@ impl MemPool { origin: TxOrigin, client_account: &AccountDetails, ) -> Result<(), Error> { - if origin != TxOrigin::Local && tx.fee < self.minimal_fee { + let action_min_fee = self.minimum_fees.min_cost(&tx.action); + if origin != TxOrigin::Local && tx.fee < action_min_fee { ctrace!( MEM_POOL, - "Dropping transaction below minimal fee: {:?} (gp: {} < {})", + "Dropping transaction below mempool defined minimum fee: {:?} (gp: {} < {})", tx.hash(), tx.fee, - self.minimal_fee + action_min_fee ); return Err(SyntaxError::InsufficientFee { - minimal: self.minimal_fee, + minimal: action_min_fee, got: tx.fee, } .into()) @@ -1430,7 +1426,7 @@ pub mod test { test_client.set_balance(default_addr, u64::max_value()); let db = Arc::new(kvdb_memorydb::create(crate::db::NUM_COLUMNS.unwrap_or(0))); - let mut mem_pool = MemPool::with_limits(8192, usize::max_value(), 3, db.clone()); + let mut mem_pool = MemPool::with_limits(8192, usize::max_value(), 3, db.clone(), Default::default()); let fetch_account = |p: &Public| -> AccountDetails { let address = public_to_address(p); @@ -1478,7 +1474,7 @@ pub mod test { inputs.push(create_mempool_input_with_pay(7u64, keypair, no_timelock)); mem_pool.add(inputs, inserted_block_number, inserted_timestamp, &fetch_account); - let mut mem_pool_recovered = MemPool::with_limits(8192, usize::max_value(), 3, db); + let mut mem_pool_recovered = MemPool::with_limits(8192, usize::max_value(), 3, db, Default::default()); mem_pool_recovered.recover_from_db(&test_client); assert_eq!(mem_pool_recovered.first_seqs, mem_pool.first_seqs); @@ -1507,6 +1503,20 @@ pub mod test { SignedTransaction::new_with_sign(tx, keypair.private()) } + fn create_signed_pay_with_fee(seq: u64, fee: u64, keypair: KeyPair) -> SignedTransaction { + let receiver = 1u64.into(); + let tx = Transaction { + seq, + fee, + network_id: "tc".into(), + action: Action::Pay { + receiver, + quantity: 100_000, + }, + }; + SignedTransaction::new_with_sign(tx, keypair.private()) + } + fn create_mempool_input_with_pay(seq: u64, keypair: KeyPair, timelock: TxTimelock) -> MemPoolInput { let signed = create_signed_pay(seq, keypair); MemPoolInput::new(signed, TxOrigin::Local, timelock) @@ -1542,13 +1552,153 @@ pub mod test { TransactionOrder::for_transaction(&item, 0) } + fn abbreviated_mempool_add( + test_client: &TestBlockChainClient, + mem_pool: &mut MemPool, + txs: Vec, + origin: TxOrigin, + ) -> Vec> { + let fetch_account = |p: &Public| -> AccountDetails { + let address = public_to_address(p); + let a = test_client.latest_regular_key_owner(&address).unwrap_or(address); + AccountDetails { + seq: test_client.latest_seq(&a), + balance: test_client.latest_balance(&a), + } + }; + let no_timelock = TxTimelock { + block: None, + timestamp: None, + }; + + let inserted_block_number = 1; + let inserted_timestamp = 100; + let inputs: Vec = txs.into_iter().map(|tx| MemPoolInput::new(tx, origin, no_timelock)).collect(); + mem_pool.add(inputs, inserted_block_number, inserted_timestamp, &fetch_account) + } + + #[test] + fn local_transactions_whose_fees_are_under_the_mem_pool_min_fee_should_not_be_rejected() { + let test_client = TestBlockChainClient::new(); + + // Set the pay transaction minimum fee + let fees = MemPoolFees::create_from_options( + Some(150), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); + + let db = Arc::new(kvdb_memorydb::create(crate::db::NUM_COLUMNS.unwrap_or(0))); + let mut mem_pool = MemPool::with_limits(8192, usize::max_value(), 3, db, fees); + let keypair = Random.generate().unwrap(); + let address = public_to_address(keypair.public()); + + test_client.set_balance(address, 1_000_000_000_000); + + let txs = vec![ + create_signed_pay_with_fee(0, 200, keypair), + create_signed_pay_with_fee(1, 140, keypair), + create_signed_pay_with_fee(2, 160, keypair), + ]; + let result = abbreviated_mempool_add(&test_client, &mut mem_pool, txs, TxOrigin::Local); + assert_eq!( + vec![ + Ok(TransactionImportResult::Current), + Ok(TransactionImportResult::Current), + Ok(TransactionImportResult::Current) + ], + result + ); + + assert_eq!( + vec![ + create_signed_pay_with_fee(0, 200, keypair), + create_signed_pay_with_fee(1, 140, keypair), + create_signed_pay_with_fee(2, 160, keypair) + ], + mem_pool.top_transactions(std::usize::MAX, None, 0..std::u64::MAX).transactions + ); + + assert_eq!(Vec::::default(), mem_pool.future_transactions()); + } + + #[test] + fn external_transactions_whose_fees_are_under_the_mem_pool_min_fee_are_rejected() { + let test_client = TestBlockChainClient::new(); + // Set the pay transaction minimum fee + let fees = MemPoolFees::create_from_options( + Some(150), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); + + let db = Arc::new(kvdb_memorydb::create(crate::db::NUM_COLUMNS.unwrap_or(0))); + let mut mem_pool = MemPool::with_limits(8192, usize::max_value(), 3, db, fees); + let keypair = Random.generate().unwrap(); + let address = public_to_address(keypair.public()); + + test_client.set_balance(address, 1_000_000_000_000); + + let txs = vec![ + create_signed_pay_with_fee(0, 200, keypair), + create_signed_pay_with_fee(1, 140, keypair), + create_signed_pay_with_fee(1, 160, keypair), + create_signed_pay_with_fee(2, 149, keypair), + ]; + let result = abbreviated_mempool_add(&test_client, &mut mem_pool, txs, TxOrigin::External); + assert_eq!( + vec![ + Ok(TransactionImportResult::Current), + Err(Error::Syntax(SyntaxError::InsufficientFee { + minimal: 150, + got: 140, + })), + Ok(TransactionImportResult::Current), + Err(Error::Syntax(SyntaxError::InsufficientFee { + minimal: 150, + got: 149, + })), + ], + result + ); + + assert_eq!( + vec![create_signed_pay_with_fee(0, 200, keypair), create_signed_pay_with_fee(1, 160, keypair)], + mem_pool.top_transactions(std::usize::MAX, None, 0..std::u64::MAX).transactions + ); + + assert_eq!(Vec::::default(), mem_pool.future_transactions()); + } + #[test] fn transactions_are_moved_to_future_queue_if_the_preceding_one_removed() { //setup test_client let test_client = TestBlockChainClient::new(); let db = Arc::new(kvdb_memorydb::create(crate::db::NUM_COLUMNS.unwrap_or(0))); - let mut mem_pool = MemPool::with_limits(8192, usize::max_value(), 3, db); + let mut mem_pool = MemPool::with_limits(8192, usize::max_value(), 3, db, Default::default()); let fetch_account = |p: &Public| -> AccountDetails { let address = public_to_address(p); diff --git a/core/src/miner/mem_pool_types.rs b/core/src/miner/mem_pool_types.rs index d69433c24c..5106083a50 100644 --- a/core/src/miner/mem_pool_types.rs +++ b/core/src/miner/mem_pool_types.rs @@ -439,3 +439,105 @@ pub struct AccountDetails { /// Current account balance pub balance: u64, } + +#[derive(Default, Clone, Copy, Debug, PartialEq)] +/// Minimum fee thresholds defined not by network but by Mempool +pub struct MemPoolFees { + min_pay_transaction_cost: u64, + min_set_regular_key_transaction_cost: u64, + min_create_shard_transaction_cost: u64, + min_set_shard_owners_transaction_cost: u64, + min_set_shard_users_transaction_cost: u64, + min_wrap_ccc_transaction_cost: u64, + min_custom_transaction_cost: u64, + min_store_transaction_cost: u64, + min_remove_transaction_cost: u64, + min_asset_mint_cost: u64, + min_asset_transfer_cost: u64, + min_asset_scheme_change_cost: u64, + min_asset_supply_increase_cost: u64, + min_asset_unwrap_ccc_cost: u64, +} + +impl MemPoolFees { + #[allow(clippy::too_many_arguments)] + pub fn create_from_options( + min_pay_cost_option: Option, + min_set_regular_key_cost_option: Option, + min_create_shard_cost_option: Option, + min_set_shard_owners_cost_option: Option, + min_set_shard_users_cost_option: Option, + min_wrap_ccc_cost_option: Option, + min_custom_cost_option: Option, + min_store_cost_option: Option, + min_remove_cost_option: Option, + min_asset_mint_cost_option: Option, + min_asset_transfer_cost_option: Option, + min_asset_scheme_change_cost_option: Option, + min_asset_supply_increase_cost_option: Option, + min_asset_unwrap_ccc_cost_option: Option, + ) -> Self { + MemPoolFees { + min_pay_transaction_cost: min_pay_cost_option.unwrap_or_default(), + min_set_regular_key_transaction_cost: min_set_regular_key_cost_option.unwrap_or_default(), + min_create_shard_transaction_cost: min_create_shard_cost_option.unwrap_or_default(), + min_set_shard_owners_transaction_cost: min_set_shard_owners_cost_option.unwrap_or_default(), + min_set_shard_users_transaction_cost: min_set_shard_users_cost_option.unwrap_or_default(), + min_wrap_ccc_transaction_cost: min_wrap_ccc_cost_option.unwrap_or_default(), + min_custom_transaction_cost: min_custom_cost_option.unwrap_or_default(), + min_store_transaction_cost: min_store_cost_option.unwrap_or_default(), + min_remove_transaction_cost: min_remove_cost_option.unwrap_or_default(), + min_asset_mint_cost: min_asset_mint_cost_option.unwrap_or_default(), + min_asset_transfer_cost: min_asset_transfer_cost_option.unwrap_or_default(), + min_asset_scheme_change_cost: min_asset_scheme_change_cost_option.unwrap_or_default(), + min_asset_supply_increase_cost: min_asset_supply_increase_cost_option.unwrap_or_default(), + min_asset_unwrap_ccc_cost: min_asset_unwrap_ccc_cost_option.unwrap_or_default(), + } + } + pub fn min_cost(&self, action: &Action) -> u64 { + match action { + Action::MintAsset { + .. + } => self.min_asset_mint_cost, + Action::TransferAsset { + .. + } => self.min_asset_transfer_cost, + Action::ChangeAssetScheme { + .. + } => self.min_asset_scheme_change_cost, + Action::IncreaseAssetSupply { + .. + } => self.min_asset_supply_increase_cost, + Action::UnwrapCCC { + .. + } => self.min_asset_unwrap_ccc_cost, + Action::Pay { + .. + } => self.min_pay_transaction_cost, + Action::SetRegularKey { + .. + } => self.min_set_regular_key_transaction_cost, + Action::CreateShard { + .. + } => self.min_create_shard_transaction_cost, + Action::SetShardOwners { + .. + } => self.min_set_shard_owners_transaction_cost, + Action::SetShardUsers { + .. + } => self.min_set_shard_users_transaction_cost, + Action::WrapCCC { + .. + } => self.min_wrap_ccc_transaction_cost, + Action::Custom { + .. + } => self.min_custom_transaction_cost, + Action::Store { + .. + } => self.min_store_transaction_cost, + Action::Remove { + .. + } => self.min_remove_transaction_cost, + } + } +} diff --git a/core/src/miner/miner.rs b/core/src/miner/miner.rs index 0278715cff..d8c6b466fc 100644 --- a/core/src/miner/miner.rs +++ b/core/src/miner/miner.rs @@ -33,6 +33,7 @@ use parking_lot::{Mutex, RwLock}; use primitives::{Bytes, H256}; use super::mem_pool::{Error as MemPoolError, MemPool}; +pub use super::mem_pool_types::MemPoolFees; use super::mem_pool_types::{AccountDetails, MemPoolInput, TxOrigin, TxTimelock}; use super::sealing_queue::SealingQueue; use super::work_notify::{NotifyWork, WorkPoster}; @@ -79,6 +80,8 @@ pub struct MinerOptions { pub allow_create_shard: bool, /// How many historical work packages can we store before running out? pub work_queue_size: usize, + /// Minimum fees configured by the machine. + pub mem_pool_fees: MemPoolFees, } impl Default for MinerOptions { @@ -96,6 +99,7 @@ impl Default for MinerOptions { mem_pool_fee_bump_shift: 3, allow_create_shard: false, work_queue_size: 20, + mem_pool_fees: Default::default(), } } } @@ -163,6 +167,7 @@ impl Miner { mem_limit, options.mem_pool_fee_bump_shift, db, + options.mem_pool_fees, ))); let notifiers: Vec> = if options.new_work_notify.is_empty() { @@ -755,14 +760,6 @@ impl MinerService for Miner { self.params.write().extra_data = extra_data; } - fn minimal_fee(&self) -> u64 { - self.mem_pool.read().minimal_fee() - } - - fn set_minimal_fee(&self, min_fee: u64) { - self.mem_pool.write().set_minimal_fee(min_fee); - } - fn transactions_limit(&self) -> usize { self.mem_pool.read().limit() } @@ -1188,7 +1185,7 @@ pub mod test { let scheme = Scheme::new_test(); let miner = Arc::new(Miner::with_scheme(&scheme, db.clone())); - let mut mem_pool = MemPool::with_limits(8192, usize::max_value(), 3, db.clone()); + let mut mem_pool = MemPool::with_limits(8192, usize::max_value(), 3, db.clone(), Default::default()); let client = generate_test_client(db, Arc::clone(&miner), &scheme).unwrap(); let private: Private = H256::random().into(); diff --git a/core/src/miner/mod.rs b/core/src/miner/mod.rs index 0eea957edf..544e555ca4 100644 --- a/core/src/miner/mod.rs +++ b/core/src/miner/mod.rs @@ -32,6 +32,7 @@ use ctypes::{BlockHash, TxHash}; use cvm::ChainTimeInfo; use primitives::Bytes; +pub use self::mem_pool_types::MemPoolFees; pub use self::miner::{AuthoringParams, Miner, MinerOptions}; pub use self::stratum::{Config as StratumConfig, Error as StratumError, Stratum}; use crate::account_provider::{AccountProvider, Error as AccountProviderError}; @@ -61,12 +62,6 @@ pub trait MinerService: Send + Sync { /// Set the extra_data that we will seal blocks with. fn set_extra_data(&self, extra_data: Bytes); - /// Get current minimal fee for tranasctions accepted to queue. - fn minimal_fee(&self) -> u64; - - /// Set minimal fee of transactions to be accepted for mining. - fn set_minimal_fee(&self, min_fee: u64); - /// Get current transactions limit in queue. fn transactions_limit(&self) -> usize; diff --git a/test/custom.minfee/tccq83wm6sjyklkd4utk6hjmewsaccgvzk5sck8cs2y/keys/key b/test/custom.minfee/tccq83wm6sjyklkd4utk6hjmewsaccgvzk5sck8cs2y/keys/key new file mode 100644 index 0000000000..599825f3de --- /dev/null +++ b/test/custom.minfee/tccq83wm6sjyklkd4utk6hjmewsaccgvzk5sck8cs2y/keys/key @@ -0,0 +1 @@ +{"crypto":{"ciphertext":"ebedec2785dabd7d1380da0f4ca6619125248fa751526d6d17801e5cc777d3b3","cipherparams":{"iv":"4646508b38209c36e40b0aa24e669981"},"cipher":"aes-128-ctr","kdf":"pbkdf2","kdfparams":{"dklen":32,"salt":"d9d54532d8172a461565ab51a8df1ae89d478c2c50750de186f655a33a393fd6","c":10240,"prf":"hmac-sha256"},"mac":"a0c4a20033e5a2e3f3bbc28f3c545b82b939604d45993072518281b710bb93d2"},"id":"b56516a4-734f-435f-83b3-b58ebfcad054","version":3,"address":"e2edea1225bf66d78bb6af2de5d0ee30860ad486","meta":"{}"} \ No newline at end of file diff --git a/test/custom.minfee/tccq83wm6sjyklkd4utk6hjmewsaccgvzk5sck8cs2y/password.json b/test/custom.minfee/tccq83wm6sjyklkd4utk6hjmewsaccgvzk5sck8cs2y/password.json new file mode 100644 index 0000000000..64a06e5fb5 --- /dev/null +++ b/test/custom.minfee/tccq83wm6sjyklkd4utk6hjmewsaccgvzk5sck8cs2y/password.json @@ -0,0 +1 @@ +[{ "address": "tccq83wm6sjyklkd4utk6hjmewsaccgvzk5sck8cs2y", "password": "" }] diff --git a/test/src/config/mem-pool-min-fee1.toml b/test/src/config/mem-pool-min-fee1.toml new file mode 100644 index 0000000000..7211ead60e --- /dev/null +++ b/test/src/config/mem-pool-min-fee1.toml @@ -0,0 +1,18 @@ +[codechain] + +[mining] +min_pay_transaction_cost = 150 + +[network] + +[rpc] + +[ipc] + +[ws] + +[snapshot] + +[stratum] + +[email_alarm] diff --git a/test/src/config/mem-pool-min-fee2.toml b/test/src/config/mem-pool-min-fee2.toml new file mode 100644 index 0000000000..f7dab7f1f3 --- /dev/null +++ b/test/src/config/mem-pool-min-fee2.toml @@ -0,0 +1,18 @@ +[codechain] + +[mining] +min_pay_transaction_cost = 200 + +[network] + +[rpc] + +[ipc] + +[ws] + +[snapshot] + +[stratum] + +[email_alarm] diff --git a/test/src/e2e.long/mempoolMinfee.test.ts b/test/src/e2e.long/mempoolMinfee.test.ts new file mode 100644 index 0000000000..f42eebbff4 --- /dev/null +++ b/test/src/e2e.long/mempoolMinfee.test.ts @@ -0,0 +1,185 @@ +// Copyright 2019 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import * as chai from "chai"; +import { expect } from "chai"; +import * as chaiAsPromised from "chai-as-promised"; +import "mocha"; +import { + validator0Address, + validator1Address, + validator2Address +} from "../helper/constants"; +import CodeChain from "../helper/spawn"; + +chai.use(chaiAsPromised); + +describe("MemPoolMinFees", async function() { + const chain = `${__dirname}/../scheme/tendermint-int.json`; + const config1 = `test/src/config/mem-pool-min-fee1.toml`; + const config2 = `test/src/config/mem-pool-min-fee2.toml`; + let valNode1WithMinPayFee150: CodeChain; + let valNode2WithMinPayFee200: CodeChain; + let valNode3: CodeChain; + const nonValAddress = "tccq83wm6sjyklkd4utk6hjmewsaccgvzk5sck8cs2y"; + let nonValNode: CodeChain; + + beforeEach(async function() { + valNode1WithMinPayFee150 = new CodeChain({ + chain, + argv: [ + "--engine-signer", + validator0Address.toString(), + "--password-path", + "test/tendermint/password.json", + "--force-sealing", + "--config", + config1 + ], + additionalKeysPath: "tendermint/keys" + }); + + valNode2WithMinPayFee200 = new CodeChain({ + chain, + argv: [ + "--engine-signer", + validator1Address.toString(), + "--password-path", + "test/tendermint/password.json", + "--force-sealing", + "--config", + config2 + ], + additionalKeysPath: "tendermint/keys" + }); + + valNode3 = new CodeChain({ + chain, + argv: [ + "--engine-signer", + validator2Address.toString(), + "--password-path", + "test/tendermint/password.json", + "--force-sealing" + ], + additionalKeysPath: "tendermint/keys" + }); + + await valNode1WithMinPayFee150.start(); + await valNode2WithMinPayFee200.start(); + await valNode3.start(); + + await valNode1WithMinPayFee150.connect(valNode2WithMinPayFee200); + await valNode1WithMinPayFee150.connect(valNode3); + await valNode2WithMinPayFee200.connect(valNode3); + + await valNode1WithMinPayFee150.waitPeers(2); + await valNode2WithMinPayFee200.waitPeers(2); + await valNode3.waitPeers(2); + }); + + afterEach(async function() { + await valNode1WithMinPayFee150.clean(); + await valNode2WithMinPayFee200.clean(); + await valNode3.clean(); + }); + + it("A node should accept a transaction with a fee higher than the node's mem pool minimum fee and the block should be propagated", async function() { + const tx = await valNode1WithMinPayFee150.sendPayTx({ + seq: 0, + fee: 175, + quantity: 100_000, + recipient: validator0Address + }); + await valNode1WithMinPayFee150.waitBlockNumber(2); + await valNode2WithMinPayFee200.waitBlockNumber(2); + await valNode3.waitBlockNumber(2); + + expect( + await valNode1WithMinPayFee150.sdk.rpc.chain.containsTransaction( + tx.hash() + ) + ).to.be.true; + expect( + await valNode2WithMinPayFee200.sdk.rpc.chain.containsTransaction( + tx.hash() + ) + ).to.be.true; + expect(await valNode3.sdk.rpc.chain.containsTransaction(tx.hash())).to + .be.true; + }); + + it("Connected validators should reject a transaction with a fee lower than the nodes' mem pool minimum fees", async function() { + nonValNode = new CodeChain({ + chain, + argv: [ + "--engine-signer", + nonValAddress, + "--password-path", + `test/custom.minfee/${nonValAddress}/password.json`, + "--force-sealing" + ], + additionalKeysPath: `tendermint.dynval/${nonValAddress}/keys` + }); + await nonValNode.start(); + await nonValNode.connect(valNode1WithMinPayFee150); + await nonValNode.connect(valNode2WithMinPayFee200); + + await nonValNode.waitPeers(2); + await valNode1WithMinPayFee150.waitPeers(3); + await valNode2WithMinPayFee200.waitPeers(3); + + const nodeArray = [ + valNode1WithMinPayFee150, + valNode2WithMinPayFee200, + valNode3, + nonValNode + ]; + + const txShouldBeRejected = await nonValNode.sendPayTx({ + fee: 145, + quantity: 100_000, + recipient: validator0Address + }); + + const txShouldBeAccepted = await nonValNode.sendPayTx({ + fee: 210, + quantity: 100_000, + recipient: validator0Address + }); + + await Promise.all(nodeArray.map(node => node.waitBlockNumber(4))); + const expectedTrues = await Promise.all( + nodeArray.map(node => + node.sdk.rpc.chain.containsTransaction( + txShouldBeAccepted.hash() + ) + ) + ); + const expectedFalses = await Promise.all( + nodeArray.map(node => + node.sdk.rpc.chain.containsTransaction( + txShouldBeRejected.hash() + ) + ) + ); + + expectedTrues.map(val => expect(val).to.be.true); + expectedFalses.map(val => expect(val).to.be.false); + + await nonValNode.clean(); + }); +});