diff --git a/CHANGELOG.md b/CHANGELOG.md index a96e01af6b1..593c5e36ba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,33 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Description of the upcoming release here. +## [Version 0.22.4] + +### Added + +- [#1743](https://github.com/FuelLabs/fuel-core/pull/1743): Added blacklisting of the transactions on the `TxPool` level. + ```shell + --tx-blacklist-addresses + The list of banned addresses ignored by the `TxPool` + + [env: TX_BLACKLIST_ADDRESSES=] + + --tx-blacklist-coins + The list of banned coins ignored by the `TxPool` + + [env: TX_BLACKLIST_COINS=] + + --tx-blacklist-messages + The list of banned messages ignored by the `TxPool` + + [env: TX_BLACKLIST_MESSAGES=] + + --tx-blacklist-contracts + The list of banned contracts ignored by the `TxPool` + + [env: TX_BLACKLIST_CONTRACTS=] + ``` + ## [Version 0.22.3] ### Added diff --git a/Cargo.lock b/Cargo.lock index a29f4edf886..290e3c80faa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2744,7 +2744,7 @@ dependencies = [ [[package]] name = "fuel-core" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "assert_matches", @@ -2828,11 +2828,11 @@ dependencies = [ [[package]] name = "fuel-core-bft" -version = "0.22.3" +version = "0.22.4" [[package]] name = "fuel-core-bin" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "clap 4.4.11", @@ -2858,7 +2858,7 @@ dependencies = [ [[package]] name = "fuel-core-chain-config" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "bech32", @@ -2877,7 +2877,7 @@ dependencies = [ [[package]] name = "fuel-core-client" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "cynic", @@ -2900,7 +2900,7 @@ dependencies = [ [[package]] name = "fuel-core-client-bin" -version = "0.22.3" +version = "0.22.4" dependencies = [ "clap 4.4.11", "fuel-core-client", @@ -2911,7 +2911,7 @@ dependencies = [ [[package]] name = "fuel-core-consensus-module" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "fuel-core-chain-config", @@ -2923,7 +2923,7 @@ dependencies = [ [[package]] name = "fuel-core-database" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "derive_more", @@ -2934,7 +2934,7 @@ dependencies = [ [[package]] name = "fuel-core-e2e-client" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "assert_cmd", @@ -2959,7 +2959,7 @@ dependencies = [ [[package]] name = "fuel-core-executor" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "fuel-core-chain-config", @@ -2973,7 +2973,7 @@ dependencies = [ [[package]] name = "fuel-core-importer" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "derive_more", @@ -2989,7 +2989,7 @@ dependencies = [ [[package]] name = "fuel-core-keygen" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "clap 4.4.11", @@ -3000,7 +3000,7 @@ dependencies = [ [[package]] name = "fuel-core-keygen-bin" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "atty", @@ -3013,7 +3013,7 @@ dependencies = [ [[package]] name = "fuel-core-metrics" -version = "0.22.3" +version = "0.22.4" dependencies = [ "axum", "once_cell", @@ -3027,7 +3027,7 @@ dependencies = [ [[package]] name = "fuel-core-p2p" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "async-trait", @@ -3071,7 +3071,7 @@ dependencies = [ [[package]] name = "fuel-core-poa" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "async-trait", @@ -3089,7 +3089,7 @@ dependencies = [ [[package]] name = "fuel-core-producer" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "async-trait", @@ -3106,7 +3106,7 @@ dependencies = [ [[package]] name = "fuel-core-relayer" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "async-trait", @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "fuel-core-services" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "async-trait", @@ -3148,7 +3148,7 @@ dependencies = [ [[package]] name = "fuel-core-storage" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "derive_more", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "fuel-core-sync" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "async-trait", @@ -3212,7 +3212,7 @@ dependencies = [ [[package]] name = "fuel-core-trace" -version = "0.22.3" +version = "0.22.4" dependencies = [ "ctor", "tracing", @@ -3222,7 +3222,7 @@ dependencies = [ [[package]] name = "fuel-core-txpool" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "async-trait", @@ -3248,7 +3248,7 @@ dependencies = [ [[package]] name = "fuel-core-types" -version = "0.22.3" +version = "0.22.4" dependencies = [ "anyhow", "bs58 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index c385eb6ac16..8bb4be78a55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,33 +46,33 @@ homepage = "https://fuel.network/" keywords = ["blockchain", "cryptocurrencies", "fuel-vm", "vm"] license = "BUSL-1.1" repository = "https://github.com/FuelLabs/fuel-core" -version = "0.22.3" +version = "0.22.4" [workspace.dependencies] # Workspace members -fuel-core = { version = "0.22.3", path = "./crates/fuel-core", default-features = false } -fuel-core-client-bin = { version = "0.22.3", path = "./bin/client" } -fuel-core-bin = { version = "0.22.3", path = "./bin/fuel-core" } -fuel-core-keygen = { version = "0.22.3", path = "./crates/keygen" } -fuel-core-keygen-bin = { version = "0.22.3", path = "./bin/keygen" } -fuel-core-chain-config = { version = "0.22.3", path = "./crates/chain-config", default-features = false } -fuel-core-client = { version = "0.22.3", path = "./crates/client" } -fuel-core-database = { version = "0.22.3", path = "./crates/database" } -fuel-core-metrics = { version = "0.22.3", path = "./crates/metrics" } -fuel-core-services = { version = "0.22.3", path = "./crates/services" } -fuel-core-consensus-module = { version = "0.22.3", path = "./crates/services/consensus_module" } -fuel-core-bft = { version = "0.22.3", path = "./crates/services/consensus_module/bft" } -fuel-core-poa = { version = "0.22.3", path = "./crates/services/consensus_module/poa" } -fuel-core-executor = { version = "0.22.3", path = "./crates/services/executor" } -fuel-core-importer = { version = "0.22.3", path = "./crates/services/importer" } -fuel-core-p2p = { version = "0.22.3", path = "./crates/services/p2p" } -fuel-core-producer = { version = "0.22.3", path = "./crates/services/producer" } -fuel-core-relayer = { version = "0.22.3", path = "./crates/services/relayer" } -fuel-core-sync = { version = "0.22.3", path = "./crates/services/sync" } -fuel-core-txpool = { version = "0.22.3", path = "./crates/services/txpool" } -fuel-core-storage = { version = "0.22.3", path = "./crates/storage" } -fuel-core-trace = { version = "0.22.3", path = "./crates/trace" } -fuel-core-types = { version = "0.22.3", path = "./crates/types", default-features = false } +fuel-core = { version = "0.22.4", path = "./crates/fuel-core", default-features = false } +fuel-core-client-bin = { version = "0.22.4", path = "./bin/client" } +fuel-core-bin = { version = "0.22.4", path = "./bin/fuel-core" } +fuel-core-keygen = { version = "0.22.4", path = "./crates/keygen" } +fuel-core-keygen-bin = { version = "0.22.4", path = "./bin/keygen" } +fuel-core-chain-config = { version = "0.22.4", path = "./crates/chain-config", default-features = false } +fuel-core-client = { version = "0.22.4", path = "./crates/client" } +fuel-core-database = { version = "0.22.4", path = "./crates/database" } +fuel-core-metrics = { version = "0.22.4", path = "./crates/metrics" } +fuel-core-services = { version = "0.22.4", path = "./crates/services" } +fuel-core-consensus-module = { version = "0.22.4", path = "./crates/services/consensus_module" } +fuel-core-bft = { version = "0.22.4", path = "./crates/services/consensus_module/bft" } +fuel-core-poa = { version = "0.22.4", path = "./crates/services/consensus_module/poa" } +fuel-core-executor = { version = "0.22.4", path = "./crates/services/executor" } +fuel-core-importer = { version = "0.22.4", path = "./crates/services/importer" } +fuel-core-p2p = { version = "0.22.4", path = "./crates/services/p2p" } +fuel-core-producer = { version = "0.22.4", path = "./crates/services/producer" } +fuel-core-relayer = { version = "0.22.4", path = "./crates/services/relayer" } +fuel-core-sync = { version = "0.22.4", path = "./crates/services/sync" } +fuel-core-txpool = { version = "0.22.4", path = "./crates/services/txpool" } +fuel-core-storage = { version = "0.22.4", path = "./crates/storage" } +fuel-core-trace = { version = "0.22.4", path = "./crates/trace" } +fuel-core-types = { version = "0.22.4", path = "./crates/types", default-features = false } fuel-core-tests = { version = "0.0.0", path = "./tests" } fuel-core-xtask = { version = "0.0.0", path = "./xtask" } diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index e6210d8330c..cb1a3015eb6 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -1,15 +1,15 @@ #![allow(unused_variables)] use crate::{ cli::{ - run::consensus::PoATriggerArgs, + run::{ + consensus::PoATriggerArgs, + tx_pool::TxPoolArgs, + }, DEFAULT_DB_PATH, }, FuelService, }; -use anyhow::{ - anyhow, - Context, -}; +use anyhow::Context; use clap::Parser; use fuel_core::{ chain_config::{ @@ -25,7 +25,10 @@ use fuel_core::{ ServiceTrait, VMConfig, }, - txpool::Config as TxPoolConfig, + txpool::{ + config::BlackList, + Config as TxPoolConfig, + }, types::{ blockchain::primitives::SecretKeyWrapper, fuel_tx::ContractId, @@ -64,6 +67,7 @@ mod consensus; mod profiling; #[cfg(feature = "relayer")] mod relayer; +mod tx_pool; /// Run the Fuel client node locally. #[derive(Debug, Clone, Parser)] @@ -151,7 +155,11 @@ pub struct Command { /// /// If not set, `consensus_key` is used as the provider of the `Address`. #[arg(long = "coinbase-recipient", env)] - pub coinbase_recipient: Option, + pub coinbase_recipient: Option, + + /// The cli arguments supported by the `TxPool`. + #[clap(flatten)] + pub tx_pool: TxPoolArgs, #[cfg_attr(feature = "relayer", clap(flatten))] #[cfg(feature = "relayer")] @@ -174,22 +182,6 @@ pub struct Command { #[clap(long = "verify-max-relayer-wait", default_value = "30s", env)] pub max_wait_time: humantime::Duration, - /// The max time to live of the transaction inside of the `TxPool`. - #[clap(long = "tx-pool-ttl", default_value = "5m", env)] - pub tx_pool_ttl: humantime::Duration, - - /// The max number of transactions that the `TxPool` can simultaneously store. - #[clap(long = "tx-max-number", default_value = "4064", env)] - pub tx_max_number: usize, - - /// The max depth of the dependent transactions that supported by the `TxPool`. - #[clap(long = "tx-max-depth", default_value = "10", env)] - pub tx_max_depth: usize, - - /// The maximum number of active subscriptions that supported by the `TxPool`. - #[clap(long = "tx-number-active-subscriptions", default_value = "4064", env)] - pub tx_number_active_subscriptions: usize, - /// The number of reserved peers to connect to before starting to sync. #[clap(long = "min-connected-reserved-peers", default_value = "0", env)] pub min_connected_reserved_peers: usize, @@ -237,10 +229,7 @@ impl Command { metrics, max_da_lag, max_wait_time, - tx_pool_ttl, - tx_max_number, - tx_max_depth, - tx_number_active_subscriptions, + tx_pool, min_connected_reserved_peers, time_until_synced, query_log_threshold_time, @@ -286,10 +275,7 @@ impl Command { } let coinbase_recipient = if let Some(coinbase_recipient) = coinbase_recipient { - Some( - ContractId::from_str(coinbase_recipient.as_str()) - .map_err(|err| anyhow!(err))?, - ) + Some(coinbase_recipient) } else { tracing::warn!("The coinbase recipient `ContractId` is not set!"); None @@ -300,6 +286,24 @@ impl Command { max_wait_time: max_wait_time.into(), }; + let TxPoolArgs { + tx_pool_ttl, + tx_max_number, + tx_max_depth, + tx_number_active_subscriptions, + tx_blacklist_addresses, + tx_blacklist_coins, + tx_blacklist_messages, + tx_blacklist_contracts, + } = tx_pool; + + let blacklist = BlackList::new( + tx_blacklist_addresses, + tx_blacklist_coins, + tx_blacklist_messages, + tx_blacklist_contracts, + ); + let config = Config { addr, api_request_timeout: api_request_timeout.into(), @@ -322,6 +326,7 @@ impl Command { metrics, tx_pool_ttl.into(), tx_number_active_subscriptions, + blacklist, ), block_producer: ProducerConfig { utxo_validation, diff --git a/bin/fuel-core/src/cli/run/tx_pool.rs b/bin/fuel-core/src/cli/run/tx_pool.rs new file mode 100644 index 00000000000..6ad5faef6f5 --- /dev/null +++ b/bin/fuel-core/src/cli/run/tx_pool.rs @@ -0,0 +1,104 @@ +//! Clap configuration related to consensus parameters + +use fuel_core::txpool::types::ContractId; +use fuel_core_types::{ + fuel_tx::{ + Address, + UtxoId, + }, + fuel_types::Nonce, +}; + +#[derive(Debug, Clone, clap::Args)] +pub struct TxPoolArgs { + /// The max time to live of the transaction inside of the `TxPool`. + #[clap(long = "tx-pool-ttl", default_value = "5m", env)] + pub tx_pool_ttl: humantime::Duration, + + /// The max number of transactions that the `TxPool` can simultaneously store. + #[clap(long = "tx-max-number", default_value = "4064", env)] + pub tx_max_number: usize, + + /// The max depth of the dependent transactions that supported by the `TxPool`. + #[clap(long = "tx-max-depth", default_value = "10", env)] + pub tx_max_depth: usize, + + /// The maximum number of active subscriptions that supported by the `TxPool`. + #[clap(long = "tx-number-active-subscriptions", default_value = "4064", env)] + pub tx_number_active_subscriptions: usize, + + /// The list of banned addresses ignored by the `TxPool`. + #[clap(long = "tx-blacklist-addresses", value_delimiter = ',', env)] + pub tx_blacklist_addresses: Vec
, + + /// The list of banned coins ignored by the `TxPool`. + #[clap(long = "tx-blacklist-coins", value_delimiter = ',', env)] + pub tx_blacklist_coins: Vec, + + /// The list of banned messages ignored by the `TxPool`. + #[clap(long = "tx-blacklist-messages", value_delimiter = ',', env)] + pub tx_blacklist_messages: Vec, + + /// The list of banned contracts ignored by the `TxPool`. + #[clap(long = "tx-blacklist-contracts", value_delimiter = ',', env)] + pub tx_blacklist_contracts: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + use fuel_core::txpool::config::BlackList; + use test_case::test_case; + + #[derive(Debug, Clone, Parser)] + pub struct Command { + #[clap(flatten)] + tx_pool: TxPoolArgs, + } + + fn blacklist( + a: Vec
, + c: Vec, + m: Vec, + ct: Vec, + ) -> BlackList { + BlackList::new(a, c, m, ct) + } + + #[test_case(&[""] => Ok(blacklist(vec![], vec![], vec![], vec![])); "defaults works")] + #[test_case(&["", "--tx-blacklist-addresses=\ + 0x0000000000000000000000000000000000000000000000000000000000000000,\ + 0101010101010101010101010101010101010101010101010101010101010101" + ] + => Ok(blacklist(vec![[0; 32].into(), [1; 32].into()], vec![], vec![], vec![])); "addresses works")] + #[test_case(&["", "--tx-blacklist-coins=\ + 0x000000000000000000000000000000000000000000000000000000000000000002,\ + 010101010101010101010101010101010101010101010101010101010101010103" + ] + => Ok(blacklist(vec![], vec![UtxoId::new([0; 32].into(), 2), UtxoId::new([1; 32].into(), 3)], vec![], vec![])); "coins works")] + #[test_case(&["", "--tx-blacklist-messages=\ + 0x0000000000000000000000000000000000000000000000000000000000000000,\ + 0101010101010101010101010101010101010101010101010101010101010101" + ] + => Ok(blacklist(vec![], vec![], vec![[0; 32].into(), [1; 32].into()], vec![])); "messages works")] + #[test_case(&["", "--tx-blacklist-contracts=\ + 0x0000000000000000000000000000000000000000000000000000000000000000,\ + 0101010101010101010101010101010101010101010101010101010101010101" + ] + => Ok(blacklist(vec![], vec![], vec![], vec![[0; 32].into(), [1; 32].into()])); "contracts works")] + fn parse(args: &[&str]) -> Result { + let command: Command = + Command::try_parse_from(args).map_err(|e| e.to_string())?; + let args = command.tx_pool; + + let blacklist = blacklist( + args.tx_blacklist_addresses, + args.tx_blacklist_coins, + args.tx_blacklist_messages, + args.tx_blacklist_contracts, + ); + + Ok(blacklist) + } +} diff --git a/crates/services/txpool/src/config.rs b/crates/services/txpool/src/config.rs index 78b82c5445c..6c6a46d0525 100644 --- a/crates/services/txpool/src/config.rs +++ b/crates/services/txpool/src/config.rs @@ -1,5 +1,60 @@ +use crate::types::ContractId; use fuel_core_chain_config::ChainConfig; -use std::time::Duration; +use fuel_core_types::{ + fuel_tx::{ + Address, + UtxoId, + }, + fuel_types::Nonce, +}; +use std::{ + collections::HashSet, + time::Duration, +}; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct BlackList { + /// Blacklisted addresses. + pub(crate) owners: HashSet
, + /// Blacklisted UTXO ids. + pub(crate) coins: HashSet, + /// Blacklisted messages by `Nonce`. + pub(crate) messages: HashSet, + /// Blacklisted contracts. + pub(crate) contracts: HashSet, +} + +impl BlackList { + pub fn new( + owners: Vec
, + utxo_ids: Vec, + messages: Vec, + contracts: Vec, + ) -> Self { + Self { + owners: owners.into_iter().collect(), + coins: utxo_ids.into_iter().collect(), + messages: messages.into_iter().collect(), + contracts: contracts.into_iter().collect(), + } + } + + pub fn contains_address(&self, address: &Address) -> bool { + self.owners.contains(address) + } + + pub fn contains_coin(&self, utxo_id: &UtxoId) -> bool { + self.coins.contains(utxo_id) + } + + pub fn contains_message(&self, nonce: &Nonce) -> bool { + self.messages.contains(nonce) + } + + pub fn contains_contract(&self, contract_id: &ContractId) -> bool { + self.contracts.contains(contract_id) + } +} #[derive(Debug, Clone)] pub struct Config { @@ -19,6 +74,8 @@ pub struct Config { pub transaction_ttl: Duration, /// The number of allowed active transaction status subscriptions. pub number_of_active_subscription: usize, + /// The blacklist used to validate transaction. + pub blacklist: BlackList, } impl Default for Config { @@ -40,6 +97,7 @@ impl Default for Config { metrics, transaction_ttl, number_of_active_subscription, + Default::default(), ) } } @@ -55,6 +113,7 @@ impl Config { metrics: bool, transaction_ttl: Duration, number_of_active_subscription: usize, + blacklist: BlackList, ) -> Self { // # Dev-note: If you add a new field, be sure that this field is propagated correctly // in all places where `new` is used. @@ -67,6 +126,7 @@ impl Config { metrics, transaction_ttl, number_of_active_subscription, + blacklist, } } } diff --git a/crates/services/txpool/src/service/update_sender/tests/test_e2e.rs b/crates/services/txpool/src/service/update_sender/tests/test_e2e.rs index b3871b06e86..482839b6679 100644 --- a/crates/services/txpool/src/service/update_sender/tests/test_e2e.rs +++ b/crates/services/txpool/src/service/update_sender/tests/test_e2e.rs @@ -144,7 +144,7 @@ fn test_update_sender_inner(ops: Vec) { Op::DropRecv(i) => { // Real if i < receivers.len() { - receivers.remove(i); + let _ = receivers.remove(i); } // Model if i < model_receivers.len() { diff --git a/crates/services/txpool/src/service/update_sender/tests/test_subscribe.rs b/crates/services/txpool/src/service/update_sender/tests/test_subscribe.rs index 4c0795be410..936cbadaae9 100644 --- a/crates/services/txpool/src/service/update_sender/tests/test_subscribe.rs +++ b/crates/services/txpool/src/service/update_sender/tests/test_subscribe.rs @@ -23,7 +23,7 @@ fn test_subscriber(input: Input) { let Input { tx_id, senders } = input; let mut senders = box_senders(senders); let len_before = senders.values().map(|v| v.len()).sum::(); - subscribe::<_, MockCreateChannel>( + let _ = subscribe::<_, MockCreateChannel>( Bytes32::from([tx_id; 32]), &mut senders, Box::new(()), diff --git a/crates/services/txpool/src/txpool.rs b/crates/services/txpool/src/txpool.rs index 50c7d2484e0..610dd191b99 100644 --- a/crates/services/txpool/src/txpool.rs +++ b/crates/services/txpool/src/txpool.rs @@ -36,7 +36,24 @@ use fuel_core_types::{ }; use fuel_core_metrics::txpool_metrics::txpool_metrics; -use fuel_core_types::fuel_vm::checked_transaction::CheckPredicateParams; +use fuel_core_types::{ + fuel_tx::{ + input::{ + coin::{ + CoinPredicate, + CoinSigned, + }, + message::{ + MessageCoinPredicate, + MessageCoinSigned, + MessageDataPredicate, + MessageDataSigned, + }, + }, + Input, + }, + fuel_vm::checked_transaction::CheckPredicateParams, +}; use std::{ cmp::Reverse, collections::HashMap, @@ -45,6 +62,11 @@ use std::{ }; use tokio_rayon::AsyncRayonHandle; +#[cfg(test)] +mod test_helpers; +#[cfg(test)] +mod tests; + #[derive(Debug, Clone)] pub struct TxPool { by_hash: HashMap, @@ -77,6 +99,11 @@ where &self.config } + #[cfg(test)] + pub fn config_mut(&mut self) -> &mut Config { + &mut self.config + } + pub fn txs(&self) -> &HashMap { &self.by_hash } @@ -101,6 +128,8 @@ where } }); + self.check_blacklisting(tx.as_ref())?; + if !tx.is_computed() { return Err(Error::NoMetadata.into()) } @@ -232,12 +261,10 @@ where let mut res = Vec::new(); for tx in txs.into_iter() { - res.push(self.insert_inner(tx)); - } + let tx_id = tx.id(); + let result = self.insert_inner(tx); - // announce to subscribers - for ret in res.iter() { - match ret { + match &result { Ok(InsertionResult { removed, inserted, @@ -253,11 +280,15 @@ where Tai64::from_unix(submitted_time.as_secs() as i64), ); } - Err(_) => { - // @dev should not broadcast tx if error occurred + Err(err) => { + tx_status_sender + .send_squeezed_out(tx_id, Error::SqueezedOut(err.to_string())); } } + + res.push(result); } + res } @@ -370,6 +401,85 @@ where result } + + fn check_blacklisting(&self, tx: &PoolTransaction) -> anyhow::Result<()> { + for input in tx.inputs() { + match input { + Input::CoinSigned(CoinSigned { utxo_id, owner, .. }) + | Input::CoinPredicate(CoinPredicate { utxo_id, owner, .. }) => { + if self.config.blacklist.contains_coin(utxo_id) { + return Err(anyhow::anyhow!( + "The UTXO `{}` is blacklisted", + utxo_id + )) + } + if self.config.blacklist.contains_address(owner) { + return Err(anyhow::anyhow!( + "The owner `{}` is blacklisted", + owner + )) + } + } + Input::Contract(contract) => { + if self + .config + .blacklist + .contains_contract(&contract.contract_id) + { + return Err(anyhow::anyhow!( + "The contract `{}` is blacklisted", + contract.contract_id + )) + } + } + Input::MessageCoinSigned(MessageCoinSigned { + nonce, + sender, + recipient, + .. + }) + | Input::MessageCoinPredicate(MessageCoinPredicate { + nonce, + sender, + recipient, + .. + }) + | Input::MessageDataSigned(MessageDataSigned { + nonce, + sender, + recipient, + .. + }) + | Input::MessageDataPredicate(MessageDataPredicate { + nonce, + sender, + recipient, + .. + }) => { + if self.config.blacklist.contains_message(nonce) { + return Err(anyhow::anyhow!( + "The message `{}` is blacklisted", + nonce + )) + } + if self.config.blacklist.contains_address(sender) { + return Err(anyhow::anyhow!( + "The sender `{}` is blacklisted", + sender + )) + } + if self.config.blacklist.contains_address(recipient) { + return Err(anyhow::anyhow!( + "The recipient `{}` is blacklisted", + recipient + )) + } + } + } + } + + Ok(()) + } } pub async fn check_transactions( @@ -464,8 +574,3 @@ impl ParallelExecutor for TokioWithRayon { futures::future::join_all(futures).await } } - -#[cfg(test)] -mod test_helpers; -#[cfg(test)] -mod tests; diff --git a/crates/services/txpool/src/txpool/tests.rs b/crates/services/txpool/src/txpool/tests.rs index 2e4c7706d56..a60a5100965 100644 --- a/crates/services/txpool/src/txpool/tests.rs +++ b/crates/services/txpool/src/txpool/tests.rs @@ -46,6 +46,7 @@ use fuel_core_types::{ fuel_vm::checked_transaction::Checked, }; +use fuel_core_types::fuel_tx::Finalizable; use std::{ cmp::Reverse, collections::HashMap, @@ -93,6 +94,126 @@ async fn insert_simple_tx_succeeds() { .expect("Transaction should be OK, got Err"); } +#[tokio::test] +async fn insert_simple_tx_with_blacklisted_utxo_id_fails() { + let mut rng = StdRng::seed_from_u64(0); + let db = MockDb::default(); + let mut txpool = TxPool::new(Default::default(), db.clone()); + + let (_, gas_coin) = setup_coin(&mut rng, Some(&txpool.database)); + let tx = TransactionBuilder::script(vec![], vec![]) + .script_gas_limit(GAS_LIMIT) + .add_input(gas_coin.clone()) + .finalize_as_transaction(); + let tx = check_unwrap_tx(tx, db.clone(), &txpool.config).await; + let utxo_id = *gas_coin.utxo_id().unwrap(); + + // Given + txpool.config_mut().blacklist.coins.insert(utxo_id); + + // When + let result = txpool.insert_inner(tx); + + // Then + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains(format!("The UTXO `{}` is blacklisted", utxo_id).as_str())); +} + +#[tokio::test] +async fn insert_simple_tx_with_blacklisted_owner_fails() { + let mut rng = StdRng::seed_from_u64(0); + let db = MockDb::default(); + let mut txpool = TxPool::new(Default::default(), db.clone()); + + let (_, gas_coin) = setup_coin(&mut rng, Some(&txpool.database)); + let tx = TransactionBuilder::script(vec![], vec![]) + .script_gas_limit(GAS_LIMIT) + .add_input(gas_coin.clone()) + .finalize_as_transaction(); + let tx = check_unwrap_tx(tx, db.clone(), &txpool.config).await; + let owner = *gas_coin.input_owner().unwrap(); + + // Given + txpool.config_mut().blacklist.owners.insert(owner); + + // When + let result = txpool.insert_inner(tx); + + // Then + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains(format!("The owner `{}` is blacklisted", owner).as_str())); +} + +#[tokio::test] +async fn insert_simple_tx_with_blacklisted_contract_fails() { + let mut rng = StdRng::seed_from_u64(0); + let db = MockDb::default(); + let mut txpool = TxPool::new(Default::default(), db.clone()); + let contract_id = Contract::EMPTY_CONTRACT_ID; + + let (_, gas_coin) = setup_coin(&mut rng, Some(&txpool.database)); + let tx = TransactionBuilder::script(vec![], vec![]) + .script_gas_limit(GAS_LIMIT) + .add_input(gas_coin.clone()) + .add_input(create_contract_input( + Default::default(), + Default::default(), + contract_id, + )) + .add_output(Output::contract(1, Default::default(), Default::default())) + .finalize_as_transaction(); + let tx = check_unwrap_tx(tx, db.clone(), &txpool.config).await; + + // Given + txpool.config_mut().blacklist.contracts.insert(contract_id); + + // When + let result = txpool.insert_inner(tx); + + // Then + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains(format!("The contract `{}` is blacklisted", contract_id).as_str())); +} + +#[tokio::test] +async fn insert_simple_tx_with_blacklisted_message_fails() { + let (message, input) = create_message_predicate_from_message(5000, 0); + + let tx = TransactionBuilder::script(vec![], vec![]) + .script_gas_limit(GAS_LIMIT) + .add_input(input) + .finalize_as_transaction(); + + let nonce = message.nonce; + let db = MockDb::default(); + db.insert_message(message); + let mut txpool = TxPool::new(Default::default(), db.clone()); + + let tx = check_unwrap_tx(tx, db.clone(), &txpool.config).await; + + // Given + txpool.config_mut().blacklist.messages.insert(nonce); + + // When + let result = txpool.insert_inner(tx); + + // Then + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains(format!("The message `{}` is blacklisted", nonce).as_str())); +} + #[tokio::test] async fn insert_simple_tx_dependency_chain_succeeds() { let mut rng = StdRng::seed_from_u64(0); @@ -236,7 +357,10 @@ async fn not_inserted_known_tx() { let db = MockDb::default(); let mut txpool = TxPool::new(config, db.clone()); - let tx = Transaction::default_test_tx(); + let tx = TransactionBuilder::script(vec![], vec![]) + .add_random_fee_input() + .finalize() + .into(); let tx = check_unwrap_tx(tx, db.clone(), &txpool.config).await; txpool diff --git a/deployment/charts/Chart.yaml b/deployment/charts/Chart.yaml index efe91b8828a..0ded0bd21ac 100644 --- a/deployment/charts/Chart.yaml +++ b/deployment/charts/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: ${fuel_core_service_name} description: ${fuel_core_service_name} Helm Chart type: application -appVersion: "0.22.3" +appVersion: "0.22.4" version: 0.1.0