Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Commit

Permalink
CLI: Support dumping the TX message in sign-only mode
Browse files Browse the repository at this point in the history
  • Loading branch information
t-nelson committed Mar 16, 2021
1 parent 1c261d2 commit d82ac80
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions clap-utils/src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use std::{

pub struct SignOnly {
pub blockhash: Hash,
pub message: Option<String>,
pub present_signers: Vec<(Pubkey, Signature)>,
pub absent_signers: Vec<Pubkey>,
pub bad_signers: Vec<Pubkey>,
Expand Down
18 changes: 18 additions & 0 deletions clap-utils/src/offline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant {
help: "Provide a public-key/signature pair for the transaction",
};

pub const DUMP_TRANSACTION_MESSAGE: ArgConstant<'static> = ArgConstant {
name: "dump_transaction_message",
long: "dump-transaction-message",
help: "Display the base64 encoded binary transaction message in sign-only mode",
};

pub fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(BLOCKHASH_ARG.name)
.long(BLOCKHASH_ARG.long)
Expand Down Expand Up @@ -47,6 +53,14 @@ fn signer_arg<'a, 'b>() -> Arg<'a, 'b> {
.help(SIGNER_ARG.help)
}

pub fn dump_transaction_message<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(DUMP_TRANSACTION_MESSAGE.name)
.long(DUMP_TRANSACTION_MESSAGE.long)
.takes_value(false)
.requires(SIGN_ONLY_ARG.name)
.help(DUMP_TRANSACTION_MESSAGE.help)
}

pub trait ArgsConfig {
fn blockhash_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
arg
Expand All @@ -57,6 +71,9 @@ pub trait ArgsConfig {
fn signer_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
arg
}
fn dump_transaction_message_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
arg
}
}

pub trait OfflineArgs {
Expand All @@ -69,6 +86,7 @@ impl OfflineArgs for App<'_, '_> {
self.arg(config.blockhash_arg(blockhash_arg()))
.arg(config.sign_only_arg(sign_only_arg()))
.arg(config.signer_arg(signer_arg()))
.arg(config.dump_transaction_message_arg(dump_transaction_message()))
}
fn offline_args(self) -> Self {
struct NullArgsConfig {}
Expand Down
1 change: 1 addition & 0 deletions cli-output/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-cli-output"

[dependencies]
base64 = "0.13.0"
chrono = { version = "0.4.11", features = ["serde"] }
console = "0.11.3"
humantime = "2.0.1"
Expand Down
49 changes: 48 additions & 1 deletion cli-output/src/cli_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,8 @@ impl fmt::Display for CliInflation {
#[serde(rename_all = "camelCase")]
pub struct CliSignOnlyData {
pub blockhash: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub signers: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
Expand All @@ -1334,6 +1336,9 @@ impl fmt::Display for CliSignOnlyData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
writeln_name_value(f, "Blockhash:", &self.blockhash)?;
if let Some(message) = self.message.as_ref() {
writeln_name_value(f, "Transaction Message:", message)?;
}
if !self.signers.is_empty() {
writeln!(f, "{}", style("Signers (Pubkey=Signature):").bold())?;
for signer in self.signers.iter() {
Expand Down Expand Up @@ -1696,9 +1701,22 @@ impl fmt::Display for CliUpgradeableBuffer {
}
}

#[derive(Debug, Default)]
pub struct ReturnSignersConfig {
pub dump_transaction_message: bool,
}

pub fn return_signers(
tx: &Transaction,
output_format: &OutputFormat,
) -> Result<String, Box<dyn std::error::Error>> {
return_signers_with_config(tx, output_format, &ReturnSignersConfig::default())
}

pub fn return_signers_with_config(
tx: &Transaction,
output_format: &OutputFormat,
config: &ReturnSignersConfig,
) -> Result<String, Box<dyn std::error::Error>> {
let verify_results = tx.verify_with_results();
let mut signers = Vec::new();
Expand All @@ -1717,9 +1735,16 @@ pub fn return_signers(
bad_sig.push(key.to_string());
}
});
let message = if config.dump_transaction_message {
let message_data = tx.message_data();
Some(base64::encode(&message_data))
} else {
None
};

let cli_command = CliSignOnlyData {
blockhash: tx.message.recent_blockhash.to_string(),
message,
signers,
absent,
bad_sig,
Expand Down Expand Up @@ -1774,8 +1799,14 @@ pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
.collect();
}

let message = object
.get("message")
.and_then(|o| o.as_str())
.map(|m| m.to_string());

SignOnly {
blockhash,
message,
present_signers,
absent_signers,
bad_signers,
Expand Down Expand Up @@ -2051,9 +2082,25 @@ mod tests {
let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
let blockhash = Hash::new(&[7u8; 32]);
tx.try_partial_sign(&signers, blockhash).unwrap();
let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
let res = return_signers(&tx, false, &OutputFormat::JsonCompact).unwrap();
let sign_only = parse_sign_only_reply_string(&res);
assert_eq!(sign_only.blockhash, blockhash);
assert_eq!(sign_only.message, None);
assert_eq!(sign_only.present_signers[0].0, present.pubkey());
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
assert_eq!(sign_only.bad_signers[0], bad.pubkey());

let expected_msg = "AwECBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgTl3Dqh9\
F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE\
BAQEBAYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBQUFBQUFBQUFBQUFBQUFBQUF\
BQUFBQUFBQUFBQUFBQUGp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcH\
BwcCBgMDBQIEBAAAAAYCAQQMAgAAACoAAAAAAAAA"
.to_string();
let res = return_signers(&tx, true, &OutputFormat::JsonCompact).unwrap();
let sign_only = parse_sign_only_reply_string(&res);
assert_eq!(sign_only.blockhash, blockhash);
assert_eq!(sign_only.message, Some(expected_msg));
assert_eq!(sign_only.present_signers[0].0, present.pubkey());
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
assert_eq!(sign_only.bad_signers[0], bad.pubkey());
Expand Down
Loading

0 comments on commit d82ac80

Please sign in to comment.