Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions cmd/crates/soroban-test/tests/it/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use soroban_test::{AssertExt, TestEnv};
const TX_ENVELOPE: &str = "AAAAAgAAAAAnEWb4nxMsQhdnS16MqBxwItF3X/JNRkfxu8eyEQyfegAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAABAAAAABk++lrW4HlZwaWwYdYvJPCil1ibyI/VTH6WKda2sOC+AAAAAAAAAAAAAABkAAAAAAAAAAA=";
const SECRET_KEY: &str = "SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW";

// Fee-bump transaction envelope (regression test for #2769).
const FEE_BUMP_ENVELOPE: &str = "AAAABQAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAACcQAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";

// `tx sign` only mixes the network passphrase into the transaction hash, so it
// must not require an RPC URL (regression test for #2455).
#[tokio::test]
Expand Down Expand Up @@ -49,3 +52,25 @@ async fn tx_hash_requires_only_network_passphrase() {
assert_eq!(hash.len(), 64, "expected a 32-byte hex hash, got: {hash}");
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}

// `tx hash` must also support fee-bump transaction envelopes (regression test for #2769).
#[tokio::test]
async fn tx_hash_supports_fee_bump_transaction() {
let sandbox = &TestEnv::new();

let output = sandbox
.new_assert_cmd("tx")
.args([
"hash",
FEE_BUMP_ENVELOPE,
"--network-passphrase",
"specified manually",
])
.assert()
.success()
.stdout_as_str();

let hash = output.trim();
assert_eq!(hash.len(), 64, "expected a 32-byte hex hash, got: {hash}");
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}
52 changes: 49 additions & 3 deletions cmd/soroban-cli/src/commands/tx/hash.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use hex;
use std::ffi::OsString;

use crate::{commands::global, config::network, utils::transaction_hash};
use crate::{commands::global, config::network, utils::transaction_env_hash};

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand All @@ -28,12 +28,58 @@ pub struct Cmd {

impl Cmd {
pub fn run(&self, global_args: &global::Args) -> Result<(), Error> {
let tx = super::xdr::unwrap_envelope_v1(super::xdr::tx_envelope_from_input(&self.tx_xdr)?)?;
let tx_env = super::xdr::tx_envelope_from_input(&self.tx_xdr)?;
let network = &self.network.resolve(&global_args.locator, false)?;
println!(
"{}",
hex::encode(transaction_hash(&tx, &network.network_passphrase)?)
hex::encode(transaction_env_hash(&tx_env, &network.network_passphrase)?)
);
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::xdr::{
FeeBumpTransaction, FeeBumpTransactionEnvelope, FeeBumpTransactionExt,
FeeBumpTransactionInnerTx, MuxedAccount, SequenceNumber, Transaction, TransactionEnvelope,
TransactionExt, TransactionV1Envelope, Uint256,
};

#[test]
fn test_tx_hash_fee_bump() {
let v1_env = TransactionV1Envelope {
tx: Transaction {
source_account: MuxedAccount::Ed25519(Uint256([0u8; 32])),
fee: 100,
seq_num: SequenceNumber(1),
cond: crate::xdr::Preconditions::None,
memo: crate::xdr::Memo::None,
operations: [].try_into().unwrap(),
ext: TransactionExt::V0,
},
signatures: [].try_into().unwrap(),
};

let fee_bump_env = FeeBumpTransactionEnvelope {
tx: FeeBumpTransaction {
fee_source: MuxedAccount::Ed25519(Uint256([1u8; 32])),
fee: 10000,
inner_tx: FeeBumpTransactionInnerTx::Tx(v1_env),
ext: FeeBumpTransactionExt::V0,
},
signatures: [].try_into().unwrap(),
};

let tx_env = TransactionEnvelope::TxFeeBump(fee_bump_env);
let network_passphrase = "Test SDF Network ; September 2015";
let hash_result = transaction_env_hash(&tx_env, network_passphrase);
assert!(hash_result.is_ok());
let hash = hash_result.unwrap();
assert_eq!(hash.len(), 32);
let hex_hash = hex::encode(hash);
assert_eq!(hex_hash.len(), 64);
assert!(hex_hash.chars().all(|c| c.is_ascii_hexdigit()));
}
}