diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index b8fc16f2d2..3bf6805313 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -29,6 +29,5 @@ "e2e::wallet_tests::wallet_encrypted_key_cmds": 1, "e2e::wallet_tests::wallet_encrypted_key_cmds_env_var": 1, "e2e::wallet_tests::wallet_unencrypted_key_cmds": 1, - "e2e::ledger_tests::masp_txs_and_queries": 82, - "e2e::ledger_tests::offline_sign": 20 + "e2e::ledger_tests::masp_txs_and_queries": 82 } \ No newline at end of file diff --git a/crates/apps_lib/src/client/tx.rs b/crates/apps_lib/src/client/tx.rs index 2324d76429..fb38a0a53a 100644 --- a/crates/apps_lib/src/client/tx.rs +++ b/crates/apps_lib/src/client/tx.rs @@ -1116,7 +1116,7 @@ where let signature_path = File::create(&output_path) .expect("Should be able to create signature file."); serde_json::to_writer_pretty(signature_path, &signature) - .expect("Signature should be deserializable."); + .expect("Signature should be serializable."); display_line!( namada.io(), diff --git a/crates/tests/src/e2e/helpers.rs b/crates/tests/src/e2e/helpers.rs index 80d77a7cb5..47555d9205 100644 --- a/crates/tests/src/e2e/helpers.rs +++ b/crates/tests/src/e2e/helpers.rs @@ -1,9 +1,9 @@ //! E2E test helpers -use std::fs::{self, File, OpenOptions}; +use std::fs::{File, OpenOptions}; use std::future::Future; use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::process::Command; use std::str::FromStr; use std::time::{Duration, Instant}; @@ -726,24 +726,3 @@ pub fn get_gaia_gov_address(test: &Test) -> Result { Ok(matched.trim().to_string()) } - -pub fn find_offline_file( - dir: &Path, - extension: &str, -) -> Result> { - // Read the directory entries - for entry in fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - - if path.is_file() { - if let Some(file_extension) = path.extension() { - if file_extension == extension { - return Ok(Some(path)); - } - } - } - } - - Ok(None) -} diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 923606c878..160338c268 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -38,9 +38,8 @@ use setup::constants::*; use setup::Test; use super::helpers::{ - epochs_per_year_from_min_duration, find_keypair, find_offline_file, - get_height, get_pregenesis_wallet, wait_for_block_height, - wait_for_wasm_pre_compile, + epochs_per_year_from_min_duration, get_height, get_pregenesis_wallet, + wait_for_block_height, wait_for_wasm_pre_compile, }; use super::setup::{set_ethereum_bridge_mode, working_dir, NamadaCmd}; use crate::e2e::helpers::{ @@ -124,104 +123,6 @@ fn run_ledger() -> Result<()> { Ok(()) } -#[test] -fn offline_sign() -> Result<()> { - let test = setup::single_node_net()?; - - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); - - // 1. Run the ledger node - let mut ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))?; - ledger.exp_string("Committed block hash")?; - let _bg_ledger = ledger.background(); - - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - - let output_folder = test.test_dir.path().to_string_lossy().to_string(); - - // 2. Dump a transfer tx - let tx_args = apply_use_device(vec![ - "transparent-transfer", - "--source", - BERTHA, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "10.1", - "--gas-price", - "0.00090", - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - "--dump-tx", - "--output-folder-path", - &output_folder, - ]); - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.assert_success(); - - let offline_tx = find_offline_file(test.test_dir.path(), "tx") - .unwrap() - .expect("Offline tx should be found.") - .to_path_buf() - .display() - .to_string(); - - let bertha_address = find_address(&test, BERTHA).unwrap().to_string(); - let bertha_sk = find_keypair(&test, BERTHA_KEY).unwrap().to_string(); - - // 3. Offline sign a transfer tx - let tx_args = apply_use_device(vec![ - "utils", - "sign-offline", - "--data-path", - &offline_tx, - "--address", - &bertha_address, - "--secret-keys", - &bertha_sk, - "--output-folder-path", - &output_folder, - ]); - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.assert_success(); - - let offline_sig = find_offline_file(test.test_dir.path(), "sig") - .unwrap() - .expect("Offline signature should be found.") - .to_path_buf() - .display() - .to_string(); - - let tx_args = apply_use_device(vec![ - "tx", - "--owner", - BERTHA_KEY, - "--tx-path", - &offline_tx, - "--signatures", - &offline_sig, - "--node", - &validator_one_rpc, - "--gas-payer", - BERTHA_KEY, - ]); - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.assert_success(); - - Ok(()) -} - /// In this test we: /// 1. Run 2 genesis validator ledger nodes and 1 non-validator node /// 2. Cross over epoch to check for consensus with multiple nodes diff --git a/crates/tests/src/integration/helpers.rs b/crates/tests/src/integration/helpers.rs index 3b49ccbaea..487ffab972 100644 --- a/crates/tests/src/integration/helpers.rs +++ b/crates/tests/src/integration/helpers.rs @@ -6,6 +6,7 @@ use namada_core::address::Address; use namada_node::shell::testing::client::run; use namada_node::shell::testing::node::MockNode; use namada_node::shell::testing::utils::{Bin, CapturedOutput}; +use namada_sdk::key::common; /// Query the wallet to get an address from a given alias. pub fn find_address( @@ -37,6 +38,46 @@ pub fn find_address( Ok(address) } +pub fn find_keypair( + node: &MockNode, + alias: impl AsRef, +) -> eyre::Result { + let captured = CapturedOutput::of(|| { + run( + node, + Bin::Wallet, + vec![ + "find", + "--keys", + "--alias", + alias.as_ref(), + "--decrypt", + "--unsafe-show-secret", + ], + ) + }); + assert!(captured.result.is_ok()); + let matched = captured.matches("Public key: .*").unwrap(); + let pk = strip_trailing_newline(matched) + .trim() + .rsplit_once(' ') + .unwrap() + .1; + let matched = captured.matches("Secret key: .*").unwrap(); + let sk = strip_trailing_newline(matched) + .trim() + .rsplit_once(' ') + .unwrap() + .1; + let key = format!("{}{}", sk, pk); + common::SecretKey::from_str(sk).map_err(|e| { + eyre!(format!( + "Key: {} parsed from {}, Error: {}", + key, matched, e + )) + }) +} + fn strip_trailing_newline(input: &str) -> &str { input .strip_suffix("\r\n") diff --git a/crates/tests/src/integration/ledger_tests.rs b/crates/tests/src/integration/ledger_tests.rs index 3d73f4ee73..023ab0dd39 100644 --- a/crates/tests/src/integration/ledger_tests.rs +++ b/crates/tests/src/integration/ledger_tests.rs @@ -1,5 +1,7 @@ use std::collections::BTreeSet; +use std::fs; use std::num::NonZeroU64; +use std::path::{Path, PathBuf}; use std::str::FromStr; use assert_matches::assert_matches; @@ -37,7 +39,7 @@ use crate::e2e::setup::constants::{ }; use crate::e2e::setup::{apply_use_device, ensure_hot_key}; use crate::integration::helpers::{ - find_address, prepare_steward_commission_update_data, + find_address, find_keypair, prepare_steward_commission_update_data, }; use crate::integration::setup; use crate::strings::{ @@ -1671,6 +1673,107 @@ fn change_validator_metadata() -> Result<()> { Ok(()) } +#[test] +fn offline_sign() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + // 1. start the ledger node + let (node, _services) = setup::setup()?; + + let output_folder = tempfile::tempdir().unwrap(); + + // 2. Dump a transfer tx + let captured = CapturedOutput::of(|| { + run( + &node, + Bin::Client, + apply_use_device(vec![ + "transparent-transfer", + "--source", + BERTHA, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + "--gas-price", + "0.00090", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + "--dump-tx", + "--output-folder-path", + &output_folder.path().to_str().unwrap(), + ]), + ) + }); + assert!(captured.result.is_ok()); + + let offline_tx = find_file_with_ext(output_folder.path(), "tx") + .unwrap() + .expect("Offline tx should be found.") + .to_path_buf() + .display() + .to_string(); + + let bertha_address = find_address(&node, BERTHA).unwrap().to_string(); + let bertha_sk = find_keypair(&node, BERTHA_KEY).unwrap().to_string(); + + // 2. Dump a transfer tx + let captured = CapturedOutput::of(|| { + run( + &node, + Bin::Client, + apply_use_device(vec![ + "utils", + "sign-offline", + "--data-path", + &offline_tx, + "--address", + &bertha_address, + "--secret-keys", + &bertha_sk, + "--output-folder-path", + &output_folder.path().to_str().unwrap(), + ]), + ) + }); + assert!(captured.result.is_ok()); + + let offline_sig = find_file_with_ext(output_folder.path(), "sig") + .unwrap() + .expect("Offline signature should be found.") + .to_path_buf() + .display() + .to_string(); + + // 3. Offline sign a transfer tx + let captured = CapturedOutput::of(|| { + run( + &node, + Bin::Client, + vec![ + "tx", + "--owner", + BERTHA_KEY, + "--tx-path", + &offline_tx, + "--signatures", + &offline_sig, + "--node", + &validator_one_rpc, + "--gas-payer", + BERTHA_KEY, + ], + ) + }); + assert!(captured.result.is_ok()); + + Ok(()) +} + // Test that fee payment is enforced and aligned with process proposal. The test // generates a tx that subtract funds from the fee payer of a following tx. Test // that wrappers (and fee payments) are evaluated before the inner transactions. @@ -2183,3 +2286,24 @@ fn make_migration_json() -> (Hash, tempfile::NamedTempFile) { std::fs::write(file.path(), json).expect("Test failed"); (hash, file) } + +pub fn find_file_with_ext( + dir: &Path, + extension: &str, +) -> Result> { + // Read the directory entries + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + if path.is_file() { + if let Some(file_extension) = path.extension() { + if file_extension == extension { + return Ok(Some(path)); + } + } + } + } + + Ok(None) +}