use std::io::{self, Read, Write};
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::{Block, BlockHeader};
use cmd;
use cmd::tx::create_transaction;
use hal::block::{BlockHeaderInfo, BlockInfo};
pub fn subcommand<'a>() -> clap::App<'a, 'a> {
cmd::subcommand_group("block", "manipulate blocks")
.subcommand(cmd_create())
.subcommand(cmd_decode())
}
pub fn execute<'a>(matches: &clap::ArgMatches<'a>) {
match matches.subcommand() {
("create", Some(ref m)) => exec_create(&m),
("decode", Some(ref m)) => exec_decode(&m),
(_, _) => unreachable!("clap prints help"),
};
}
fn cmd_create<'a>() -> clap::App<'a, 'a> {
cmd::subcommand("create", "create a raw block from JSON")
.args(&[
cmd::arg("block-info", "the block info in JSON").required(false),
cmd::opt("raw-stdout", "output the raw bytes of the result to stdout")
.short("r")
.required(false),
])
.long_about(r#"
Create a block from JSON. Use the same format as the `hal block decode` output.
It's possible to pass the JSON string as the first argument or pass it via stdin.
One can chose to pass in transaction info objects like used for `hal tx create` into the
"transactions" field or hexadecimal raw transactions in the "raw_transactions" field.
Example format:
{
"header": {
"block_hash": "0000000000000000000b52e5f79972ccb1b42f14bd5097381551ed7bb4f78478",
"version": 1,
"previous_block_hash": "00000000000000000011604b6d32a6074720f60aae04edf20396bbaf65e50edc",
"merkle_root": "b7f360ae3bb1f6ca1935269d8955808c6606ff20af9b98fbc3ddb7de6c9df5c3",
"time": 12345,
"bits": 12345,
"nonce": 12345
},
!! only either of these fields should be provided !!
"transactions": [
{ ... }
],
"raw_transaction": [
"deadbeef"
]
}"#
)
}
fn create_block_header(info: BlockHeaderInfo) -> BlockHeader {
if info.block_hash.is_some() {
warn!("Field \"block_hash\" is ignored.");
}
BlockHeader {
version: info.version,
prev_blockhash: info.previous_block_hash,
merkle_root: info.merkle_root,
time: info.time,
bits: info.bits,
nonce: info.nonce,
}
}
fn exec_create<'a>(matches: &clap::ArgMatches<'a>) {
let info: BlockInfo = if let Some(json) = matches.value_of("block-info") {
serde_json::from_str(json).expect("invalid JSON")
} else {
let mut input = Vec::new();
let stdin = io::stdin();
let mut stdin_lock = stdin.lock();
while stdin_lock.read(&mut input).expect("failed to read from stdin") > 0 {}
if input.is_empty() {
panic!("No block-info argument given");
}
serde_json::from_slice(&input).expect("invalid JSON from stdin")
};
if info.txids.is_some() {
warn!("Field \"txids\" is ignored.");
}
let block = Block {
header: create_block_header(info.header),
txdata: match (info.transactions, info.raw_transactions) {
(Some(_), Some(_)) => panic!("Can't provide transactions both in JSON and raw."),
(None, None) => panic!("No transactions provided."),
(Some(infos), None) => infos.into_iter().map(create_transaction).collect(),
(None, Some(raws)) => raws
.into_iter()
.map(|r| deserialize(&r.0).expect("invalid raw transaction"))
.collect(),
},
};
let block_bytes = serialize(&block);
if matches.is_present("raw-stdout") {
::std::io::stdout().write_all(&block_bytes).unwrap();
} else {
print!("{}", hex::encode(&block_bytes));
}
}
fn cmd_decode<'a>() -> clap::App<'a, 'a> {
cmd::subcommand("decode", "decode a raw block to JSON").args(&cmd::opts_networks()).args(&[
cmd::opt_yaml(),
cmd::arg("raw-block", "the raw block in hex").required(true),
cmd::opt("txids", "provide transactions IDs instead of full transactions"),
])
}
fn exec_decode<'a>(matches: &clap::ArgMatches<'a>) {
let hex_tx = matches.value_of("raw-block").expect("no raw block provided");
let raw_tx = hex::decode(hex_tx).expect("could not decode raw block hex");
let block: Block = deserialize(&raw_tx).expect("invalid block format");
if matches.is_present("txids") {
let info = hal::block::BlockInfo {
header: hal::GetInfo::get_info(&block.header, cmd::network(matches)),
txids: Some(block.txdata.iter().map(|t| t.txid()).collect()),
transactions: None,
raw_transactions: None,
};
cmd::print_output(matches, &info)
} else {
let info = hal::GetInfo::get_info(&block, cmd::network(matches));
cmd::print_output(matches, &info)
}
}