diff --git a/rothschild/src/main.rs b/rothschild/src/main.rs index d303f1da0..35d08493b 100644 --- a/rothschild/src/main.rs +++ b/rothschild/src/main.rs @@ -17,7 +17,10 @@ use kaspa_rpc_core::{api::rpc::RpcApi, notify::mode::NotificationMode, RpcUtxoEn use kaspa_txscript::pay_to_address_script; use parking_lot::Mutex; use rayon::prelude::*; -use secp256k1::{rand::thread_rng, Keypair}; +use secp256k1::{ + rand::{thread_rng, Rng}, + Keypair, +}; use tokio::time::{interval, MissedTickBehavior}; const DEFAULT_SEND_AMOUNT: u64 = 10 * SOMPI_PER_KASPA; @@ -40,6 +43,9 @@ pub struct Args { pub rpc_server: String, pub threads: u8, pub unleashed: bool, + pub addr: Option, + pub priority_fee: u64, + pub randomize_fee: bool, } impl Args { @@ -51,6 +57,9 @@ impl Args { rpc_server: m.get_one::("rpcserver").cloned().unwrap_or("localhost:16210".to_owned()), threads: m.get_one::("threads").cloned().unwrap(), unleashed: m.get_one::("unleashed").cloned().unwrap_or(false), + addr: m.get_one::("addr").cloned(), + priority_fee: m.get_one::("priority-fee").cloned().unwrap_or(0), + randomize_fee: m.get_one::("randomize-fee").cloned().unwrap_or(false), } } } @@ -85,6 +94,25 @@ pub fn cli() -> Command { .help("The number of threads to use for TX generation. Set to 0 to use 1 thread per core. Default is 2."), ) .arg(Arg::new("unleashed").long("unleashed").action(ArgAction::SetTrue).hide(true).help("Allow higher TPS")) + .arg(Arg::new("addr").long("to-addr").short('a').value_name("addr").help("address to send to")) + .arg( + Arg::new("priority-fee") + .long("priority-fee") + .short('f') + .value_name("priority-fee") + .default_value("0") + .value_parser(clap::value_parser!(u64)) + .help("Transaction priority fee"), + ) + .arg( + Arg::new("randomize-fee") + .long("randomize-fee") + .short('r') + .value_name("randomize-fee") + .action(ArgAction::SetTrue) + .default_value("false") + .help("Randomize transaction priority fee"), + ) } async fn new_rpc_client(subscription_context: &SubscriptionContext, address: &str) -> GrpcClient { @@ -111,6 +139,11 @@ struct ClientPoolArg { utxos_len: usize, } +struct TxsFeeConfig { + priority_fee: u64, + randomize_fee: bool, +} + #[tokio::main] async fn main() { kaspa_core::log::init_logger(None, ""); @@ -150,9 +183,31 @@ async fn main() { let kaspa_addr = Address::new(ADDRESS_PREFIX, ADDRESS_VERSION, &schnorr_key.x_only_public_key().0.serialize()); + let kaspa_to_addr = args.addr.as_ref().map_or_else(|| kaspa_addr.clone(), |addr_str| Address::try_from(addr_str.clone()).unwrap()); + + let fee_config = TxsFeeConfig { priority_fee: args.priority_fee, randomize_fee: args.randomize_fee }; + rayon::ThreadPoolBuilder::new().num_threads(args.threads as usize).build_global().unwrap(); - info!("Using Rothschild with private key {} and address {}", schnorr_key.display_secret(), String::from(&kaspa_addr)); + let mut log_message = format!( + "Using Rothschild with:\n\ + \tprivate key: {}\n\ + \tfrom address: {}", + schnorr_key.display_secret(), + String::from(&kaspa_addr) + ); + if args.addr.is_some() { + log_message.push_str(&format!("\n\tto address: {}", String::from(&kaspa_to_addr))); + } + if args.priority_fee != 0 { + log_message.push_str(&format!( + "\n\tpriority fee: {} SOMPS {}", + fee_config.priority_fee, + if fee_config.randomize_fee { "[randomize]" } else { "" } + )); + } + info!("{}", log_message); + let info = rpc_client.get_block_dag_info().await.unwrap(); let coinbase_maturity = match info.network.suffix { Some(11) => TESTNET11_PARAMS.coinbase_maturity, @@ -249,13 +304,14 @@ async fn main() { let has_funds = maybe_send_tx( txs_to_send, &tx_sender, - kaspa_addr.clone(), + kaspa_to_addr.clone(), &mut utxos, &mut pending, schnorr_key, stats.clone(), maximize_inputs, &mut next_available_utxo_index, + &fee_config, ) .await; if !has_funds { @@ -369,6 +425,7 @@ async fn maybe_send_tx( stats: Arc>, maximize_inputs: bool, next_available_utxo_index: &mut usize, + fee_config: &TxsFeeConfig, ) -> bool { let num_outs = if maximize_inputs { 1 } else { 2 }; @@ -377,7 +434,7 @@ async fn maybe_send_tx( let selected_utxos_groups = (0..txs_to_send) .map(|_| { let (selected_utxos, selected_amount) = - select_utxos(utxos, DEFAULT_SEND_AMOUNT, num_outs, maximize_inputs, next_available_utxo_index); + select_utxos(utxos, DEFAULT_SEND_AMOUNT, num_outs, maximize_inputs, next_available_utxo_index, fee_config); if selected_amount == 0 { return None; } @@ -473,10 +530,12 @@ fn select_utxos( num_outs: u64, maximize_utxos: bool, next_available_utxo_index: &mut usize, + fee_config: &TxsFeeConfig, ) -> (Vec<(TransactionOutpoint, UtxoEntry)>, u64) { const MAX_UTXOS: usize = 84; let mut selected_amount: u64 = 0; let mut selected = Vec::new(); + let mut rng = thread_rng(); while next_available_utxo_index < &mut utxos.len() { let (outpoint, entry) = utxos[*next_available_utxo_index].clone(); @@ -484,11 +543,16 @@ fn select_utxos( selected.push((outpoint, entry)); let fee = required_fee(selected.len(), num_outs); + let priority_fee = if fee_config.randomize_fee && fee_config.priority_fee > 0 { + rng.gen_range(0..fee_config.priority_fee) + } else { + fee_config.priority_fee + }; *next_available_utxo_index += 1; - if selected_amount >= min_amount + fee && (!maximize_utxos || selected.len() == MAX_UTXOS) { - return (selected, selected_amount - fee); + if selected_amount >= min_amount + fee + priority_fee && (!maximize_utxos || selected.len() == MAX_UTXOS) { + return (selected, selected_amount - fee - priority_fee); } if selected.len() > MAX_UTXOS {