github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/perf/smallbank_workload/src/main.rs (about) 1 /* 2 * Copyright 2017 Intel Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * ------------------------------------------------------------------------------ 16 */ 17 extern crate clap; 18 extern crate crypto; 19 extern crate env_logger; 20 extern crate protobuf; 21 extern crate rand; 22 extern crate sawtooth_perf; 23 extern crate sawtooth_sdk; 24 25 mod playlist; 26 mod smallbank; 27 mod smallbank_tranformer; 28 29 use std::error::Error; 30 use std::fs::File; 31 use std::io::Read; 32 use std::io::Write; 33 use std::str::{FromStr, Split}; 34 35 use batch_gen::generate_signed_batches; 36 use batch_gen::SignedBatchIterator; 37 use batch_submit::run_workload; 38 use batch_submit::submit_signed_batches; 39 use batch_submit::InfiniteBatchListIterator; 40 use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; 41 use playlist::generate_smallbank_playlist; 42 use playlist::process_smallbank_playlist; 43 use rand::Rng; 44 45 use sawtooth_perf::batch_gen; 46 use sawtooth_perf::batch_submit; 47 48 use sawtooth_sdk::signing; 49 use sawtooth_sdk::signing::secp256k1::Secp256k1PrivateKey; 50 51 use playlist::SmallbankGeneratingIter; 52 use smallbank_tranformer::SBPayloadTransformer; 53 54 const APP_NAME: &str = env!("CARGO_PKG_NAME"); 55 const VERSION: &str = env!("CARGO_PKG_VERSION"); 56 57 fn main() { 58 env_logger::init(); 59 60 let arg_matches = App::new(APP_NAME) 61 .version(VERSION) 62 .setting(AppSettings::SubcommandRequiredElseHelp) 63 .subcommand(create_batch_subcommand_args()) 64 .subcommand(create_submit_subcommand_args()) 65 .subcommand(create_playlist_subcommand_args()) 66 .subcommand(create_load_subcommand_args()) 67 .get_matches(); 68 69 let result = match arg_matches.subcommand() { 70 ("batch", Some(args)) => run_batch_command(args), 71 ("submit", Some(args)) => run_submit_command(args), 72 ("playlist", Some(args)) => run_playlist_command(args), 73 ("load", Some(args)) => run_load_command(args), 74 _ => panic!("Should have processed a subcommand or exited before here"), 75 }; 76 77 std::process::exit(match result { 78 Ok(_) => 0, 79 Err(err) => { 80 eprintln!("Error: {}", err); 81 1 82 } 83 }); 84 } 85 86 #[inline] 87 fn arg_error(msg: &str) -> Result<(), Box<Error>> { 88 Err(Box::new(CliError::ArgumentError(String::from(msg)))) 89 } 90 91 fn create_load_subcommand_args<'a, 'b>() -> App<'a, 'b> { 92 SubCommand::with_name("load") 93 .about("Submit smallbank workload at a continuous rate") 94 .arg( 95 Arg::with_name("key") 96 .short("k") 97 .long("key") 98 .value_name("KEY_FILE") 99 .required(true) 100 .help("The signing key for both batches and transactions."), 101 ) 102 .arg( 103 Arg::with_name("max-batch-size") 104 .short("n") 105 .long("max-batch-size") 106 .value_name("NUMBER") 107 .help("The number of transaction in a batch. Defaults to 1."), 108 ) 109 .arg( 110 Arg::with_name("num-accounts") 111 .short("a") 112 .long("accounts") 113 .value_name("ACCOUNTS") 114 .help("The number of Smallbank accounts to make. Defaults to 100."), 115 ) 116 .arg( 117 Arg::with_name("rate") 118 .short("r") 119 .long("rate") 120 .value_name("RATE") 121 .help("The number of batches per second. Defaults to 2."), 122 ) 123 .arg( 124 Arg::with_name("target") 125 .short("t") 126 .long("target") 127 .value_name("TARGET") 128 .help("A comma separated list of Sawtooth REST Api endpoints."), 129 ) 130 .arg( 131 Arg::with_name("seed") 132 .short("s") 133 .long("seed") 134 .value_name("SEED") 135 .help("An integer to use as a seed to make the workload reproduceable."), 136 ) 137 .arg( 138 Arg::with_name("update") 139 .short("u") 140 .long("update-length") 141 .value_name("UPDATE_LENGTH") 142 .help("The time in seconds between updates from this utility."), 143 ) 144 .arg( 145 Arg::with_name("username") 146 .long("--auth-username") 147 .value_name("AUTH_USERNAME") 148 .help("The basic auth username to authenticate with the Sawtooth REST Api."), 149 ) 150 .arg( 151 Arg::with_name("password") 152 .long("--auth-password") 153 .value_name("AUTH_PASSWORD") 154 .help("The basic auth password to authenticate with the Sawtooth REST Api."), 155 ) 156 } 157 158 fn run_load_command(args: &ArgMatches) -> Result<(), Box<Error>> { 159 let max_txns: usize = match args.value_of("max-batch-size").unwrap_or("1").parse() { 160 Ok(n) => n, 161 Err(_) => 0, 162 }; 163 if max_txns == 0 { 164 return arg_error("max-batch-size must be a number greater than 0"); 165 } 166 let accounts: usize = match args.value_of("num-accounts").unwrap_or("100").parse() { 167 Ok(n) => n, 168 Err(_) => 100, 169 }; 170 if accounts == 0 { 171 return arg_error("The number of accounts must be greater than 0."); 172 } 173 174 let target: Vec<String> = match args.value_of("target") 175 .unwrap_or("http://localhost:8008") 176 .parse() 177 { 178 Ok(st) => { 179 let s: String = st; 180 let split: Split<char> = s.split(','); 181 split 182 .map(|s| std::string::String::from_str(s).unwrap()) 183 .collect() 184 } 185 Err(_) => return arg_error("The target is the Sawtooth REST api endpoint with the scheme."), 186 }; 187 let update: u32 = match args.value_of("update").unwrap_or("30").parse() { 188 Ok(n) => n, 189 Err(_) => return arg_error("The update is the time between logging in seconds."), 190 }; 191 192 let rate: usize = match args.value_of("rate").unwrap_or("2").parse() { 193 Ok(r) => r, 194 Err(_) => return arg_error("The rate is the number of batches per second."), 195 }; 196 let username = args.value_of("username"); 197 let password = args.value_of("password"); 198 199 let basic_auth = { 200 match username { 201 Some(username) => match password { 202 None => Some(String::from(username)), 203 Some(password) => Some([username, password].join(":")), 204 }, 205 None => None, 206 } 207 }; 208 let seed: Vec<usize> = match args.value_of("seed") 209 .unwrap_or({ 210 let mut rng = rand::thread_rng(); 211 rng.gen::<usize>().to_string().as_ref() 212 }) 213 .parse() 214 { 215 Ok(s) => vec![s], 216 Err(_) => return arg_error("The seed is a number to seed the random number generator."), 217 }; 218 let mut key_file = File::open(args.value_of("key").unwrap())?; 219 220 let mut buf = String::new(); 221 key_file.read_to_string(&mut buf)?; 222 buf.pop(); // remove the new line 223 224 let private_key = Secp256k1PrivateKey::from_hex(&buf)?; 225 let context = signing::create_context("secp256k1")?; 226 let signer = signing::Signer::new(context.as_ref(), &private_key); 227 228 let mut transformer = SBPayloadTransformer::new(&signer); 229 230 let mut transaction_iterator = SmallbankGeneratingIter::new(accounts, seed.as_slice()) 231 .map(|payload| transformer.payload_to_transaction(&payload)) 232 .map(|item| item.unwrap()); 233 234 let mut batch_iter = SignedBatchIterator::new(&mut transaction_iterator, max_txns, &signer); 235 let mut batchlist_iter = InfiniteBatchListIterator::new(&mut batch_iter); 236 237 let time_to_wait: u32 = 1_000_000_000 / rate as u32; 238 239 match run_workload( 240 &mut batchlist_iter, 241 time_to_wait, 242 update, 243 target, 244 &basic_auth, 245 ) { 246 Ok(_) => Ok(()), 247 Err(err) => { 248 println!("{}", err.description()); 249 Err(Box::new(err)) 250 } 251 } 252 } 253 254 fn create_batch_subcommand_args<'a, 'b>() -> App<'a, 'b> { 255 SubCommand::with_name("batch") 256 .about( 257 "Generates signed batches from transaction input.\n \ 258 The transaction input is expected to be length-delimited protobuf \ 259 Transaction messages, which should also be pre-signed for \ 260 submission to the validator.", 261 ) 262 .arg( 263 Arg::with_name("input") 264 .short("i") 265 .long("input") 266 .value_name("FILE") 267 .required(true) 268 .help("The source of input transactions"), 269 ) 270 .arg( 271 Arg::with_name("output") 272 .short("o") 273 .long("output") 274 .value_name("FILE") 275 .required(true) 276 .help("The target for the signed batches"), 277 ) 278 .arg( 279 Arg::with_name("key") 280 .short("k") 281 .long("key") 282 .value_name("FILE") 283 .required(true) 284 .help("The signing key for the transactions"), 285 ) 286 .arg( 287 Arg::with_name("max-batch-size") 288 .short("n") 289 .long("max-batch-size") 290 .value_name("NUMBER") 291 .help( 292 "The maximum number of transactions to include in a batch; \ 293 Defaults to 100.", 294 ), 295 ) 296 } 297 298 fn run_batch_command(args: &ArgMatches) -> Result<(), Box<Error>> { 299 let max_txns: usize = match args.value_of("max-batch-size").unwrap_or("100").parse() { 300 Ok(n) => n, 301 Err(_) => 0, 302 }; 303 304 if max_txns == 0 { 305 return arg_error("max-batch-size must be a number greater than 0"); 306 } 307 308 let mut in_file = File::open(args.value_of("input").unwrap())?; 309 let mut out_file = File::create(args.value_of("output").unwrap())?; 310 311 let mut key_file = try!(File::open(args.value_of("key").unwrap())); 312 313 let mut buf = String::new(); 314 try!(key_file.read_to_string(&mut buf)); 315 buf.pop(); // remove the new line 316 317 let private_key = try!(Secp256k1PrivateKey::from_hex(&buf)); 318 let context = try!(signing::create_context("secp256k1")); 319 320 if let Err(err) = generate_signed_batches( 321 &mut in_file, 322 &mut out_file, 323 max_txns, 324 context.as_ref(), 325 &private_key, 326 ) { 327 return Err(Box::new(err)); 328 } 329 330 Ok(()) 331 } 332 333 fn create_submit_subcommand_args<'a, 'b>() -> App<'a, 'b> { 334 SubCommand::with_name("submit") 335 .about( 336 "Submits signed batches to one or more targets from batch input.\n \ 337 The batch input is expected to be length-delimited protobuf \ 338 Batch messages, which should also be pre-signed for \ 339 submission to the validator.", 340 ) 341 .arg( 342 Arg::with_name("input") 343 .short("i") 344 .long("input") 345 .value_name("FILE") 346 .help("The source of batch transactions"), 347 ) 348 .arg( 349 Arg::with_name("target") 350 .short("t") 351 .long("target") 352 .value_name("TARGET") 353 .help("A Sawtooth REST API endpoint"), 354 ) 355 .arg( 356 Arg::with_name("rate") 357 .short("r") 358 .long("rate") 359 .value_name("RATE") 360 .help("The number of batches per second to submit to the target"), 361 ) 362 } 363 364 fn run_submit_command(args: &ArgMatches) -> Result<(), Box<Error>> { 365 let rate: usize = match args.value_of("rate").unwrap_or("1").parse() { 366 Ok(n) => n, 367 Err(_) => 0, 368 }; 369 370 if rate == 0 { 371 return arg_error("rate must be a number greater than 0"); 372 } 373 374 let target: String = match args.value_of("target") 375 .unwrap_or("http://localhost:8008") 376 .parse() 377 { 378 Ok(s) => s, 379 Err(_) => String::new(), 380 }; 381 382 if target == "" { 383 return arg_error("target must be a valid http uri"); 384 } 385 386 let input: String = match args.value_of("input").unwrap_or("").parse() { 387 Ok(s) => s, 388 Err(_) => String::new(), 389 }; 390 391 if input == "" { 392 return arg_error("an input file must be specified"); 393 } 394 395 let mut in_file = File::open(args.value_of("input").unwrap())?; 396 397 println!("Input: {} Target: {} Rate: {}", input, target, rate); 398 399 if let Err(err) = submit_signed_batches(&mut in_file, target, rate) { 400 return Err(Box::new(err)); 401 } 402 403 Ok(()) 404 } 405 406 fn create_playlist_subcommand_args<'a, 'b>() -> App<'a, 'b> { 407 SubCommand::with_name("playlist") 408 .subcommand(create_playlist_create_subcommand_args()) 409 .subcommand(create_playlist_process_subcommand_args()) 410 } 411 412 fn create_playlist_create_subcommand_args<'a, 'b>() -> App<'a, 'b> { 413 SubCommand::with_name("create") 414 .about( 415 "Generates a smallbank transaction playlist.\n \ 416 A playlist is a series of transactions, described in \ 417 YAML. This command generates a playlist and writes it \ 418 to file or statndard out.", 419 ) 420 .arg( 421 Arg::with_name("output") 422 .short("o") 423 .long("output") 424 .value_name("FILE") 425 .help("The target for the generated playlist"), 426 ) 427 .arg( 428 Arg::with_name("random_seed") 429 .short("S") 430 .long("seed") 431 .value_name("NUMBER") 432 .help("A random seed, which will generate the same output"), 433 ) 434 .arg( 435 Arg::with_name("accounts") 436 .short("a") 437 .long("accounts") 438 .value_name("NUMBER") 439 .required(true) 440 .help("The number of unique accounts to generate"), 441 ) 442 .arg( 443 Arg::with_name("transactions") 444 .short("n") 445 .long("transactions") 446 .value_name("NUMBER") 447 .required(true) 448 .help( 449 "The number of transactions generate, in \ 450 addition to the created accounts", 451 ), 452 ) 453 } 454 455 fn create_playlist_process_subcommand_args<'a, 'b>() -> App<'a, 'b> { 456 SubCommand::with_name("process") 457 .about( 458 "Processes a smallbank transaction playlist.\n \ 459 A playlist is a series of transactions, described in \ 460 YAML. This command processes a playlist, converting it into \ 461 transactions and writes it to file or statndard out.", 462 ) 463 .arg( 464 Arg::with_name("input") 465 .short("i") 466 .long("input") 467 .value_name("FILE") 468 .required(true) 469 .help("The source of the input playlist yaml"), 470 ) 471 .arg( 472 Arg::with_name("key") 473 .short("k") 474 .long("key") 475 .value_name("FILE") 476 .required(true) 477 .help("The signing key for the transactions"), 478 ) 479 .arg( 480 Arg::with_name("output") 481 .short("o") 482 .long("output") 483 .value_name("FILE") 484 .help("The target for the generated transactions"), 485 ) 486 } 487 488 fn run_playlist_command(args: &ArgMatches) -> Result<(), Box<Error>> { 489 match args.subcommand() { 490 ("create", Some(args)) => run_playlist_create_command(args), 491 ("process", Some(args)) => run_playlist_process_command(args), 492 _ => panic!("Should have processed a subcommand or exited before here"), 493 } 494 } 495 496 fn run_playlist_create_command(args: &ArgMatches) -> Result<(), Box<Error>> { 497 let num_accounts = match args.value_of("accounts").unwrap().parse() { 498 Ok(n) => n, 499 Err(_) => 0, 500 }; 501 502 if num_accounts < 2 { 503 return arg_error("'accounts' must be a number greater than 2"); 504 } 505 506 let num_transactions = match args.value_of("transactions").unwrap().parse() { 507 Ok(n) => n, 508 Err(_) => 0, 509 }; 510 511 if num_transactions == 0 { 512 return arg_error("'transactions' must be a number greater than 0"); 513 } 514 515 let random_seed = match args.value_of("random_seed") { 516 Some(seed) => match seed.parse::<i32>() { 517 Ok(n) => Some(n), 518 Err(_) => return arg_error("'seed' must be a valid number"), 519 }, 520 None => None, 521 }; 522 523 let mut output_writer: Box<Write> = match args.value_of("output") { 524 Some(file_name) => try!(File::create(file_name).map(Box::new)), 525 None => Box::new(std::io::stdout()), 526 }; 527 528 try!(generate_smallbank_playlist( 529 &mut *output_writer, 530 num_accounts, 531 num_transactions, 532 random_seed 533 )); 534 535 Ok(()) 536 } 537 538 fn run_playlist_process_command(args: &ArgMatches) -> Result<(), Box<Error>> { 539 let mut in_file = try!(File::open(args.value_of("input").unwrap())); 540 541 let mut output_writer: Box<Write> = match args.value_of("output") { 542 Some(file_name) => try!(File::create(file_name).map(Box::new)), 543 None => Box::new(std::io::stdout()), 544 }; 545 546 let mut key_file = try!(File::open(args.value_of("key").unwrap())); 547 548 let mut buf = String::new(); 549 try!(key_file.read_to_string(&mut buf)); 550 buf.pop(); // remove the new line 551 552 let context = try!(signing::create_context("secp256k1")); 553 let private_key = try!(Secp256k1PrivateKey::from_hex(&buf)); 554 555 try!(process_smallbank_playlist( 556 &mut output_writer, 557 &mut in_file, 558 context.as_ref(), 559 &private_key 560 )); 561 562 Ok(()) 563 } 564 565 #[derive(Debug)] 566 enum CliError { 567 ArgumentError(String), 568 } 569 570 impl std::fmt::Display for CliError { 571 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 572 match *self { 573 CliError::ArgumentError(ref msg) => write!(f, "ArgumentError: {}", msg), 574 } 575 } 576 } 577 578 impl std::error::Error for CliError { 579 fn description(&self) -> &str { 580 match *self { 581 CliError::ArgumentError(ref msg) => msg, 582 } 583 } 584 585 fn cause(&self) -> Option<&Error> { 586 match *self { 587 CliError::ArgumentError(_) => None, 588 } 589 } 590 }