From 1b81c11b5ae4d9c272023e76e54b7574de42c6d3 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 19 Jan 2023 11:37:07 -0500 Subject: [PATCH] cli: add --dump-tx option to dump transactions This option dumps transactions before and after signing, and dumps the wrapper transaction as well. (We currently wrap and sign the wrapper simultaneously.) --- apps/src/lib/cli.rs | 9 +++++++ apps/src/lib/client/signing.rs | 49 ++++++++++++++++++++++++++++++++++ apps/src/lib/client/types.rs | 2 ++ 3 files changed, 60 insertions(+) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 08d6dfe7222..0a87e093cab 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1568,6 +1568,7 @@ pub mod args { const DECRYPT: ArgFlag = flag("decrypt"); const DONT_ARCHIVE: ArgFlag = flag("dont-archive"); const DRY_RUN_TX: ArgFlag = flag("dry-run"); + const DUMP_TX: ArgFlag = flag("dump-tx"); const EPOCH: ArgOpt = arg_opt("epoch"); const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); @@ -2734,6 +2735,8 @@ pub mod args { pub struct Tx { /// Simulate applying the transaction pub dry_run: bool, + /// Dump the transaction bytes + pub dump_tx: bool, /// Submit the transaction even if it doesn't pass client checks pub force: bool, /// Do not wait for the transaction to be added to the blockchain @@ -2759,6 +2762,7 @@ pub mod args { pub fn parse_from_context(&self, ctx: &mut Context) -> ParsedTxArgs { ParsedTxArgs { dry_run: self.dry_run, + dump_tx: self.dump_tx, force: self.force, broadcast_only: self.broadcast_only, ledger_address: self.ledger_address.clone(), @@ -2784,6 +2788,9 @@ pub mod args { .def() .about("Simulate the transaction application."), ) + .arg(DUMP_TX.def().about( + "Dump transaction bytes to a file." + )) .arg(FORCE.def().about( "Submit the transaction even if it doesn't pass client checks.", )) @@ -2830,6 +2837,7 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let dry_run = DRY_RUN_TX.parse(matches); + let dump_tx = DUMP_TX.parse(matches); let force = FORCE.parse(matches); let broadcast_only = BROADCAST_ONLY.parse(matches); let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); @@ -2842,6 +2850,7 @@ pub mod args { let signer = SIGNER.parse(matches); Self { dry_run, + dump_tx, force, broadcast_only, ledger_address, diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 0f893a0190c..fb62755e751 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -5,6 +5,7 @@ use borsh::BorshSerialize; use namada::ledger::parameters::storage as parameter_storage; use namada::proto::Tx; use namada::types::address::{Address, ImplicitAddress}; +use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::token; @@ -151,8 +152,15 @@ pub async fn sign_tx( default: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> (Context, TxBroadcastData) { + if args.dump_tx { + dump_tx_helper(&ctx, &tx, "unsigned", None); + } + let keypair = tx_signer(&mut ctx, args, default).await; let tx = tx.sign(&keypair); + if args.dump_tx { + dump_tx_helper(&ctx, &tx, "signed", None); + } let epoch = rpc::query_epoch(args::Query { ledger_address: args.ledger_address.clone(), @@ -172,9 +180,50 @@ pub async fn sign_tx( ) .await }; + + if args.dump_tx && !args.dry_run { + let (wrapper_tx, wrapper_hash) = match broadcast_data { + TxBroadcastData::DryRun(_) => panic!( + "somehow created a dry run transaction without --dry-run" + ), + TxBroadcastData::Wrapper { + ref tx, + ref wrapper_hash, + decrypted_hash: _, + } => (tx, wrapper_hash), + }; + + dump_tx_helper(&ctx, wrapper_tx, "wrapper", Some(wrapper_hash)); + } + (ctx, broadcast_data) } +pub fn dump_tx_helper( + ctx: &Context, + tx: &Tx, + extension: &str, + precomputed_hash: Option<&String>, +) -> () { + let chain_dir = ctx.config.ledger.chain_dir(); + let hash = match precomputed_hash { + Some(hash) => hash.to_owned(), + None => { + let hash: Hash = tx + .hash() + .as_ref() + .try_into() + .expect("expected hash of dumped tx to be a hash"); + format!("{}", hash) + } + }; + let filename = chain_dir.join(hash).with_extension(extension); + let tx_bytes = tx.to_bytes(); + + std::fs::write(filename, tx_bytes) + .expect("expected to be able to write tx dump file"); +} + /// Create a wrapper tx from a normal tx. Get the hash of the /// wrapper and its payload which is needed for monitoring its /// progress on chain. diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs index 1ed41e9c809..a966e7063b2 100644 --- a/apps/src/lib/client/types.rs +++ b/apps/src/lib/client/types.rs @@ -18,6 +18,8 @@ use crate::facade::tendermint_config::net::Address as TendermintAddress; pub struct ParsedTxArgs { /// Simulate applying the transaction pub dry_run: bool, + /// Dump the transaction bytes + pub dump_tx: bool, /// Submit the transaction even if it doesn't pass client checks pub force: bool, /// Do not wait for the transaction to be added to the blockchain