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  }