Skip to content

Commit 02008f9

Browse files
committed
add decode-tx and decode-block
1 parent f671d4b commit 02008f9

File tree

1 file changed

+113
-2
lines changed

1 file changed

+113
-2
lines changed

src/blockstack_cli.rs

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ extern crate blockstack_lib;
2424
use blockstack_lib::address::AddressHashMode;
2525
use blockstack_lib::burnchains::Address;
2626
use blockstack_lib::chainstate::stacks::{
27-
StacksAddress, StacksPrivateKey, StacksPublicKey, StacksTransaction, StacksTransactionSigner,
27+
StacksAddress, StacksBlock, StacksPrivateKey, StacksPublicKey, StacksTransaction, StacksTransactionSigner,
2828
TokenTransferMemo, TransactionAuth, TransactionContractCall, TransactionPayload,
2929
TransactionSmartContract, TransactionSpendingCondition, TransactionVersion,
3030
C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
3131
};
3232
use blockstack_lib::net::{Error as NetError, StacksMessageCodec};
33-
use blockstack_lib::util::{hash::hex_bytes, hash::to_hex, log, strings::StacksString};
33+
use blockstack_lib::util::{hash::hex_bytes, hash::to_hex, log, strings::StacksString, retry::LogReader};
3434
use blockstack_lib::vm;
3535
use blockstack_lib::vm::{
3636
errors::{Error as ClarityError, RuntimeErrorType},
@@ -116,6 +116,18 @@ The addresses command calculates both the Bitcoin and Stacks addresses from a se
116116
If successful, this command outputs both the Bitcoin and Stacks addresses to stdout, formatted
117117
as JSON, and exits with code 0";
118118

119+
const DECODE_TRANSACTION_USAGE: &str = "blockstack-cli (options) decode-tx [transaction-hex-or-stdin]
120+
121+
The decode-tx command decodes a serialized Stacks transaction and prints it to stdout as JSON.
122+
The transaction, if given, must be a hex string. Alternatively, you may pass - instead, and the
123+
raw binary transaction will be read from stdin";
124+
125+
const DECODE_BLOCK_USAGE: &str = "blockstack-cli (options) decode-block [block-path-or-stdin]
126+
127+
The decode-tx command decodes a serialized Stacks block and prints it to stdout as JSON.
128+
The block, if given, must be a hex string. Alternatively, you may pass - instead, and the
129+
raw binary block will be read from stdin";
130+
119131
#[derive(Debug)]
120132
enum CliError {
121133
ClarityRuntimeError(RuntimeErrorType),
@@ -518,6 +530,81 @@ fn get_addresses(args: &[String], version: TransactionVersion) -> Result<String,
518530
))
519531
}
520532

533+
fn decode_transaction(args: &[String], _version: TransactionVersion) -> Result<String, CliError> {
534+
if (args.len() >= 1 && args[0] == "-h") || args.len() != 1 {
535+
return Err(CliError::Message(format!("Usage: {}\n", DECODE_TRANSACTION_USAGE)));
536+
}
537+
538+
let tx_str =
539+
if args[0] == "-" {
540+
// read from stdin
541+
let mut tx_str = Vec::new();
542+
io::stdin().read_to_end(&mut tx_str).expect("Failed to read transaction from stdin");
543+
tx_str
544+
}
545+
else {
546+
// given as a command-line arg
547+
hex_bytes(&args[0].clone())
548+
.expect("Failed to decode transaction: must be a hex string")
549+
};
550+
551+
let mut cursor = io::Cursor::new(&tx_str);
552+
let mut debug_cursor = LogReader::from_reader(&mut cursor);
553+
554+
match StacksTransaction::consensus_deserialize(&mut debug_cursor) {
555+
Ok(tx) => {
556+
Ok(serde_json::to_string(&tx).expect("Failed to serialize transaction to JSON"))
557+
},
558+
Err(e) => {
559+
let mut ret = String::new();
560+
ret.push_str(&format!("Failed to decode transaction: {:?}\n", &e));
561+
ret.push_str("Bytes consumed:\n");
562+
for buf in debug_cursor.log().iter() {
563+
ret.push_str(&format!(" {}", to_hex(buf)));
564+
}
565+
ret.push_str("\n");
566+
Ok(ret)
567+
}
568+
}
569+
}
570+
571+
fn decode_block(args: &[String], _version: TransactionVersion) -> Result<String, CliError> {
572+
if (args.len() >= 1 && args[0] == "-h") || args.len() != 1 {
573+
return Err(CliError::Message(format!("Usage: {}\n", DECODE_BLOCK_USAGE)));
574+
}
575+
let block_data =
576+
if args[0] == "-" {
577+
// read from stdin
578+
let mut block_str = Vec::new();
579+
io::stdin().read_to_end(&mut block_str).expect("Failed to read block from stdin");
580+
block_str
581+
}
582+
else {
583+
// given as a command-line arg
584+
hex_bytes(&args[0].clone())
585+
.expect("Failed to decode block: must be a hex string")
586+
};
587+
588+
let mut cursor = io::Cursor::new(&block_data);
589+
let mut debug_cursor = LogReader::from_reader(&mut cursor);
590+
591+
match StacksBlock::consensus_deserialize(&mut debug_cursor) {
592+
Ok(block) => {
593+
Ok(serde_json::to_string(&block).expect("Failed to serialize block to JSON"))
594+
},
595+
Err(e) => {
596+
let mut ret = String::new();
597+
ret.push_str(&format!("Failed to decode block: {:?}\n", &e));
598+
ret.push_str("Bytes consumed:\n");
599+
for buf in debug_cursor.log().iter() {
600+
ret.push_str(&format!(" {}", to_hex(buf)));
601+
}
602+
ret.push_str("\n");
603+
Ok(ret)
604+
}
605+
}
606+
}
607+
521608
fn main() {
522609
log::set_loglevel(log::LOG_DEBUG).unwrap();
523610
let mut argv: Vec<String> = env::args().collect();
@@ -556,6 +643,8 @@ fn main_handler(mut argv: Vec<String>) -> Result<String, CliError> {
556643
"token-transfer" => handle_token_transfer(args, tx_version, chain_id),
557644
"generate-sk" => generate_secret_key(args, tx_version),
558645
"addresses" => get_addresses(args, tx_version),
646+
"decode-tx" => decode_transaction(args, tx_version),
647+
"decode-block" => decode_block(args, tx_version),
559648
_ => Err(CliError::Usage),
560649
}
561650
} else {
@@ -830,4 +919,26 @@ mod test {
830919
assert!(result.contains("mzGHS7KN25DEtXipGxjo1tFebb7Fw5aAkp"));
831920
assert!(result.contains("ST36T883PDD2EK4PHVTA5GFHC8NQW6558XJQX6Q3K"));
832921
}
922+
923+
#[test]
924+
fn simple_decode_tx() {
925+
let tx_args = [
926+
"decode-tx",
927+
"8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000100000000000000000100c90ae0235365f3a73c595f8c6ab3c529807feb3cb269247329c9a24218d50d3f34c7eef5d28ba26831affa652a73ec32f098fec4bf1decd1ceb3fde4b8ce216b030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"
928+
];
929+
930+
let result = main_handler(to_string_vec(&tx_args)).unwrap();
931+
eprintln!("result:\n{}", result);
932+
}
933+
934+
#[test]
935+
fn simple_decode_block() {
936+
let block_args = [
937+
"decode-block",
938+
"000000000000395f800000000000000179cb51f6bbd6d90cb257616e77a495919667c3772dd08ea7c4f5c372739490bc91da6609c5c95c96f612dbc8cab2f7a0d8bfb83abdb630167579ccc36b66c03c1d0d250cd3b3615c03afcdaef313dbd30d3d5b0fd10ed5acbc35d042abfba66cdfc32881c5a665ad9685a2eb6e0c131fb400000000000000000000000000000000000000000000000000000000000000000000e87f28593f66d77ae3c57abd4e5ae0e632b837b2596be14c2b2572cd4d0015229976eb5c4a5b08816b31f485513d2e6501f6cd29ee240a2c4056b1f7cc32c2e118ef6499e0fcc575da75fca8cc409e5c884eb3450000000180800000000400403e2ff80a8a8ecacfb827dcf6adddd21fdd4c3c000000000000017800000000000000000000f3f497268f8a12e318f96ba4f1ad3ed2485e87cefe75b88bf735bb1bbb7db754746e6a244ba869183a2ab73002c6465936b7d9b059ffc5a94488bee7b5afb33c010200000000040000000000000000000000000000000000000000000000000000000000000000",
939+
];
940+
941+
let result = main_handler(to_string_vec(&block_args)).unwrap();
942+
eprintln!("result:\n{}", result);
943+
}
833944
}

0 commit comments

Comments
 (0)