solana/dos/src/main.rs

704 lines
23 KiB
Rust
Raw Normal View History

#![allow(clippy::integer_arithmetic)]
use {
2022-03-04 01:54:46 -08:00
clap::{crate_description, crate_name, crate_version, ArgEnum, Args, Parser},
log::*,
rand::{thread_rng, Rng},
2022-02-28 07:15:38 -08:00
serde::{Deserialize, Serialize},
solana_client::rpc_client::RpcClient,
solana_core::serve_repair::RepairProtocol,
solana_gossip::{contact_info::ContactInfo, gossip_service::discover},
2022-02-11 01:36:15 -08:00
solana_sdk::{
2022-02-16 07:05:38 -08:00
hash::Hash,
2022-02-28 07:15:38 -08:00
instruction::{AccountMeta, CompiledInstruction, Instruction},
pubkey::Pubkey,
2022-03-09 07:49:15 -08:00
signature::{read_keypair_file, Keypair, Signature, Signer},
2022-02-16 07:05:38 -08:00
stake,
system_instruction::SystemInstruction,
system_program,
transaction::Transaction,
2022-02-11 01:36:15 -08:00
},
solana_streamer::socket::SocketAddrSpace,
std::{
net::{SocketAddr, UdpSocket},
process::exit,
str::FromStr,
time::{Duration, Instant},
},
};
fn get_repair_contact(nodes: &[ContactInfo]) -> ContactInfo {
let source = thread_rng().gen_range(0, nodes.len());
let mut contact = nodes[source].clone();
contact.id = solana_sdk::pubkey::new_rand();
contact
}
2022-02-16 07:05:38 -08:00
struct TransactionGenerator {
blockhash: Hash,
last_generated: Instant,
transaction_params: TransactionParams,
cached_transaction: Option<Transaction>,
}
impl TransactionGenerator {
fn new(transaction_params: TransactionParams) -> Self {
TransactionGenerator {
blockhash: Hash::default(),
last_generated: (Instant::now() - Duration::from_secs(100)),
transaction_params,
cached_transaction: None,
}
}
2022-03-04 01:54:46 -08:00
fn generate(&mut self, payer: Option<&Keypair>, rpc_client: &Option<RpcClient>) -> Transaction {
if !self.transaction_params.unique_transactions && self.cached_transaction.is_some() {
2022-02-16 07:05:38 -08:00
return self.cached_transaction.as_ref().unwrap().clone();
}
// generate a new blockhash every 1sec
2022-03-04 01:54:46 -08:00
if self.transaction_params.valid_blockhash
2022-02-16 07:05:38 -08:00
&& self.last_generated.elapsed().as_millis() > 1000
{
self.blockhash = rpc_client.as_ref().unwrap().get_latest_blockhash().unwrap();
self.last_generated = Instant::now();
}
2022-03-15 08:22:54 -07:00
// in order to evaluate the performance implications of the different transactions
// we create here transactions which are filetered out on different stages of processing pipeline
// create an arbitrary valid instruction
2022-02-16 07:05:38 -08:00
let lamports = 5;
let transfer_instruction = SystemInstruction::Transfer { lamports };
let program_ids = vec![system_program::id(), stake::program::id()];
2022-03-15 08:22:54 -07:00
// transaction with payer, in this case signatures are valid and num_signatures is irrelevant
2022-03-04 01:54:46 -08:00
// random payer will cause error "attempt to debit an account but found no record of a prior credit"
2022-02-16 07:05:38 -08:00
// if payer is correct, it will trigger error with not enough signatures
2022-03-04 01:54:46 -08:00
let transaction = if let Some(payer) = payer {
2022-02-16 07:05:38 -08:00
let instruction = Instruction::new_with_bincode(
program_ids[0],
&transfer_instruction,
vec![
AccountMeta::new(program_ids[0], false),
AccountMeta::new(program_ids[1], false),
],
);
Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer],
self.blockhash,
)
} else if self.transaction_params.valid_signatures {
2022-03-04 01:54:46 -08:00
// Since we don't provide a payer, this transaction will
2022-03-09 07:49:15 -08:00
// end up filtered at legacy.rs sanitize method (banking_stage) with error "a program cannot be payer"
2022-03-15 08:22:54 -07:00
let kpvals: Vec<Keypair> = (0..self.transaction_params.num_signatures)
2022-02-16 07:05:38 -08:00
.map(|_| Keypair::new())
.collect();
let keypairs: Vec<&Keypair> = kpvals.iter().collect();
let instructions = vec![CompiledInstruction::new(
0,
&transfer_instruction,
vec![0, 1],
)];
Transaction::new_with_compiled_instructions(
&keypairs,
&[],
self.blockhash,
program_ids,
instructions,
2022-02-11 07:19:10 -08:00
)
} else {
2022-03-09 07:49:15 -08:00
// Since we provided invalid signatures
// this transaction will end up filtered at legacy.rs (banking_stage) because
// num_required_signatures == 0
2022-02-16 07:05:38 -08:00
let instructions = vec![CompiledInstruction::new(
0,
&transfer_instruction,
vec![0, 1],
)];
2022-02-11 01:36:15 -08:00
2022-02-16 07:05:38 -08:00
let mut tx = Transaction::new_with_compiled_instructions(
&[] as &[&Keypair; 0],
&[],
self.blockhash,
program_ids,
instructions,
);
2022-03-15 08:22:54 -07:00
tx.signatures = vec![Signature::new_unique(); self.transaction_params.num_signatures];
2022-02-16 07:05:38 -08:00
tx
};
2022-02-08 03:11:58 -08:00
2022-03-04 01:54:46 -08:00
// if we need to generate only one transaction, we cache it to reuse later
2022-02-16 07:05:38 -08:00
if !self.transaction_params.unique_transactions {
self.cached_transaction = Some(transaction.clone());
}
2022-02-08 03:11:58 -08:00
2022-02-16 07:05:38 -08:00
transaction
2022-02-11 01:36:15 -08:00
}
2022-02-08 03:11:58 -08:00
}
fn run_dos(
nodes: &[ContactInfo],
iterations: usize,
2022-03-04 01:54:46 -08:00
payer: Option<&Keypair>,
params: DosClientParameters,
) {
let mut target = None;
2020-09-09 08:21:48 -07:00
let mut rpc_client = None;
if nodes.is_empty() {
2022-03-04 01:54:46 -08:00
if params.mode == Mode::Rpc {
rpc_client = Some(RpcClient::new_socket(params.entrypoint_addr));
2020-09-09 08:21:48 -07:00
}
2022-03-04 01:54:46 -08:00
target = Some(params.entrypoint_addr);
2020-09-09 08:21:48 -07:00
} else {
2022-02-28 07:15:38 -08:00
info!("************ NODE ***********");
2020-09-09 08:21:48 -07:00
for node in nodes {
2022-02-28 07:15:38 -08:00
info!("{:?}", node);
}
2022-03-04 01:54:46 -08:00
info!("ADDR = {}", params.entrypoint_addr);
2022-02-28 07:15:38 -08:00
for node in nodes {
2022-03-04 01:54:46 -08:00
if node.gossip == params.entrypoint_addr {
2022-02-28 07:15:38 -08:00
info!("{}", node.gossip);
2022-03-04 01:54:46 -08:00
target = match params.mode {
Mode::Gossip => Some(node.gossip),
Mode::Tvu => Some(node.tvu),
Mode::TvuForwards => Some(node.tvu_forwards),
Mode::Tpu => {
2022-02-09 00:25:29 -08:00
rpc_client = Some(RpcClient::new_socket(node.rpc));
Some(node.tpu)
2022-02-11 01:36:15 -08:00
}
2022-03-04 01:54:46 -08:00
Mode::TpuForwards => Some(node.tpu_forwards),
Mode::Repair => Some(node.repair),
Mode::ServeRepair => Some(node.serve_repair),
Mode::Rpc => {
2020-09-09 08:21:48 -07:00
rpc_client = Some(RpcClient::new_socket(node.rpc));
None
}
};
break;
}
}
}
let target = target.expect("should have target");
2022-02-16 07:05:38 -08:00
info!("Targeting {}", target);
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let mut data = Vec::new();
2022-03-04 01:54:46 -08:00
let mut transaction_generator = None;
2022-03-04 01:54:46 -08:00
match params.data_type {
DataType::RepairHighest => {
let slot = 100;
let req = RepairProtocol::WindowIndexWithNonce(get_repair_contact(nodes), slot, 0, 0);
data = bincode::serialize(&req).unwrap();
}
2022-03-04 01:54:46 -08:00
DataType::RepairShred => {
let slot = 100;
let req =
RepairProtocol::HighestWindowIndexWithNonce(get_repair_contact(nodes), slot, 0, 0);
data = bincode::serialize(&req).unwrap();
}
2022-03-04 01:54:46 -08:00
DataType::RepairOrphan => {
let slot = 100;
let req = RepairProtocol::OrphanWithNonce(get_repair_contact(nodes), slot, 0);
data = bincode::serialize(&req).unwrap();
}
2022-03-04 01:54:46 -08:00
DataType::Random => {
data.resize(params.data_size, 0);
}
2022-03-04 01:54:46 -08:00
DataType::Transaction => {
let tp = params.transaction_params;
2022-02-28 07:15:38 -08:00
info!("{:?}", tp);
2022-03-04 01:54:46 -08:00
transaction_generator = Some(TransactionGenerator::new(tp));
let tx = transaction_generator
.as_mut()
.unwrap()
.generate(payer, &rpc_client);
2022-02-16 07:05:38 -08:00
info!("{:?}", tx);
data = bincode::serialize(&tx).unwrap();
}
2022-03-04 01:54:46 -08:00
DataType::GetAccountInfo => {}
DataType::GetProgramAccounts => {}
}
let mut last_log = Instant::now();
let mut count = 0;
let mut error_count = 0;
loop {
2022-03-04 01:54:46 -08:00
if params.mode == Mode::Rpc {
match params.data_type {
DataType::GetAccountInfo => {
let res = rpc_client.as_ref().unwrap().get_account(
&Pubkey::from_str(params.data_input.as_ref().unwrap()).unwrap(),
);
2020-09-09 08:21:48 -07:00
if res.is_err() {
error_count += 1;
}
}
2022-03-04 01:54:46 -08:00
DataType::GetProgramAccounts => {
2020-09-09 08:21:48 -07:00
let res = rpc_client.as_ref().unwrap().get_program_accounts(
2022-03-04 01:54:46 -08:00
&Pubkey::from_str(params.data_input.as_ref().unwrap()).unwrap(),
2020-09-09 08:21:48 -07:00
);
if res.is_err() {
error_count += 1;
}
}
2022-03-04 01:54:46 -08:00
_ => {
2020-09-09 08:21:48 -07:00
panic!("unsupported data type");
}
}
} else {
2022-03-04 01:54:46 -08:00
if params.data_type == DataType::Random {
2020-09-09 08:21:48 -07:00
thread_rng().fill(&mut data[..]);
}
2022-03-04 01:54:46 -08:00
if let Some(tg) = transaction_generator.as_mut() {
2022-02-16 07:05:38 -08:00
let tx = tg.generate(payer, &rpc_client);
info!("{:?}", tx);
data = bincode::serialize(&tx).unwrap();
2022-02-11 01:36:15 -08:00
}
2020-09-09 08:21:48 -07:00
let res = socket.send_to(&data, target);
if res.is_err() {
error_count += 1;
}
}
count += 1;
2022-02-01 06:26:55 -08:00
if last_log.elapsed().as_millis() > 10_000 {
info!("count: {} errors: {}", count, error_count);
last_log = Instant::now();
count = 0;
}
if iterations != 0 && count >= iterations {
break;
}
}
}
2022-03-04 01:54:46 -08:00
// command line parsing
#[derive(Parser)]
2022-03-15 08:22:54 -07:00
#[clap(name = crate_name!(),
version = crate_version!(),
about = crate_description!(),
rename_all = "kebab-case"
)]
2022-03-04 01:54:46 -08:00
struct DosClientParameters {
2022-03-15 08:22:54 -07:00
#[clap(long, arg_enum, help = "Interface to DoS")]
mode: Mode,
#[clap(long, arg_enum, help = "Type of data to send")]
data_type: DataType,
2022-03-04 01:54:46 -08:00
#[clap(
long = "entrypoint",
parse(try_from_str = addr_parser),
default_value = "127.0.0.1:8001",
help = "Gossip entrypoint address. Usually <ip>:8001"
)]
entrypoint_addr: SocketAddr,
#[clap(
2022-03-15 08:22:54 -07:00
long,
2022-03-04 01:54:46 -08:00
default_value = "128",
required_if_eq("data-type", "random"),
help = "Size of packet to DoS with, relevant only for data-type=random"
)]
data_size: usize,
2022-03-15 08:22:54 -07:00
#[clap(long, help = "Data to send [Optional]")]
2022-03-04 01:54:46 -08:00
data_input: Option<String>,
2022-03-15 08:22:54 -07:00
#[clap(long, help = "Just use entrypoint address directly")]
2022-03-04 01:54:46 -08:00
skip_gossip: bool,
2022-03-15 08:22:54 -07:00
#[clap(long, help = "Allow contacting private ip addresses")]
2022-03-04 01:54:46 -08:00
allow_private_addr: bool,
#[clap(flatten)]
transaction_params: TransactionParams,
}
#[derive(Args, Serialize, Deserialize, Debug, Default)]
2022-03-15 08:22:54 -07:00
#[clap(rename_all = "kebab-case")]
2022-03-04 01:54:46 -08:00
struct TransactionParams {
#[clap(
2022-03-15 08:22:54 -07:00
long,
2022-03-04 01:54:46 -08:00
default_value = "2",
help = "Number of signatures in transaction"
)]
2022-03-15 08:22:54 -07:00
num_signatures: usize,
2022-03-04 01:54:46 -08:00
2022-03-15 08:22:54 -07:00
#[clap(long, help = "Generate a valid blockhash for transaction")]
2022-03-04 01:54:46 -08:00
valid_blockhash: bool,
2022-03-15 08:22:54 -07:00
#[clap(long, help = "Generate valid signature(s) for transaction")]
2022-03-04 01:54:46 -08:00
valid_signatures: bool,
2022-03-15 08:22:54 -07:00
#[clap(long, help = "Generate unique transactions")]
2022-03-04 01:54:46 -08:00
unique_transactions: bool,
#[clap(
long = "payer",
help = "Payer's keypair file to fund transactions [Optional]"
)]
payer_filename: Option<String>,
}
#[derive(ArgEnum, Clone, Eq, PartialEq)]
enum Mode {
Gossip,
Tvu,
TvuForwards,
Tpu,
TpuForwards,
Repair,
ServeRepair,
Rpc,
}
#[derive(ArgEnum, Clone, Eq, PartialEq)]
enum DataType {
RepairHighest,
RepairShred,
RepairOrphan,
Random,
GetAccountInfo,
GetProgramAccounts,
Transaction,
}
fn addr_parser(addr: &str) -> Result<SocketAddr, &'static str> {
match solana_net_utils::parse_host_port(addr) {
Ok(v) => Ok(v),
Err(_) => Err("failed to parse entrypoint address"),
}
2022-03-04 01:54:46 -08:00
}
/// input checks which are not covered by Clap
fn validate_input(params: &DosClientParameters) {
if params.mode == Mode::Rpc
&& (params.data_type != DataType::GetAccountInfo
&& params.data_type != DataType::GetProgramAccounts)
{
panic!("unsupported data type");
}
if params.data_type != DataType::Transaction {
let tp = &params.transaction_params;
if tp.valid_blockhash
|| tp.valid_signatures
|| tp.unique_transactions
|| tp.payer_filename.is_some()
{
println!("Arguments valid-blockhash, valid-sign, unique-trans, payer are ignored if data-type != transaction");
}
}
2022-03-09 07:49:15 -08:00
if params.transaction_params.payer_filename.is_some()
&& params.transaction_params.valid_signatures
{
println!("Arguments valid-signatures is ignored if payer is provided");
}
2022-03-04 01:54:46 -08:00
}
fn main() {
solana_logger::setup_with_default("solana=info");
let cmd_params = DosClientParameters::parse();
validate_input(&cmd_params);
2020-09-09 08:21:48 -07:00
let mut nodes = vec![];
2022-03-04 01:54:46 -08:00
if !cmd_params.skip_gossip {
info!("Finding cluster entry: {:?}", cmd_params.entrypoint_addr);
let socket_addr_space = SocketAddrSpace::new(cmd_params.allow_private_addr);
2020-09-09 08:21:48 -07:00
let (gossip_nodes, _validators) = discover(
None, // keypair
2022-03-04 01:54:46 -08:00
Some(&cmd_params.entrypoint_addr),
None, // num_nodes
Duration::from_secs(60), // timeout
None, // find_node_by_pubkey
Some(&cmd_params.entrypoint_addr), // find_node_by_gossip_addr
None, // my_gossip_addr
0, // my_shred_version
socket_addr_space,
2020-09-09 08:21:48 -07:00
)
.unwrap_or_else(|err| {
2022-03-04 01:54:46 -08:00
eprintln!(
"Failed to discover {} node: {:?}",
cmd_params.entrypoint_addr, err
);
2020-09-09 08:21:48 -07:00
exit(1);
});
nodes = gossip_nodes;
}
2022-02-16 07:05:38 -08:00
info!("done found {} nodes", nodes.len());
2022-03-04 01:54:46 -08:00
let payer = cmd_params
.transaction_params
.payer_filename
.as_ref()
.map(|keypair_file_name| {
read_keypair_file(&keypair_file_name)
.unwrap_or_else(|_| panic!("bad keypair {:?}", keypair_file_name))
});
run_dos(&nodes, 0, payer.as_ref(), cmd_params);
}
#[cfg(test)]
pub mod test {
use {
super::*,
solana_local_cluster::{cluster::Cluster, local_cluster::LocalCluster},
solana_sdk::timing::timestamp,
};
#[test]
fn test_dos() {
2020-10-19 12:23:14 -07:00
let nodes = [ContactInfo::new_localhost(
&solana_sdk::pubkey::new_rand(),
timestamp(),
)];
let entrypoint_addr = nodes[0].gossip;
2022-02-16 07:05:38 -08:00
run_dos(
&nodes,
1,
2022-02-11 01:36:15 -08:00
None,
2022-03-04 01:54:46 -08:00
DosClientParameters {
entrypoint_addr,
mode: Mode::Tvu,
data_size: 10,
data_type: DataType::Random,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams::default(),
},
);
run_dos(
&nodes,
1,
2022-02-11 01:36:15 -08:00
None,
2022-03-04 01:54:46 -08:00
DosClientParameters {
entrypoint_addr,
mode: Mode::Repair,
data_size: 10,
data_type: DataType::RepairHighest,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams::default(),
},
);
run_dos(
&nodes,
1,
2022-02-11 01:36:15 -08:00
None,
2022-03-04 01:54:46 -08:00
DosClientParameters {
entrypoint_addr,
mode: Mode::ServeRepair,
data_size: 10,
data_type: DataType::RepairShred,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams::default(),
},
);
}
2022-02-01 06:26:55 -08:00
2022-03-09 07:49:15 -08:00
#[test]
fn test_dos_local_cluster_transactions() {
let num_nodes = 1;
let cluster =
LocalCluster::new_with_equal_stakes(num_nodes, 100, 3, SocketAddrSpace::Unspecified);
assert_eq!(cluster.validators.len(), num_nodes);
let nodes = cluster.get_node_pubkeys();
let node = cluster.get_contact_info(&nodes[0]).unwrap().clone();
let nodes_slice = [node];
// send random transactions to TPU
// will be discarded on sigverify stage
run_dos(
&nodes_slice,
1,
None,
DosClientParameters {
entrypoint_addr: cluster.entry_point_info.gossip,
mode: Mode::Tpu,
data_size: 1024,
data_type: DataType::Random,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams::default(),
},
);
2022-03-15 08:22:54 -07:00
// send transactions to TPU with 2 random signatures
2022-03-09 07:49:15 -08:00
// will be filtered on dedup (because transactions are not unique)
run_dos(
&nodes_slice,
1,
None,
DosClientParameters {
entrypoint_addr: cluster.entry_point_info.gossip,
mode: Mode::Tpu,
data_size: 0, // irrelevant if not random
data_type: DataType::Transaction,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams {
2022-03-15 08:22:54 -07:00
num_signatures: 2,
2022-03-09 07:49:15 -08:00
valid_blockhash: false,
valid_signatures: false,
unique_transactions: false,
payer_filename: None,
},
},
);
2022-03-15 08:22:54 -07:00
// send *unique* transactions to TPU with 2 random signatures
2022-03-09 07:49:15 -08:00
// will be discarded on banking stage in legacy.rs
// ("there should be at least 1 RW fee-payer account")
run_dos(
&nodes_slice,
1,
None,
DosClientParameters {
entrypoint_addr: cluster.entry_point_info.gossip,
mode: Mode::Tpu,
data_size: 0, // irrelevant if not random
data_type: DataType::Transaction,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams {
2022-03-15 08:22:54 -07:00
num_signatures: 4,
2022-03-09 07:49:15 -08:00
valid_blockhash: false,
valid_signatures: false,
unique_transactions: true,
payer_filename: None,
},
},
);
2022-03-15 08:22:54 -07:00
// send unique transactions to TPU with 2 random signatures
2022-03-09 07:49:15 -08:00
// will be discarded on banking stage in legacy.rs (A program cannot be a payer)
// because we haven't provided a valid payer
run_dos(
&nodes_slice,
1,
None,
DosClientParameters {
entrypoint_addr: cluster.entry_point_info.gossip,
mode: Mode::Tpu,
data_size: 0, // irrelevant if not random
data_type: DataType::Transaction,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams {
2022-03-15 08:22:54 -07:00
num_signatures: 2,
2022-03-09 07:49:15 -08:00
valid_blockhash: false, // irrelevant without valid payer, because
// it will be filtered before blockhash validity checks
valid_signatures: true,
unique_transactions: true,
payer_filename: None,
},
},
);
// send unique transaction to TPU with valid blockhash
// will be discarded due to invalid hash
run_dos(
&nodes_slice,
1,
Some(&cluster.funding_keypair),
DosClientParameters {
entrypoint_addr: cluster.entry_point_info.gossip,
mode: Mode::Tpu,
data_size: 0, // irrelevant if not random
data_type: DataType::Transaction,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams {
2022-03-15 08:22:54 -07:00
num_signatures: 2,
2022-03-09 07:49:15 -08:00
valid_blockhash: false,
valid_signatures: true,
unique_transactions: true,
payer_filename: None,
},
},
);
// send unique transaction to TPU with valid blockhash
// will fail with error processing Instruction 0: missing required signature for instruction
run_dos(
&nodes_slice,
1,
Some(&cluster.funding_keypair),
DosClientParameters {
entrypoint_addr: cluster.entry_point_info.gossip,
mode: Mode::Tpu,
data_size: 0, // irrelevant if not random
data_type: DataType::Transaction,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams {
2022-03-15 08:22:54 -07:00
num_signatures: 2,
2022-03-09 07:49:15 -08:00
valid_blockhash: true,
valid_signatures: true,
unique_transactions: true,
payer_filename: None,
},
},
);
}
2022-02-01 06:26:55 -08:00
#[test]
#[ignore]
fn test_dos_local_cluster() {
solana_logger::setup();
let num_nodes = 1;
let cluster =
LocalCluster::new_with_equal_stakes(num_nodes, 100, 3, SocketAddrSpace::Unspecified);
assert_eq!(cluster.validators.len(), num_nodes);
let nodes = cluster.get_node_pubkeys();
let node = cluster.get_contact_info(&nodes[0]).unwrap().clone();
run_dos(
&[node],
2022-02-16 07:05:38 -08:00
10_000_000,
2022-03-04 01:54:46 -08:00
Some(&cluster.funding_keypair),
DosClientParameters {
entrypoint_addr: cluster.entry_point_info.gossip,
mode: Mode::Tpu,
data_size: 0, // irrelevant if not random
data_type: DataType::Transaction,
data_input: None,
skip_gossip: false,
allow_private_addr: false,
transaction_params: TransactionParams {
2022-03-15 08:22:54 -07:00
num_signatures: 2,
2022-03-04 01:54:46 -08:00
valid_blockhash: true,
valid_signatures: true,
unique_transactions: true,
payer_filename: None,
},
},
2022-02-01 06:26:55 -08:00
);
}
}