Skip to content

Commit 1c2f766

Browse files
Apply mempool dust prevention patch + version prep (#286)
* reject spam transactions * allow multiple grpc client connections from the same ip * log utxo set size * improve spam logs * rollback virtual utxoset sweep on startup * block dust txs only on mainnet * bump version to 0.1.7 * fine tune DB file limits for non-consensus DBs
1 parent 79bc631 commit 1c2f766

File tree

12 files changed

+201
-116
lines changed

12 files changed

+201
-116
lines changed

Cargo.lock

Lines changed: 50 additions & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ members = [
5454
]
5555

5656
[workspace.package]
57-
version = "0.1.6"
57+
version = "0.1.7"
5858
authors = ["Kaspa developers"]
5959
license = "MIT/Apache-2.0"
6060
edition = "2021"
@@ -70,53 +70,53 @@ include = [
7070
]
7171

7272
[workspace.dependencies]
73-
# kaspa-testing-integration = { version = "0.1.2", path = "testing/integration" }
74-
kaspa-os = { version = "0.1.6", path = "kaspa-os" }
75-
kaspa-daemon = { version = "0.1.6", path = "daemon" }
76-
kaspa-addresses = { version = "0.1.6", path = "crypto/addresses" }
77-
kaspa-addressmanager = { version = "0.1.6", path = "components/addressmanager" }
78-
kaspa-bip32 = { version = "0.1.6", path = "wallet/bip32" }
79-
kaspa-connectionmanager = { version = "0.1.6", path = "components/connectionmanager" }
80-
kaspa-consensus = { version = "0.1.6", path = "consensus" }
81-
kaspa-consensus-core = { version = "0.1.6", path = "consensus/core" }
82-
kaspa-consensus-notify = { version = "0.1.6", path = "consensus/notify" }
83-
kaspa-consensus-wasm = { version = "0.1.6", path = "consensus/wasm" }
84-
kaspa-consensusmanager = { version = "0.1.6", path = "components/consensusmanager" }
85-
kaspa-core = { version = "0.1.6", path = "core" }
86-
kaspa-database = { version = "0.1.6", path = "database" }
87-
kaspa-grpc-client = { version = "0.1.6", path = "rpc/grpc/client" }
88-
kaspa-grpc-core = { version = "0.1.6", path = "rpc/grpc/core" }
89-
kaspa-grpc-server = { version = "0.1.6", path = "rpc/grpc/server" }
90-
kaspa-hashes = { version = "0.1.6", path = "crypto/hashes" }
91-
kaspa-index-core = { version = "0.1.6", path = "indexes/core" }
92-
kaspa-index-processor = { version = "0.1.6", path = "indexes/processor" }
93-
kaspa-math = { version = "0.1.6", path = "math" }
94-
kaspa-merkle = { version = "0.1.6", path = "crypto/merkle" }
95-
kaspa-mining = { version = "0.1.6", path = "mining" }
73+
# kaspa-testing-integration = { version = "0.1.7", path = "testing/integration" }
74+
kaspa-os = { version = "0.1.7", path = "kaspa-os" }
75+
kaspa-daemon = { version = "0.1.7", path = "daemon" }
76+
kaspa-addresses = { version = "0.1.7", path = "crypto/addresses" }
77+
kaspa-addressmanager = { version = "0.1.7", path = "components/addressmanager" }
78+
kaspa-bip32 = { version = "0.1.7", path = "wallet/bip32" }
79+
kaspa-connectionmanager = { version = "0.1.7", path = "components/connectionmanager" }
80+
kaspa-consensus = { version = "0.1.7", path = "consensus" }
81+
kaspa-consensus-core = { version = "0.1.7", path = "consensus/core" }
82+
kaspa-consensus-notify = { version = "0.1.7", path = "consensus/notify" }
83+
kaspa-consensus-wasm = { version = "0.1.7", path = "consensus/wasm" }
84+
kaspa-consensusmanager = { version = "0.1.7", path = "components/consensusmanager" }
85+
kaspa-core = { version = "0.1.7", path = "core" }
86+
kaspa-database = { version = "0.1.7", path = "database" }
87+
kaspa-grpc-client = { version = "0.1.7", path = "rpc/grpc/client" }
88+
kaspa-grpc-core = { version = "0.1.7", path = "rpc/grpc/core" }
89+
kaspa-grpc-server = { version = "0.1.7", path = "rpc/grpc/server" }
90+
kaspa-hashes = { version = "0.1.7", path = "crypto/hashes" }
91+
kaspa-index-core = { version = "0.1.7", path = "indexes/core" }
92+
kaspa-index-processor = { version = "0.1.7", path = "indexes/processor" }
93+
kaspa-math = { version = "0.1.7", path = "math" }
94+
kaspa-merkle = { version = "0.1.7", path = "crypto/merkle" }
95+
kaspa-mining = { version = "0.1.7", path = "mining" }
9696
kaspa-mining-errors = { path = "mining/errors" }
97-
kaspa-muhash = { version = "0.1.6", path = "crypto/muhash" }
98-
kaspa-notify = { version = "0.1.6", path = "notify" }
99-
kaspa-p2p-flows = { version = "0.1.6", path = "protocol/flows" }
100-
kaspa-p2p-lib = { version = "0.1.6", path = "protocol/p2p" }
101-
kaspa-pow = { version = "0.1.6", path = "consensus/pow" }
102-
kaspa-rpc-core = { version = "0.1.6", path = "rpc/core" }
103-
kaspa-rpc-macros = { version = "0.1.6", path = "rpc/macros" }
104-
kaspa-rpc-service = { version = "0.1.6", path = "rpc/service" }
105-
kaspa-txscript = { version = "0.1.6", path = "crypto/txscript" }
106-
kaspa-txscript-errors = { version = "0.1.6", path = "crypto/txscript/errors" }
107-
kaspa-utils = { version = "0.1.6", path = "utils" }
108-
kaspa-utxoindex = { version = "0.1.6", path = "indexes/utxoindex" }
109-
kaspa-wallet = { version = "0.1.6", path = "wallet/native" }
110-
kaspa-cli = { version = "0.1.6", path = "cli" }
111-
kaspa-wallet-cli-wasm = { version = "0.1.6", path = "wallet/wasm" }
112-
kaspa-wallet-core = { version = "0.1.6", path = "wallet/core" }
113-
kaspa-wasm = { version = "0.1.6", path = "wasm" }
114-
kaspa-wrpc-core = { version = "0.1.6", path = "rpc/wrpc/core" }
115-
kaspa-wrpc-client = { version = "0.1.6", path = "rpc/wrpc/client" }
116-
kaspa-wrpc-proxy = { version = "0.1.6", path = "rpc/wrpc/proxy" }
117-
kaspa-wrpc-server = { version = "0.1.6", path = "rpc/wrpc/server" }
118-
kaspa-wrpc-wasm = { version = "0.1.6", path = "rpc/wrpc/wasm" }
119-
kaspad = { version = "0.1.6", path = "kaspad" }
97+
kaspa-muhash = { version = "0.1.7", path = "crypto/muhash" }
98+
kaspa-notify = { version = "0.1.7", path = "notify" }
99+
kaspa-p2p-flows = { version = "0.1.7", path = "protocol/flows" }
100+
kaspa-p2p-lib = { version = "0.1.7", path = "protocol/p2p" }
101+
kaspa-pow = { version = "0.1.7", path = "consensus/pow" }
102+
kaspa-rpc-core = { version = "0.1.7", path = "rpc/core" }
103+
kaspa-rpc-macros = { version = "0.1.7", path = "rpc/macros" }
104+
kaspa-rpc-service = { version = "0.1.7", path = "rpc/service" }
105+
kaspa-txscript = { version = "0.1.7", path = "crypto/txscript" }
106+
kaspa-txscript-errors = { version = "0.1.7", path = "crypto/txscript/errors" }
107+
kaspa-utils = { version = "0.1.7", path = "utils" }
108+
kaspa-utxoindex = { version = "0.1.7", path = "indexes/utxoindex" }
109+
kaspa-wallet = { version = "0.1.7", path = "wallet/native" }
110+
kaspa-cli = { version = "0.1.7", path = "cli" }
111+
kaspa-wallet-cli-wasm = { version = "0.1.7", path = "wallet/wasm" }
112+
kaspa-wallet-core = { version = "0.1.7", path = "wallet/core" }
113+
kaspa-wasm = { version = "0.1.7", path = "wasm" }
114+
kaspa-wrpc-core = { version = "0.1.7", path = "rpc/wrpc/core" }
115+
kaspa-wrpc-client = { version = "0.1.7", path = "rpc/wrpc/client" }
116+
kaspa-wrpc-proxy = { version = "0.1.7", path = "rpc/wrpc/proxy" }
117+
kaspa-wrpc-server = { version = "0.1.7", path = "rpc/wrpc/server" }
118+
kaspa-wrpc-wasm = { version = "0.1.7", path = "rpc/wrpc/wasm" }
119+
kaspad = { version = "0.1.7", path = "kaspad" }
120120
kaspa-perf-monitor = { path = "metrics/perf_monitor" }
121121

122122
# external

consensus/core/src/network.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ impl NetworkId {
191191
self.network_type
192192
}
193193

194+
pub fn is_mainnet(&self) -> bool {
195+
self.network_type == NetworkType::Mainnet
196+
}
197+
194198
pub fn suffix(&self) -> Option<u32> {
195199
self.suffix
196200
}

kaspad/src/daemon.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const DEFAULT_DATA_DIR: &str = "datadir";
3131
const CONSENSUS_DB: &str = "consensus";
3232
const UTXOINDEX_DB: &str = "utxoindex";
3333
const META_DB: &str = "meta";
34+
const UTXO_INDEX_DB_FILE_LIMIT: i32 = 100;
35+
const META_DB_FILE_LIMIT: i32 = 5;
3436
const DEFAULT_LOG_DIR: &str = "logs";
3537

3638
fn get_home_dir() -> PathBuf {
@@ -192,7 +194,8 @@ do you confirm? (answer y/n or pass --yes to the Kaspad command line to confirm
192194
}
193195

194196
// DB used for addresses store and for multi-consensus management
195-
let mut meta_db = kaspa_database::prelude::ConnBuilder::default().with_db_path(meta_db_dir.clone()).build();
197+
let mut meta_db =
198+
kaspa_database::prelude::ConnBuilder::default().with_files_limit(META_DB_FILE_LIMIT).with_db_path(meta_db_dir.clone()).build();
196199

197200
// TEMP: upgrade from Alpha version or any version before this one
198201
if meta_db.get_pinned(b"multi-consensus-metadata-key").is_ok_and(|r| r.is_some()) {
@@ -213,7 +216,8 @@ do you confirm? (answer y/n or pass --yes to the Kaspad command line to confirm
213216
fs::create_dir_all(utxoindex_db_dir.as_path()).unwrap();
214217

215218
// Reopen the DB
216-
meta_db = kaspa_database::prelude::ConnBuilder::default().with_db_path(meta_db_dir).build();
219+
meta_db =
220+
kaspa_database::prelude::ConnBuilder::default().with_files_limit(META_DB_FILE_LIMIT).with_db_path(meta_db_dir).build();
217221
}
218222

219223
let connect_peers = args.connect_peers.iter().map(|x| x.normalize(config.default_p2p_port())).collect::<Vec<_>>();
@@ -266,7 +270,10 @@ do you confirm? (answer y/n or pass --yes to the Kaspad command line to confirm
266270
let notify_service = Arc::new(NotifyService::new(notification_root.clone(), notification_recv));
267271
let index_service: Option<Arc<IndexService>> = if args.utxoindex {
268272
// Use only a single thread for none-consensus databases
269-
let utxoindex_db = kaspa_database::prelude::ConnBuilder::default().with_db_path(utxoindex_db_dir).build();
273+
let utxoindex_db = kaspa_database::prelude::ConnBuilder::default()
274+
.with_files_limit(UTXO_INDEX_DB_FILE_LIMIT)
275+
.with_db_path(utxoindex_db_dir)
276+
.build();
270277
let utxoindex = UtxoIndexProxy::new(UtxoIndex::new(consensus_manager.clone(), utxoindex_db).unwrap());
271278
let index_service = Arc::new(IndexService::new(&notify_service.notifier(), Some(utxoindex)));
272279
Some(index_service)
@@ -275,8 +282,13 @@ do you confirm? (answer y/n or pass --yes to the Kaspad command line to confirm
275282
};
276283

277284
let address_manager = AddressManager::new(config.clone(), meta_db);
278-
let mining_manager =
279-
MiningManagerProxy::new(Arc::new(MiningManager::new(config.target_time_per_block, false, config.max_block_mass, None)));
285+
let mining_manager = MiningManagerProxy::new(Arc::new(MiningManager::new_with_spam_blocking_option(
286+
network.is_mainnet(),
287+
config.target_time_per_block,
288+
false,
289+
config.max_block_mass,
290+
None,
291+
)));
280292

281293
let flow_context = Arc::new(FlowContext::new(
282294
consensus_manager.clone(),

mining/errors/src/mempool.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ pub enum RuleError {
6666
// TODO: This error is added for the tx_relay flow but is never constructed neither in the golang nor in this version. Discuss if it can be removed.
6767
#[error("transaction {0} is invalid")]
6868
RejectInvalid(TransactionId),
69+
70+
#[error("Rejected spam tx {0} from mempool")]
71+
RejectSpamTransaction(TransactionId),
6972
}
7073

7174
impl From<NonStandardError> for RuleError {

mining/src/manager.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ impl MiningManager {
4444
Self::with_config(config, cache_lifetime)
4545
}
4646

47+
pub fn new_with_spam_blocking_option(
48+
block_spam_txs: bool,
49+
target_time_per_block: u64,
50+
relay_non_std_transactions: bool,
51+
max_block_mass: u64,
52+
cache_lifetime: Option<u64>,
53+
) -> Self {
54+
let config = Config::build_default_with_spam_blocking_option(
55+
block_spam_txs,
56+
target_time_per_block,
57+
relay_non_std_transactions,
58+
max_block_mass,
59+
);
60+
Self::with_config(config, cache_lifetime)
61+
}
62+
4763
pub(crate) fn with_config(config: Config, cache_lifetime: Option<u64>) -> Self {
4864
let block_template_builder = BlockTemplateBuilder::new(config.maximum_mass_per_block);
4965
let mempool = RwLock::new(Mempool::new(config));

mining/src/mempool/config.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub struct Config {
3838
pub minimum_relay_transaction_fee: u64,
3939
pub minimum_standard_transaction_version: u16,
4040
pub maximum_standard_transaction_version: u16,
41+
pub block_spam_txs: bool,
4142
}
4243

4344
impl Config {
@@ -56,6 +57,7 @@ impl Config {
5657
minimum_relay_transaction_fee: u64,
5758
minimum_standard_transaction_version: u16,
5859
maximum_standard_transaction_version: u16,
60+
block_spam_txs: bool,
5961
) -> Self {
6062
Self {
6163
maximum_transaction_count,
@@ -71,12 +73,13 @@ impl Config {
7173
minimum_relay_transaction_fee,
7274
minimum_standard_transaction_version,
7375
maximum_standard_transaction_version,
76+
block_spam_txs,
7477
}
7578
}
7679

7780
/// Build a default config.
7881
/// The arguments should be obtained from the current consensus [`kaspa_consensus_core::config::params::Params`] instance.
79-
pub fn build_default(target_milliseconds_per_block: u64, relay_non_std_transactions: bool, max_block_mass: u64) -> Self {
82+
pub const fn build_default(target_milliseconds_per_block: u64, relay_non_std_transactions: bool, max_block_mass: u64) -> Self {
8083
Self {
8184
maximum_transaction_count: DEFAULT_MAXIMUM_TRANSACTION_COUNT,
8285
transaction_expire_interval_daa_score: DEFAULT_TRANSACTION_EXPIRE_INTERVAL_SECONDS * 1000 / target_milliseconds_per_block,
@@ -92,6 +95,18 @@ impl Config {
9295
minimum_relay_transaction_fee: DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE,
9396
minimum_standard_transaction_version: DEFAULT_MINIMUM_STANDARD_TRANSACTION_VERSION,
9497
maximum_standard_transaction_version: DEFAULT_MAXIMUM_STANDARD_TRANSACTION_VERSION,
98+
block_spam_txs: false,
9599
}
96100
}
101+
102+
/// Build a default config with optional spam blocking.
103+
/// The arguments should be obtained from the current consensus [`kaspa_consensus_core::config::params::Params`] instance.
104+
pub const fn build_default_with_spam_blocking_option(
105+
block_spam_txs: bool,
106+
target_milliseconds_per_block: u64,
107+
relay_non_std_transactions: bool,
108+
max_block_mass: u64,
109+
) -> Self {
110+
Self { block_spam_txs, ..Self::build_default(target_milliseconds_per_block, relay_non_std_transactions, max_block_mass) }
111+
}
97112
}

mining/src/mempool/validate_and_insert_transaction.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::mempool::{
77
};
88
use kaspa_consensus_core::{
99
api::ConsensusApi,
10-
constants::UNACCEPTED_DAA_SCORE,
10+
constants::{SOMPI_PER_KASPA, UNACCEPTED_DAA_SCORE},
1111
tx::{MutableTransaction, Transaction, TransactionId, TransactionOutpoint, UtxoEntry},
1212
};
1313
use kaspa_core::info;
@@ -84,6 +84,21 @@ impl Mempool {
8484
}
8585

8686
fn validate_transaction_in_context(&self, transaction: &MutableTransaction) -> RuleResult<()> {
87+
if self.config.block_spam_txs {
88+
// TEMP: apply go-kaspad mempool dust prevention patch
89+
// Note: we do not apply the part of the patch which modifies BBT since
90+
// we do not support BBT on mainnet yet
91+
let has_coinbase_input = transaction.entries.iter().any(|e| e.as_ref().unwrap().is_coinbase);
92+
let num_extra_outs = transaction.tx.outputs.len() as i64 - transaction.tx.inputs.len() as i64;
93+
if !has_coinbase_input
94+
&& num_extra_outs > 2
95+
&& transaction.calculated_fee.unwrap() < num_extra_outs as u64 * SOMPI_PER_KASPA
96+
{
97+
kaspa_core::trace!("Rejected spam tx {} from mempool ({} outputs)", transaction.id(), transaction.tx.outputs.len());
98+
return Err(RuleError::RejectSpamTransaction(transaction.id()));
99+
}
100+
}
101+
87102
if !self.config.accept_non_standard {
88103
self.check_transaction_standard_in_context(transaction)?;
89104
}

protocol/flows/src/v5/txrelay/flow.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub struct RelayTransactionsFlow {
4444
invs_route: IncomingRoute,
4545
/// A route for other messages such as Transaction and TransactionNotFound
4646
msg_route: IncomingRoute,
47+
48+
/// Track the number of spam txs coming from this peer
49+
spam_counter: u64,
4750
}
4851

4952
#[async_trait::async_trait]
@@ -59,7 +62,7 @@ impl Flow for RelayTransactionsFlow {
5962

6063
impl RelayTransactionsFlow {
6164
pub fn new(ctx: FlowContext, router: Arc<Router>, invs_route: IncomingRoute, msg_route: IncomingRoute) -> Self {
62-
Self { ctx, router, invs_route, msg_route }
65+
Self { ctx, router, invs_route, msg_route, spam_counter: 0 }
6366
}
6467

6568
pub fn invs_channel_size() -> usize {
@@ -190,9 +193,18 @@ impl RelayTransactionsFlow {
190193
self.ctx.broadcast_transactions(accepted_transactions.iter().map(|x| x.id())).await?;
191194
}
192195
Err(MiningManagerError::MempoolError(err)) => {
193-
if let RuleError::RejectInvalid(_) = err {
194-
// TODO: discuss a banning process
195-
return Err(ProtocolError::MisbehavingPeer(format!("rejected invalid transaction {}", transaction_id)));
196+
match err {
197+
RuleError::RejectInvalid(_) => {
198+
// TODO: discuss a banning process
199+
return Err(ProtocolError::MisbehavingPeer(format!("rejected invalid transaction {}", transaction_id)));
200+
}
201+
RuleError::RejectSpamTransaction(_) => {
202+
self.spam_counter += 1;
203+
if self.spam_counter % 100 == 0 {
204+
kaspa_core::warn!("Peer {} has shared {} spam txs", self.router, self.spam_counter);
205+
}
206+
}
207+
_ => (),
196208
}
197209
continue;
198210
}

rpc/grpc/server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ kaspa-grpc-core.workspace = true
1616
kaspa-utils.workspace = true
1717
kaspa-core.workspace = true
1818

19+
uuid.workspace = true
1920
faster-hex.workspace = true
2021
async-channel.workspace = true
2122
parking_lot.workspace = true

0 commit comments

Comments
 (0)