github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/adm/src/commands/blockstore.rs (about) 1 /* 2 * Copyright 2018 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 18 use std::collections::HashMap; 19 use std::fs::File; 20 use std::io::{self, Read, Write}; 21 22 use clap::ArgMatches; 23 use protobuf; 24 use protobuf::Message; 25 use serde_yaml; 26 27 use sawtooth_sdk::messages::block::{Block, BlockHeader}; 28 use sawtooth_sdk::messages::transaction::TransactionHeader; 29 30 use blockstore::Blockstore; 31 use config; 32 use database::error::DatabaseError; 33 use database::lmdb; 34 use err::CliError; 35 use wrappers::Block as BlockWrapper; 36 37 const NULL_BLOCK_IDENTIFIER: &str = "0000000000000000"; 38 39 pub fn run<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 40 match args.subcommand() { 41 ("backup", Some(args)) => run_backup_command(args), 42 ("restore", Some(args)) => run_restore_command(args), 43 ("list", Some(args)) => run_list_command(args), 44 ("show", Some(args)) => run_show_command(args), 45 ("prune", Some(args)) => run_prune_command(args), 46 ("export", Some(args)) => run_export_command(args), 47 ("import", Some(args)) => run_import_command(args), 48 ("stats", Some(args)) => run_stats_command(args), 49 _ => { 50 println!("Invalid subcommand; Pass --help for usage."); 51 Ok(()) 52 } 53 } 54 } 55 56 fn run_backup_command<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 57 let ctx = create_context()?; 58 let blockstore = open_blockstore(&ctx)?; 59 60 let filepath = args.value_of("output") 61 .ok_or_else(|| CliError::ArgumentError("No output file".into()))?; 62 let mut file = File::create(filepath) 63 .map_err(|err| CliError::EnvironmentError(format!("Failed to create file: {}", err)))?; 64 65 let mut current = match args.value_of("start") { 66 None => blockstore 67 .get_chain_head() 68 .map_err(|err| CliError::EnvironmentError(format!("{}", err))), 69 Some(sig) => Ok(sig.into()), 70 }?; 71 72 while current != NULL_BLOCK_IDENTIFIER { 73 let block = blockstore.get(¤t).map_err(|err| { 74 CliError::EnvironmentError(format!("Block in chain missing from blockstore: {}", err)) 75 })?; 76 backup_block(&block, &mut file)?; 77 let block_header: BlockHeader = protobuf::parse_from_bytes(&block.header) 78 .map_err(|err| CliError::ParseError(format!("{}", err)))?; 79 current = block_header.previous_block_id 80 } 81 Ok(()) 82 } 83 84 fn run_restore_command<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 85 let ctx = create_context()?; 86 let blockstore = open_blockstore(&ctx)?; 87 88 let filepath = args.value_of("input") 89 .ok_or_else(|| CliError::ArgumentError("No input file".into()))?; 90 let mut file = File::open(filepath) 91 .map_err(|err| CliError::EnvironmentError(format!("Failed to open file: {}", err)))?; 92 93 let mut source = protobuf::CodedInputStream::new(&mut file); 94 95 while let Some(block) = restore_block(&mut source)? { 96 blockstore 97 .put(&block) 98 .map_err(|err| CliError::EnvironmentError(format!("Failed to put block: {}", err)))?; 99 } 100 Ok(()) 101 } 102 103 fn run_list_command<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 104 let ctx = create_context()?; 105 let blockstore = open_blockstore(&ctx)?; 106 107 let mut count = u64::from_str_radix(args.value_of("count").unwrap_or("100"), 10).unwrap(); 108 109 // Get the chain head 110 let head_sig = match args.value_of("start") { 111 None => blockstore 112 .get_chain_head() 113 .map_err(|err| CliError::EnvironmentError(format!("{}", err))), 114 Some(sig) => Ok(sig.into()), 115 }?; 116 117 // Walk back from the chain head 118 let mut block_id = head_sig; 119 print_block_store_list_header(); 120 121 while block_id != NULL_BLOCK_IDENTIFIER && count > 0 { 122 let block = blockstore 123 .get(&block_id) 124 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 125 let block_header: BlockHeader = protobuf::parse_from_bytes(&block.header) 126 .map_err(|err| CliError::ParseError(format!("{}", err)))?; 127 let batches = block.batches.len(); 128 let txns = block 129 .batches 130 .iter() 131 .fold(0, |acc, batch| acc + batch.transactions.len()); 132 print_block_store_list_row( 133 block_header.block_num, 134 &block.header_signature, 135 batches, 136 txns, 137 &block_header.signer_public_key, 138 ); 139 block_id = block_header.previous_block_id; 140 count -= 1; 141 } 142 Ok(()) 143 } 144 145 fn print_block_store_list_header() { 146 println!( 147 "{:<5} {:<128} {:<5} {:<5} {}", 148 "NUM", "BLOCK_ID", "BATS", "TXNS", "SIGNER" 149 ); 150 } 151 152 fn print_block_store_list_row( 153 block_num: u64, 154 block_id: &str, 155 batches: usize, 156 txns: usize, 157 signer: &str, 158 ) { 159 println!( 160 "{:<5} {:<128} {:<5} {:<5} {}...", 161 block_num, 162 block_id, 163 batches, 164 txns, 165 &signer[..6] 166 ); 167 } 168 169 fn run_show_command<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 170 let ctx = create_context()?; 171 let blockstore = open_blockstore(&ctx)?; 172 173 let block = { 174 if args.is_present("block") { 175 let block = args.value_of("block") 176 .ok_or_else(|| CliError::ArgumentError("No block".into()))?; 177 blockstore.get(block) 178 } else if args.is_present("batch") { 179 let batch = args.value_of("batch") 180 .ok_or_else(|| CliError::ArgumentError("No batch".into()))?; 181 blockstore.get_by_batch(batch) 182 } else if args.is_present("transaction") { 183 let transaction = args.value_of("transaction") 184 .ok_or_else(|| CliError::ArgumentError("No transaction".into()))?; 185 blockstore.get_by_transaction(transaction) 186 } else if args.is_present("blocknum") { 187 let blocknum = args.value_of("blocknum") 188 .ok_or_else(|| CliError::ArgumentError("No block num".into()))?; 189 let height: u64 = blocknum 190 .parse() 191 .map_err(|err| CliError::ArgumentError(format!("Invalid block num: {}", err)))?; 192 blockstore.get_by_height(height) 193 } else { 194 return Err(CliError::ArgumentError("No identifier specified".into())); 195 } 196 }.map_err(|err| CliError::ArgumentError(format!("Error getting block: {}", err)))?; 197 198 let block_wrapper = BlockWrapper::try_from(block) 199 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 200 201 let block_yaml = serde_yaml::to_string(&block_wrapper) 202 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 203 204 println!("{}", block_yaml); 205 Ok(()) 206 } 207 208 fn run_prune_command<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 209 let ctx = create_context()?; 210 let blockstore = open_blockstore(&ctx)?; 211 212 let block_id = args.value_of("block") 213 .ok_or_else(|| CliError::ArgumentError("No block id".into()))?; 214 215 blockstore 216 .get(block_id) 217 .map_err(|_| CliError::ArgumentError(format!("Block not found: {}", block_id)))?; 218 219 // Get the chain head 220 let chain_head = blockstore 221 .get_chain_head() 222 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 223 224 let mut current = blockstore 225 .get(&chain_head) 226 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 227 228 loop { 229 blockstore 230 .delete(¤t.header_signature) 231 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 232 if current.header_signature == block_id { 233 break; 234 } 235 let header: BlockHeader = protobuf::parse_from_bytes(¤t.header) 236 .map_err(|err| CliError::ParseError(format!("{}", err)))?; 237 238 current = blockstore 239 .get(&header.previous_block_id) 240 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 241 } 242 Ok(()) 243 } 244 245 fn run_export_command<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 246 let ctx = create_context()?; 247 let blockstore = open_blockstore(&ctx)?; 248 249 let block_id = args.value_of("block") 250 .ok_or_else(|| CliError::ArgumentError("No block id".into()))?; 251 252 let block = blockstore 253 .get(block_id) 254 .map_err(|_| CliError::ArgumentError(format!("Block not found: {}", block_id)))?; 255 256 match args.value_of("output") { 257 Some(filepath) => { 258 let mut file = File::create(filepath).map_err(|err| { 259 CliError::EnvironmentError(format!("Failed to create file: {}", err)) 260 })?; 261 block 262 .write_to_writer(&mut file) 263 .map_err(|err| CliError::EnvironmentError(format!("{}", err))) 264 } 265 None => { 266 let stdout = io::stdout(); 267 let mut handle = stdout.lock(); 268 block 269 .write_to_writer(&mut handle) 270 .map_err(|err| CliError::EnvironmentError(format!("{}", err))) 271 } 272 } 273 } 274 275 fn run_import_command<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 276 let ctx = create_context()?; 277 let blockstore = open_blockstore(&ctx)?; 278 279 let filepath = args.value_of("blockfile") 280 .ok_or_else(|| CliError::ArgumentError("No file".into()))?; 281 let mut file = File::open(filepath) 282 .map_err(|err| CliError::EnvironmentError(format!("Failed to open file: {}", err)))?; 283 let mut packed = Vec::new(); 284 file.read_to_end(&mut packed) 285 .map_err(|err| CliError::EnvironmentError(format!("Failed to read file: {}", err)))?; 286 287 let block: Block = protobuf::parse_from_bytes(&packed) 288 .map_err(|err| CliError::ParseError(format!("{}", err)))?; 289 let block_header: BlockHeader = protobuf::parse_from_bytes(&block.header) 290 .map_err(|err| CliError::ParseError(format!("{}", err)))?; 291 let block_id = block.header_signature.clone(); 292 293 // Ensure this block is an immediate child of the current chain head 294 match blockstore.get_chain_head() { 295 Ok(chain_head) => { 296 if block_header.previous_block_id != chain_head { 297 return Err(CliError::ArgumentError(format!( 298 "New block must be an immediate child of the current chain head: {}", 299 chain_head 300 ))); 301 } 302 } 303 Err(DatabaseError::NotFoundError(_)) => (), 304 Err(err) => { 305 return Err(CliError::EnvironmentError(format!("{}", err))); 306 } 307 } 308 309 blockstore.put(&block).map_err(|err| { 310 CliError::ArgumentError(format!("Failed to put block into database: {}", err)) 311 })?; 312 313 println!("Block {} added", block_id); 314 Ok(()) 315 } 316 317 fn run_stats_command<'a>(args: &ArgMatches<'a>) -> Result<(), CliError> { 318 let ctx = create_context()?; 319 let blockstore = open_blockstore(&ctx)?; 320 321 let block_count = blockstore 322 .get_current_height() 323 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 324 let batch_count = blockstore 325 .get_batch_count() 326 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 327 let txn_count = blockstore 328 .get_transaction_count() 329 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 330 331 if args.is_present("extended") { 332 let mut txn_family_counts = HashMap::new(); 333 let chain_head = blockstore 334 .get_chain_head() 335 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 336 let mut block = blockstore 337 .get(&chain_head) 338 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 339 340 loop { 341 for batch in &block.batches { 342 for txn in &batch.transactions { 343 let txn_header: TransactionHeader = protobuf::parse_from_bytes(&txn.header) 344 .map_err(|err| CliError::ParseError(format!("{}", err)))?; 345 let count = txn_family_counts.entry(txn_header.family_name).or_insert(0); 346 *count += 1; 347 } 348 } 349 let header: BlockHeader = protobuf::parse_from_bytes(&block.header) 350 .map_err(|err| CliError::ParseError(format!("{}", err)))?; 351 if header.previous_block_id == NULL_BLOCK_IDENTIFIER { 352 break; 353 } 354 block = blockstore 355 .get(&header.previous_block_id) 356 .map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 357 } 358 359 println!("Blocks: {}", block_count); 360 println!("Batches: {}", batch_count); 361 println!("Transactions: {}", txn_count); 362 for (family, count) in &txn_family_counts { 363 println!(" {}: {}", family, count); 364 } 365 } else { 366 println!("Blocks: {}", block_count); 367 println!("Batches: {}", batch_count); 368 println!("Transactions: {}", txn_count); 369 } 370 371 Ok(()) 372 } 373 374 fn create_context() -> Result<lmdb::LmdbContext, CliError> { 375 let path_config = config::get_path_config(); 376 let blockstore_path = &path_config.data_dir.join(config::get_blockstore_filename()); 377 378 lmdb::LmdbContext::new(blockstore_path, 3, None) 379 .map_err(|err| CliError::EnvironmentError(format!("{}", err))) 380 } 381 382 fn open_blockstore(ctx: &lmdb::LmdbContext) -> Result<Blockstore, CliError> { 383 let blockstore_db = lmdb::LmdbDatabase::new( 384 ctx, 385 &["index_batch", "index_transaction", "index_block_num"], 386 ).map_err(|err| CliError::EnvironmentError(format!("{}", err)))?; 387 388 Ok(Blockstore::new(blockstore_db)) 389 } 390 391 fn backup_block<W: Write>(block: &Block, writer: &mut W) -> Result<(), CliError> { 392 block 393 .write_length_delimited_to_writer(writer) 394 .map_err(|err| CliError::EnvironmentError(format!("{}", err))) 395 } 396 397 fn restore_block(source: &mut protobuf::CodedInputStream) -> Result<Option<Block>, CliError> { 398 let eof = source 399 .eof() 400 .map_err(|err| CliError::EnvironmentError(format!("Failed to check EOF: {}", err)))?; 401 if eof { 402 return Ok(None); 403 } 404 405 let block = protobuf::parse_length_delimited_from(source) 406 .map_err(|err| CliError::EnvironmentError(format!("Failed to parse block: {}", err)))?; 407 408 Ok(Some(block)) 409 }