From 390ed15f4cc762f912e77550875ad15454f1debf Mon Sep 17 00:00:00 2001 From: wiesche Date: Thu, 8 Oct 2026 22:22:15 +0200 Subject: [PATCH 01/18] update secp256k1 to 0.8.0 --- Cargo.lock | 20 ++++++++++---------- grin | 2 +- libwallet/src/mwixnet/onion/mod.rs | 19 +++++++++++-------- libwallet/src/mwixnet/onion/onion.rs | 18 ++++++++++-------- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index debbb82..2067978 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2222,7 +2222,7 @@ checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" [[package]] name = "grin_api" -version = "5.5.1" +version = "5.5.2" dependencies = [ "bytes 1.12.0", "easy-jsonrpc-mw", @@ -2254,7 +2254,7 @@ dependencies = [ [[package]] name = "grin_chain" -version = "5.5.1" +version = "5.5.2" dependencies = [ "bit-vec", "bitflags 1.3.2", @@ -2278,7 +2278,7 @@ dependencies = [ [[package]] name = "grin_core" -version = "5.5.1" +version = "5.5.2" dependencies = [ "blake2-rfc", "byteorder", @@ -2304,7 +2304,7 @@ dependencies = [ [[package]] name = "grin_keychain" -version = "5.5.1" +version = "5.5.2" dependencies = [ "blake2-rfc", "byteorder", @@ -2325,7 +2325,7 @@ dependencies = [ [[package]] name = "grin_p2p" -version = "5.5.1" +version = "5.5.2" dependencies = [ "bitflags 1.3.2", "built", @@ -2348,7 +2348,7 @@ dependencies = [ [[package]] name = "grin_pool" -version = "5.5.1" +version = "5.5.2" dependencies = [ "blake2-rfc", "chrono", @@ -2365,9 +2365,9 @@ dependencies = [ [[package]] name = "grin_secp256k1zkp" -version = "0.7.15" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7bb95f155b1eede2648a1b9afbba82bc3d9f2af0518b478767559a572bd973" +checksum = "57e0a5f761e8c56db5be865f79832677d3badb3be91d7172d468e448f3d69f82" dependencies = [ "arrayvec 0.7.8", "cc", @@ -2380,7 +2380,7 @@ dependencies = [ [[package]] name = "grin_store" -version = "5.5.1" +version = "5.5.2" dependencies = [ "byteorder", "chrono", @@ -2402,7 +2402,7 @@ dependencies = [ [[package]] name = "grin_util" -version = "5.5.1" +version = "5.5.2" dependencies = [ "anyhow", "backtrace", diff --git a/grin b/grin index d5f3235..dbaff34 160000 --- a/grin +++ b/grin @@ -1 +1 @@ -Subproject commit d5f32350ab548736c17c1db7ff51457917fe4e96 +Subproject commit dbaff34f7751ee23316fbbfe5806b47c546776ca diff --git a/libwallet/src/mwixnet/onion/mod.rs b/libwallet/src/mwixnet/onion/mod.rs index e1eb424..c23ada3 100644 --- a/libwallet/src/mwixnet/onion/mod.rs +++ b/libwallet/src/mwixnet/onion/mod.rs @@ -170,6 +170,7 @@ pub mod test_util { None, None, ) + .expect("failed to generate test bulletproof") } pub fn proof( @@ -187,14 +188,16 @@ pub mod test_util { let out_value = value - (fee as u64); - let rp = secp.bullet_proof( - out_value, - blind.clone(), - secp::random_secret(false), - secp::random_secret(false), - None, - None, - ); + let rp = secp + .bullet_proof( + out_value, + blind.clone(), + secp::random_secret(false), + secp::random_secret(false), + None, + None, + ) + .expect("failed to generate test bulletproof"); (secp::commit(out_value, &blind).unwrap(), rp) } diff --git a/libwallet/src/mwixnet/onion/onion.rs b/libwallet/src/mwixnet/onion/onion.rs index 2540dae..8159232 100644 --- a/libwallet/src/mwixnet/onion/onion.rs +++ b/libwallet/src/mwixnet/onion/onion.rs @@ -397,14 +397,16 @@ pub mod tests { final_commit = secp::add_excess(&final_commit, &excess).unwrap(); let proof = if i == 4 { let n1 = random_secret(false); - let rp = secp.bullet_proof( - out_value, - final_blind.clone(), - n1.clone(), - n1.clone(), - None, - None, - ); + let rp = secp + .bullet_proof( + out_value, + final_blind.clone(), + n1.clone(), + n1.clone(), + None, + None, + ) + .expect("failed to generate test bulletproof"); assert!(secp.verify_bullet_proof(final_commit, rp, None).is_ok()); Some(rp) } else { From ac4d18d6bd0759be23a8485a2054416187848946 Mon Sep 17 00:00:00 2001 From: wiesche <120273695+wiesche89@users.noreply.github.com> Date: Mon, 14 Sep 2026 21:08:36 +0200 Subject: [PATCH 02/18] reject wrong slate states in receive and finalize (#807) (cherry picked from commit e5e319b47efb754ca02000b03f4ba65e608937c7) --- controller/src/command.rs | 38 +++++++++++++++++++++++++++++++ controller/tests/invoice.rs | 38 +++++++++++++++++++++++++++++++ libwallet/src/api_impl/foreign.rs | 3 +++ tests/cmd_line_basic.rs | 16 +++++++++++++ 4 files changed, 95 insertions(+) diff --git a/controller/src/command.rs b/controller/src/command.rs index e8970bd..b645892 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -696,6 +696,25 @@ where args.input_file, args.input_slatepack_message, )?; + match slate.state { + SlateState::Invoice1 => { + return Err(Error::GenericError( + "Use the 'pay' command to process this invoice transaction".to_string(), + )) + } + SlateState::Standard2 | SlateState::Invoice2 => { + return Err(Error::GenericError( + "Use the 'finalize' command to complete this transaction".to_string(), + )) + } + SlateState::Standard3 | SlateState::Invoice3 => { + return Err(Error::GenericError( + "Use the 'post' command to post this finalized transaction to the chain" + .to_string(), + )) + } + _ => {} + } let km = match keychain_mask.as_ref() { None => None, @@ -859,6 +878,25 @@ where args.input_file.clone(), args.input_slatepack_message.clone(), )?; + match slate.state { + SlateState::Standard1 => { + return Err(Error::GenericError( + "Use the 'receive' command on the recipient's wallet first".to_string(), + )) + } + SlateState::Invoice1 => { + return Err(Error::GenericError( + "Use the 'pay' command on the payer's wallet first".to_string(), + )) + } + SlateState::Standard3 | SlateState::Invoice3 => { + return Err(Error::GenericError( + "Use the 'post' command to post this finalized transaction to the chain" + .to_string(), + )) + } + _ => {} + } // Rather than duplicating the entire command, we'll just // try to determine what kind of finalization this is diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 7acd8bf..3713d5e 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -124,6 +124,44 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { )?; assert_eq!(slate.state, SlateState::Invoice1); + let receive_result = wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| api.receive_tx(&slate, None, None).map(|_| ()), + ); + assert_eq!(receive_result, Err(libwallet::Error::SlateState)); + wallet::controller::owner_single_use( + wallet1.clone(), + mask1, + PathBuf::from(test_dir), + |api, m| { + let (_, txs) = api.retrieve_txs(m, false, None, Some(slate.id), None)?; + assert!(txs.is_empty()); + Ok(()) + }, + )?; + + // receive must reject every slate state except S1 + for state in [ + SlateState::Unknown, + SlateState::Standard2, + SlateState::Standard3, + SlateState::Invoice1, + SlateState::Invoice2, + SlateState::Invoice3, + ] { + let mut bad_slate = slate.clone(); + bad_slate.state = state.clone(); + let res = wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| api.receive_tx(&bad_slate, None, None).map(|_| ()), + ); + assert_eq!(res, Err(libwallet::Error::SlateState), "state {}", state); + } + wallet::controller::owner_single_use( wallet1.clone(), mask1, diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index 76b6014..4547b87 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -66,6 +66,9 @@ where K: Keychain, { let mut ret_slate = slate.clone(); + if ret_slate.state != SlateState::Standard1 { + return Err(Error::SlateState); + } check_ttl(w, &ret_slate)?; let parent_key_id = match dest_acct_name { Some(d) => { diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index 019e6cf..86f7675 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -567,6 +567,22 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: test_dir ); + // receive and finalize should point to pay + for cmd in ["receive", "finalize"] { + let arg_vec = vec![ + "grin-wallet", + "-a", + "mining", + "-p", + "password1", + cmd, + "-i", + &file_name, + ]; + let e = execute_command(&app, test_dir, "wallet1", &client1, arg_vec).unwrap_err(); + assert!(e.to_string().contains("'pay'"), "{}", e); + } + // now pay the invoice tx, wallet 1 let arg_vec = vec![ "grin-wallet", From bded95e28233f9eaec71c36c27ee12b39afa9c13 Mon Sep 17 00:00:00 2001 From: wiesche <120273695+wiesche89@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:42:56 +0200 Subject: [PATCH 03/18] use current header version in new slates (#804) * use current header version * flip header version check and bump min node version (cherry picked from commit 991ba1a1f7d4e0e4d0e233ca475cfd77b498a6c3) --- api/src/owner_rpc.rs | 2 +- controller/src/controller.rs | 10 +++------- controller/tests/transaction.rs | 24 +++++++++++++++++++++++- libwallet/src/internal/tx.rs | 15 ++------------- libwallet/src/slate_versions/mod.rs | 4 ++-- src/cmd/wallet.rs | 2 +- 6 files changed, 32 insertions(+), 25 deletions(-) diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index 628d8ef..92511b0 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -973,7 +973,7 @@ pub trait OwnerRpc { "id": "0436430c-2b02-624c-2032-570501212b00", "sigs": [], "sta": "S3", - "ver": "4:3" + "ver": "4:5" } } } diff --git a/controller/src/controller.rs b/controller/src/controller.rs index c2f2c4e..ef61152 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -66,22 +66,18 @@ lazy_static! { fn check_middleware( name: ForeignCheckMiddlewareFn, - node_version_info: Option, + _node_version_info: Option, slate: Option<&Slate>, ) -> Result<(), Error> { match name { // allow coinbases to be built regardless ForeignCheckMiddlewareFn::BuildCoinbase => Ok(()), _ => { - let mut bhv = 3; - if let Some(n) = node_version_info { - bhv = n.block_header_version; - } if let Some(s) = slate { - if bhv > 4 && s.version_info.block_header_version < GRIN_BLOCK_HEADER_VERSION { + if s.version_info.block_header_version > GRIN_BLOCK_HEADER_VERSION { Err(Error::Compatibility( "Incoming Slate is not compatible with this wallet. \ - Please upgrade the node or use a different one." + Please upgrade the wallet." .into(), ))?; } diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 6c3d8e5..5425ef8 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -23,7 +23,7 @@ use grin_core as core; use self::core::core::transaction; use self::core::core::Transaction; use self::core::global; -use self::libwallet::{InitTxArgs, OutputStatus, Slate, SlateState}; +use self::libwallet::{InitTxArgs, OutputStatus, Slate, SlateState, GRIN_BLOCK_HEADER_VERSION}; use impls::test_framework::{self, LocalWalletClient}; use std::convert::TryInto; use std::path::PathBuf; @@ -105,6 +105,7 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> // assert wallet contents // and a single use api for a send command let amount = 60_000_000_000; + let header_version = core::consensus::header_version(chain.head().unwrap().height).0; let mut slate = Slate::blank(1, false); wallet::controller::owner_single_use( wallet1.clone(), @@ -124,6 +125,7 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> let slate_i = sender_api.init_send_tx(m, args)?; assert_eq!(slate_i.state, SlateState::Standard1); + assert_eq!(slate_i.version_info.block_header_version, header_version); // Check we are creating a tx with the expected lock_height of 0. // We will check this produces a Plain kernel later. @@ -131,6 +133,26 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; assert_eq!(slate.state, SlateState::Standard2); + + // wallet2 already has this slate, so accepted versions fail on the duplicate check + for bhv in 1..=GRIN_BLOCK_HEADER_VERSION + 1 { + let mut s = slate_i.clone(); + s.version_info.block_header_version = bhv; + let res = wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| api.receive_tx(&s, None, None).map(|_| ()), + ); + if bhv <= GRIN_BLOCK_HEADER_VERSION { + assert!(matches!( + res, + Err(libwallet::Error::TransactionAlreadyReceived(_)) + )); + } else { + assert!(matches!(res, Err(libwallet::Error::Compatibility(_)))); + } + } sender_api.tx_lock_outputs(m, &slate)?; slate = sender_api.finalize_tx(m, &slate)?; assert_eq!(slate.state, SlateState::Standard3); diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 449f98f..6b655e3 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -18,8 +18,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::io::Cursor; use uuid::Uuid; -use crate::grin_core::consensus::valid_header_version; -use crate::grin_core::core::HeaderVersion; +use crate::grin_core::consensus::header_version; use crate::grin_keychain::{Identifier, Keychain}; use crate::grin_util::secp::key::SecretKey; use crate::grin_util::secp::pedersen; @@ -70,17 +69,7 @@ where } slate.amount = amount; - if valid_header_version(current_height, HeaderVersion(1)) { - slate.version_info.block_header_version = 1; - } - - if valid_header_version(current_height, HeaderVersion(2)) { - slate.version_info.block_header_version = 2; - } - - if valid_header_version(current_height, HeaderVersion(3)) { - slate.version_info.block_header_version = 3; - } + slate.version_info.block_header_version = header_version(current_height).0; // Set the features explicitly to 0 here. // This will generate a Plain kernel (rather than a HeightLocked kernel). diff --git a/libwallet/src/slate_versions/mod.rs b/libwallet/src/slate_versions/mod.rs index ca2e749..05416d9 100644 --- a/libwallet/src/slate_versions/mod.rs +++ b/libwallet/src/slate_versions/mod.rs @@ -34,8 +34,8 @@ pub mod v4_bin; /// The most recent version of the slate pub const CURRENT_SLATE_VERSION: u16 = 4; -/// The grin block header this slate is intended to be compatible with -pub const GRIN_BLOCK_HEADER_VERSION: u16 = 3; +/// The latest grin block header version this wallet supports +pub const GRIN_BLOCK_HEADER_VERSION: u16 = 5; /// Existing versions of the slate #[derive(EnumIter, Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] diff --git a/src/cmd/wallet.rs b/src/cmd/wallet.rs index e538ef6..9e6c2cb 100644 --- a/src/cmd/wallet.rs +++ b/src/cmd/wallet.rs @@ -20,7 +20,7 @@ use semver::Version; use std::thread; use std::time::Duration; -const MIN_COMPAT_NODE_VERSION: &str = "4.0.0-alpha.1"; +const MIN_COMPAT_NODE_VERSION: &str = "5.0.0-beta.1"; pub fn wallet_command( wallet_args: &ArgMatches<'_>, From 31635576d7273511a1250b230cea92dcbd5a93b9 Mon Sep 17 00:00:00 2001 From: wiesche <120273695+wiesche89@users.noreply.github.com> Date: Tue, 15 Sep 2026 20:34:26 +0200 Subject: [PATCH 04/18] display slatepack addresses (#808) (cherry picked from commit 18da793ace9b8fe83407aa7cd55fd85375f0ea37) --- controller/src/display.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/controller/src/display.rs b/controller/src/display.rs index e5f2f88..f94a855 100644 --- a/controller/src/display.rs +++ b/controller/src/display.rs @@ -16,10 +16,10 @@ use crate::core::core::FeeFields; use crate::core::core::{self, amount_to_hr_string}; use crate::core::global; use crate::libwallet::{ - AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, ViewWallet, WalletInfo, + AcctPathMapping, Error, OutputCommitMapping, OutputStatus, SlatepackAddress, TxLogEntry, + ViewWallet, WalletInfo, }; use crate::util::ToHex; -use grin_wallet_util::OnionV3Address; use prettytable; use std::io::prelude::Write; use term; @@ -614,7 +614,7 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { writeln!( t, "Receiver Address: {}", - OnionV3Address::from_bytes(pp.receiver_address.to_bytes()) + SlatepackAddress::new(&pp.receiver_address) ) .unwrap(); writeln!(t, "Receiver Signature: {}", receiver_signature).unwrap(); @@ -623,7 +623,7 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { writeln!( t, "Sender Address: {}", - OnionV3Address::from_bytes(pp.sender_address.to_bytes()) + SlatepackAddress::new(&pp.sender_address) ) .unwrap(); writeln!(t, "Sender Signature: {}", sender_signature).unwrap(); From 4cb67d9e54398b20732d872db25696f8672032bc Mon Sep 17 00:00:00 2001 From: wiesche Date: Mon, 17 Aug 2026 16:36:57 +0200 Subject: [PATCH 05/18] clarify bip32 derivation paths (#800) Co-authored-by: ardocrat (cherry picked from commit f585c70eba95f47942ddb8dbf4931f12d2079775) --- controller/src/display.rs | 23 ++++++++++++++---- controller/tests/accounts.rs | 2 ++ libwallet/src/address.rs | 41 +++++++++++++++++++++++++-------- libwallet/src/api_impl/owner.rs | 6 ++++- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/controller/src/display.rs b/controller/src/display.rs index f94a855..559fbcf 100644 --- a/controller/src/display.rs +++ b/controller/src/display.rs @@ -16,11 +16,12 @@ use crate::core::core::FeeFields; use crate::core::core::{self, amount_to_hr_string}; use crate::core::global; use crate::libwallet::{ - AcctPathMapping, Error, OutputCommitMapping, OutputStatus, SlatepackAddress, TxLogEntry, - ViewWallet, WalletInfo, + address, AcctPathMapping, Error, OutputCommitMapping, OutputStatus, SlatepackAddress, + TxLogEntry, ViewWallet, WalletInfo, }; use crate::util::ToHex; use prettytable; +use prettytable::format::{FormatBuilder, LinePosition, LineSeparator}; use std::io::prelude::Write; use term; @@ -537,15 +538,29 @@ pub fn accounts(acct_mappings: Vec) { table.set_titles(row![ mMG->"Name", - bMG->"Parent BIP-32 Derivation Path", + bMG->"Parent Output Key", + bMG->"Slatepack Address (Index 0)", ]); for m in acct_mappings { + let slatepack_path = address::address_derivation_path(&m.path, 0).to_bip_32_string(); table.add_row(row![ bFC->m.label, bGC->m.path.to_bip_32_string(), + bGC->slatepack_path, ]); } - table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_format( + FormatBuilder::new() + .column_separator('|') + .separators( + &[LinePosition::Top, LinePosition::Title], + LineSeparator::new('-', '+', '+', '+'), + ) + .padding(1, 1) + .build(), + ); + let width = table.to_string().find('\n').unwrap_or(0); + println!("{:^1$}", "BIP-32 Derivation Path", width); table.printstd(); println!(); } diff --git a/controller/tests/accounts.rs b/controller/tests/accounts.rs index 9a47e41..59645f8 100644 --- a/controller/tests/accounts.rs +++ b/controller/tests/accounts.rs @@ -175,6 +175,8 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { mask1, PathBuf::from(test_dir), |api, m| { + let labels: Vec<_> = api.accounts(m)?.into_iter().map(|a| a.label).collect(); + assert_eq!(labels, ["account1", "default", "account2", "account3"]); // check last confirmed height on this account is different from above (should be 0) let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; assert_eq!(wallet1_info.last_confirmed_height, 0); diff --git a/libwallet/src/address.rs b/libwallet/src/address.rs index 8d55b39..4a6f54b 100644 --- a/libwallet/src/address.rs +++ b/libwallet/src/address.rs @@ -21,6 +21,23 @@ use grin_keychain::{ChildNumber, Identifier, Keychain, SwitchCommitmentType}; use crate::blake2::blake2b::blake2b; +/// Derive a Slatepack address path from an output parent path and index. +/// +/// Output paths: +/// - account m/0: m/0/0/0, m/0/0/1 +/// - account m/1: m/1/0/0, m/1/0/1 +/// +/// Slatepack address paths: +/// - account m/0: m/0/1/0, m/0/1/1 +/// - account m/1: m/1/1/0, m/1/1/1 +pub fn address_derivation_path(parent_key_id: &Identifier, index: u32) -> Identifier { + let mut key_path = parent_key_id.to_path(); + key_path.path[1] = ChildNumber::from(1); + key_path.depth += 1; + key_path.path[key_path.depth as usize - 1] = ChildNumber::from(index); + Identifier::from_path(&key_path) +} + /// Derive a secret key given a derivation path and index pub fn address_from_derivation_path( keychain: &K, @@ -30,16 +47,7 @@ pub fn address_from_derivation_path( where K: Keychain, { - let mut key_path = parent_key_id.to_path(); - // An output derivation for acct m/0 - // is m/0/0/0, m/0/0/1 (for instance), m/1 is m/1/0/0, m/1/0/1 - // Address generation path should be - // for m/0: m/0/1/0, m/0/1/1 - // for m/1: m/1/1/0, m/1/1/1 - key_path.path[1] = ChildNumber::from(1); - key_path.depth += 1; - key_path.path[key_path.depth as usize - 1] = ChildNumber::from(index); - let key_id = Identifier::from_path(&key_path); + let key_id = address_derivation_path(parent_key_id, index); let sec_key = keychain.derive_key(0, &key_id, SwitchCommitmentType::None)?; let hashed = blake2b(32, &[], &sec_key.0[..]); Ok(SecretKey::from_slice( @@ -47,3 +55,16 @@ where &hashed.as_bytes()[..], )?) } + +#[cfg(test)] +mod tests { + use super::*; + use grin_keychain::ExtKeychain; + + #[test] + fn slatepack_address_path() { + let parent = ExtKeychain::derive_key_id(2, 2, 0, 0, 0); + let expected = ExtKeychain::derive_key_id(3, 2, 1, 3, 0); + assert_eq!(address_derivation_path(&parent, 3), expected); + } +} diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 10a8d4a..638c608 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -57,7 +57,11 @@ where C: NodeClient, K: Keychain, { - keys::accounts(w) + let mut accounts = keys::accounts(w)?; + accounts.sort_by(|a, b| a.path.cmp(&b.path)); + // Put active account on top. + accounts.sort_by_key(|k| k.path != w.parent_key_id()); + Ok(accounts) } /// new account path From 2daa36f7485a935903e2650e8325a823e1b7c671 Mon Sep 17 00:00:00 2001 From: wiesche Date: Mon, 17 Aug 2026 14:43:11 +0200 Subject: [PATCH 06/18] handle unavailable terminal colors (#797) Co-authored-by: ardocrat (cherry picked from commit 99cca5b8bfdeb1f129d7b467f4241ed043fc47b6) --- controller/src/display.rs | 88 +++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/controller/src/display.rs b/controller/src/display.rs index 559fbcf..54077fc 100644 --- a/controller/src/display.rs +++ b/controller/src/display.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::core::core::amount_to_hr_string; use crate::core::core::FeeFields; -use crate::core::core::{self, amount_to_hr_string}; use crate::core::global; use crate::libwallet::{ address, AcctPathMapping, Error, OutputCommitMapping, OutputStatus, SlatepackAddress, @@ -43,9 +43,9 @@ pub fn outputs( return Ok(()); } let mut t = term::stdout().unwrap(); - t.fg(term::color::MAGENTA).unwrap(); - writeln!(t, "{}", title).unwrap(); - t.reset().unwrap(); + let _ = t.fg(term::color::MAGENTA); + writeln!(t, "{}", title)?; + let _ = t.reset(); let mut table = table!(); @@ -78,7 +78,7 @@ pub fn outputs( }; let num_confirmations = format!("{}", m.output.num_confirmations(cur_height)); - let value = format!("{}", core::amount_to_hr_string(m.output.value, false)); + let value = format!("{}", amount_to_hr_string(m.output.value, false)); let tx = match m.output.tx_log_entry { None => "".to_owned(), Some(t) => t.to_string(), @@ -118,7 +118,7 @@ pub fn outputs( if !validated { println!( "\nWARNING: Wallet failed to verify data. \ - The above is from local cache and possibly invalid! \ + The above is from local cache and possibly invalid! \ (is your `grin server` offline or broken?)" ); } @@ -144,9 +144,9 @@ pub fn txs( return Ok(()); } let mut t = term::stdout().unwrap(); - t.fg(term::color::MAGENTA).unwrap(); - writeln!(t, "{}", title).unwrap(); - t.reset().unwrap(); + let _ = t.fg(term::color::MAGENTA); + writeln!(t, "{}", title)?; + let _ = t.reset(); let mut table = table!(); @@ -193,18 +193,18 @@ pub fn txs( let confirmed = format!("{}", t.confirmed); let num_inputs = format!("{}", t.num_inputs); let num_outputs = format!("{}", t.num_outputs); - let amount_debited_str = core::amount_to_hr_string(t.amount_debited, true); - let amount_credited_str = core::amount_to_hr_string(t.amount_credited, true); + let amount_debited_str = amount_to_hr_string(t.amount_debited, true); + let amount_credited_str = amount_to_hr_string(t.amount_credited, true); let fee = match t.fee { - Some(f) => format!("{}", core::amount_to_hr_string(f.fee(), true)), + Some(f) => format!("{}", amount_to_hr_string(f.fee(), true)), None => "None".to_owned(), }; let net_diff = if t.amount_credited >= t.amount_debited { - core::amount_to_hr_string(t.amount_credited - t.amount_debited, true) + amount_to_hr_string(t.amount_credited - t.amount_debited, true) } else { format!( "-{}", - core::amount_to_hr_string(t.amount_debited - t.amount_credited, true) + amount_to_hr_string(t.amount_debited - t.amount_credited, true) ) }; let tx_data = match t.stored_tx { @@ -290,7 +290,7 @@ pub fn txs( if !validated && include_status { println!( "\nWARNING: Wallet failed to verify data. \ - The above is from local cache and possibly invalid! \ + The above is from local cache and possibly invalid! \ (is your `grin server` offline or broken?)" ); } @@ -334,9 +334,9 @@ pub fn view_wallet_output( } let mut t = term::stdout().unwrap(); - t.fg(term::color::MAGENTA).unwrap(); - writeln!(t, "{}", title).unwrap(); - t.reset().unwrap(); + let _ = t.fg(term::color::MAGENTA); + writeln!(t, "{}", title)?; + let _ = t.reset(); let mut table = table!(); @@ -357,7 +357,7 @@ pub fn view_wallet_output( let lock_height = format!("{}", m.lock_height); let is_coinbase = format!("{}", m.is_coinbase); let num_confirmations = format!("{}", m.num_confirmations(cur_height)); - let value = format!("{}", core::amount_to_hr_string(m.value, false)); + let value = format!("{}", amount_to_hr_string(m.value, false)); if dark_background_color_scheme { table.add_row(row![ @@ -409,11 +409,11 @@ pub fn info( ]); if wallet_info.amount_reverted > 0 { table.add_row(row![ - Fr->format!("Reverted"), + Fr->"Reverted", Fr->amount_to_hr_string(wallet_info.amount_reverted, false) ]); } - // Only dispay "Immature Coinbase" if we have related outputs in the wallet. + // Only display "Immature Coinbase" if we have related outputs in the wallet. // This row just introduces confusion if the wallet does not receive coinbase rewards. if wallet_info.amount_immature > 0 { table.add_row(row![ @@ -426,7 +426,7 @@ pub fn info( FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation, false) ]); table.add_row(row![ - bFB->format!("Awaiting Finalization"), + bFB->"Awaiting Finalization", FB->amount_to_hr_string(wallet_info.amount_awaiting_finalization, false) ]); table.add_row(row![ @@ -448,11 +448,11 @@ pub fn info( ]); if wallet_info.amount_reverted > 0 { table.add_row(row![ - Fr->format!("Reverted"), + Fr->"Reverted", Fr->amount_to_hr_string(wallet_info.amount_reverted, false) ]); } - // Only dispay "Immature Coinbase" if we have related outputs in the wallet. + // Only display "Immature Coinbase" if we have related outputs in the wallet. // This row just introduces confusion if the wallet does not receive coinbase rewards. if wallet_info.amount_immature > 0 { table.add_row(row![ @@ -483,7 +483,7 @@ pub fn info( if !validated { println!( "\nWARNING: Wallet failed to verify data against a live chain. \ - The above is from local cache and only valid up to the given height! \ + The above is from local cache and only valid up to the given height! \ (is your `grin server` offline or broken?)" ); } @@ -533,7 +533,7 @@ pub fn estimate( /// Display list of wallet accounts in a pretty way pub fn accounts(acct_mappings: Vec) { - println!("\n____ Wallet Accounts ____\n",); + println!("\n____ Wallet Accounts ____\n"); let mut table = table!(); table.set_titles(row![ @@ -567,28 +567,28 @@ pub fn accounts(acct_mappings: Vec) { /// Display individual Payment Proof pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { - let title = format!("Payment Proof - Transaction '{}'", tx.id,); + let title = format!("Payment Proof - Transaction '{}'", tx.id); println!(); if term::stdout().is_none() { println!("Could not open terminal"); return Ok(()); } let mut t = term::stdout().unwrap(); - t.fg(term::color::MAGENTA).unwrap(); - writeln!(t, "{}", title).unwrap(); - t.reset().unwrap(); + let _ = t.fg(term::color::MAGENTA); + writeln!(t, "{}", title)?; + let _ = t.reset(); let pp = match &tx.payment_proof { None => { - writeln!(t, "None").unwrap(); - t.reset().unwrap(); + writeln!(t, "None")?; + let _ = t.reset(); return Ok(()); } Some(p) => p.clone(), }; - t.fg(term::color::WHITE).unwrap(); - writeln!(t).unwrap(); + let _ = t.fg(term::color::WHITE); + writeln!(t)?; let receiver_signature = match pp.receiver_signature { Some(s) => { let sig_bytes = s.to_bytes(); @@ -602,11 +602,11 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { None => 0, }; let amount = if tx.amount_credited >= tx.amount_debited { - core::amount_to_hr_string(tx.amount_credited - tx.amount_debited, true) + amount_to_hr_string(tx.amount_credited - tx.amount_debited, true) } else { format!( "{}", - core::amount_to_hr_string(tx.amount_debited - tx.amount_credited - fee, true) + amount_to_hr_string(tx.amount_debited - tx.amount_credited - fee, true) ) }; @@ -630,20 +630,18 @@ pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { t, "Receiver Address: {}", SlatepackAddress::new(&pp.receiver_address) - ) - .unwrap(); - writeln!(t, "Receiver Signature: {}", receiver_signature).unwrap(); - writeln!(t, "Amount: {}", amount).unwrap(); - writeln!(t, "Kernel Excess: {}", kernel_excess).unwrap(); + )?; + writeln!(t, "Receiver Signature: {}", receiver_signature)?; + writeln!(t, "Amount: {}", amount)?; + writeln!(t, "Kernel Excess: {}", kernel_excess)?; writeln!( t, "Sender Address: {}", SlatepackAddress::new(&pp.sender_address) - ) - .unwrap(); - writeln!(t, "Sender Signature: {}", sender_signature).unwrap(); + )?; + writeln!(t, "Sender Signature: {}", sender_signature)?; - t.reset().unwrap(); + let _ = t.reset(); println!(); From 78ae279bdf3a63bb70cb7afdf71f1c59f4b37a9e Mon Sep 17 00:00:00 2001 From: wiesche Date: Mon, 17 Aug 2026 15:00:54 +0200 Subject: [PATCH 07/18] reject invalid invoice locking (#798) (cherry picked from commit 645363436d41ba621d8aa9fd536c619ee2110c5d) --- controller/tests/invoice.rs | 16 ++++++++++++++++ libwallet/src/api_impl/owner.rs | 3 +++ 2 files changed, 19 insertions(+) diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 3713d5e..006d5d4 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -184,6 +184,22 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { )?; assert_eq!(slate.state, SlateState::Invoice2); + wallet::controller::owner_single_use( + wallet2.clone(), + mask2, + PathBuf::from(test_dir), + |api, m| { + assert_eq!( + api.tx_lock_outputs(m, &slate), + Err(libwallet::Error::SlateState) + ); + let (_, txs) = api.retrieve_txs(m, false, None, None, None)?; + assert_eq!(txs.len(), 1); + assert_eq!(txs[0].tx_type, libwallet::TxLogEntryType::TxReceived); + Ok(()) + }, + )?; + // wallet 2 finalizes and posts wallet::controller::foreign_single_use( wallet2.clone(), diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 638c608..44aecae 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -830,6 +830,9 @@ where K: Keychain, { let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?; + if slate.state == SlateState::Invoice2 && context.input_ids.is_empty() { + return Err(Error::SlateState); + } let mut excess_override = None; let mut sl = slate.clone(); From 5adceaae786eee4a4e2593f0c3941896e5d7b196 Mon Sep 17 00:00:00 2001 From: wiesche Date: Mon, 17 Aug 2026 14:23:23 +0200 Subject: [PATCH 08/18] fix tx filter (#795) (cherry picked from commit 0d5ea84bfd6bcfffdcc1f2eb5ce313f428d08162) --- controller/tests/tx_list_filter.rs | 19 +++++++++++++++++++ libwallet/src/internal/updater.rs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/controller/tests/tx_list_filter.rs b/controller/tests/tx_list_filter.rs index 424db24..c7fa9c8 100644 --- a/controller/tests/tx_list_filter.rs +++ b/controller/tests/tx_list_filter.rs @@ -61,6 +61,25 @@ fn test_wallet_tx_filtering( mask, PathBuf::from(test_dir), |api, _m| { + let query = |args| { + api.retrieve_txs(mask, true, None, None, Some(args)) + .map(|(_, txs)| txs) + }; + let txs = query(RetrieveTxQueryArgs::default())?; + assert!(!txs.is_empty()); + let before_first_tx = + txs.iter().map(|tx| tx.creation_ts).min().unwrap() - Duration::from_secs(1); + let mut tx_query_args = RetrieveTxQueryArgs::default(); + + // Max creation time before all test transactions + tx_query_args.max_creation_timestamp = Some(before_first_tx); + assert!(query(tx_query_args.clone())?.is_empty()); + + // Min confirmation time should not affect creation time + tx_query_args.max_creation_timestamp = None; + tx_query_args.min_confirmed_timestamp = Some(before_first_tx); + assert_eq!(query(tx_query_args)?.len(), txs.len()); + let mut tx_query_args = RetrieveTxQueryArgs::default(); tx_query_args.min_id = Some(5); diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index 9590f23..47134e5 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -247,7 +247,7 @@ where } }) .filter(|tx_entry| { - if let Some(v) = query_args.min_confirmed_timestamp { + if let Some(v) = query_args.max_creation_timestamp { tx_entry.creation_ts <= v } else { true From 697cee89b74a8807cd87b437295ecdc089031ecb Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 2 Sep 2026 23:32:16 +0300 Subject: [PATCH 09/18] Downgrade arti to 0.43 (#810) (cherry picked from commit e0873a1b2ee33492dd08af2566bbd9bd3b8166ea) --- Cargo.lock | 226 +++++++++++++++++++++++------------------------ impls/Cargo.toml | 16 ++-- 2 files changed, 121 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2067978..b1948dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,9 +202,9 @@ checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "arti-client" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d54b6c87059a9b6562bfe585f3a637b72f90cddaa327ac29b5ddf082a54b4" +checksum = "7ffc89b08e6a475a83d70e52f0ede139a433eb46e986a6cee6eeaa1b73297bf1" dependencies = [ "async-trait", "cfg-if 1.0.4", @@ -699,9 +699,9 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "caret" -version = "0.10.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb840a063107846780a14acc022b55bf6c174997a393ffde8b58ff6630c1b56" +checksum = "beae2cb9f60bc3f21effaaf9c64e51f6627edd54eedc9199ba07f519ef2a2101" [[package]] name = "cc" @@ -1970,9 +1970,9 @@ dependencies = [ [[package]] name = "fs-mistrust" -version = "0.15.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd938e053fbfb6dba36106f4bcf27c53362e6152adc9341700c4bc2264cd4a8f" +checksum = "9cfebc7c6bb65d327ded064db65cd260b6c418c27ae790318650cfa2a81bf33f" dependencies = [ "derive_builder_fork_arti", "dirs 6.0.0", @@ -1984,6 +1984,17 @@ dependencies = [ "walkdir", ] +[[package]] +name = "fslock-guard" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77b8a36ee17f9079b53db7281de9b16a8fb2bc97a7cd1e41a2998f3a837af7a" +dependencies = [ + "libc", + "thiserror 2.0.18", + "windows-sys 0.61.2", +] + [[package]] name = "fslock-guard" version = "0.8.0" @@ -2040,9 +2051,9 @@ dependencies = [ [[package]] name = "futures-copy" -version = "0.4.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcad91f8ba72542347d5cc8ee43891abe4698429fa7b3de56f17f4bb6fa514c4" +checksum = "24f320278d16b5617859cc2234ca5775a8d9b24faee8c14e54e79540caab053a" dependencies = [ "futures 0.3.32", "libc", @@ -2531,7 +2542,7 @@ dependencies = [ "chrono", "ed25519-dalek", "fs-mistrust", - "fslock-guard", + "fslock-guard 0.8.0", "futures 0.3.32", "grin_api", "grin_chain", @@ -4004,9 +4015,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "oneshot-fused-workaround" -version = "0.7.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d6f8f565703c8a15f98b7782e39526490f8c291910d80c9be54a753a822f6f" +checksum = "e17b52d0e4a06a4c7eb8d2943c0015fa628cf4ccc409429cebc0f5bed6d33a82" dependencies = [ "futures 0.3.32", ] @@ -5030,9 +5041,9 @@ dependencies = [ [[package]] name = "retry-error" -version = "0.13.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c0e06f9b501df8600ad8bd073f4990f703f36e0f48e574b431f4a15d69b449" +checksum = "cf6aa271ee564cc5d1df57c5cf7c6ac7a21a4f9f40d2bf1d32bf0a1bb3ddaeb0" dependencies = [ "humantime 2.4.0", "web-time", @@ -5343,9 +5354,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safelog" -version = "0.9.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0812d386a9992c036500b92565c41f84c28fd2e48fe853a395c393261b730b" +checksum = "97a907e0d82c61b1b06a2030c968eb313dcf432686b77801a26bc4b206f96573" dependencies = [ "derive_more", "educe", @@ -5389,9 +5400,9 @@ dependencies = [ [[package]] name = "saturating-time" -version = "0.4.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "802bdbfcca9a239cb46eeaaedea507e37bb13ba1a673762d2e2ef7a9dac63144" +checksum = "b63583a1dd0647d1484228529ab4ecaa874048d2956f117362aa5f5826456230" [[package]] name = "schannel" @@ -5798,9 +5809,9 @@ dependencies = [ [[package]] name = "slotmap-careful" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7cd5208f878dce99cfbefa73c692c67edf946ac53e0cc4455e2dabe43d54af7" +checksum = "ed92816c1fbb29891a525b92d5fa95757c9dee47044f76c8e06ceb1e052a8d64" dependencies = [ "paste", "serde", @@ -6464,35 +6475,26 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tor-async-utils" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58580b41636378f79500bca6a178787e0e434207e0880dd90698ae3f686f24d6" +checksum = "27fc83e7539ac5f717b722456df991ca8819782d04f7ae2ee28eefacca030eef" dependencies = [ - "cfg-if 1.0.4", "derive-deftly", "educe", "futures 0.3.32", "oneshot-fused-workaround", "pin-project", "postage", - "sync_wrapper", "thiserror 2.0.18", - "tokio", - "tokio-util", - "tor-basic-utils", - "tor-rtcompat", - "tracing", "void", - "web-time-compat", ] [[package]] name = "tor-basic-utils" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd3046aa299df67da91b45c02c24d48d40ae1845f9b91e27f9c8adbde78315ba" +checksum = "7bd01b9947f57a99193547f3a40a0bd179e9a332ead855c07e2fb702a1517f74" dependencies = [ - "derive-deftly", "derive_more", "getrandom 0.4.3", "hex", @@ -6505,16 +6507,15 @@ dependencies = [ "slab", "smallvec", "thiserror 2.0.18", - "tracing", "weak-table", "web-time-compat", ] [[package]] name = "tor-bytes" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4833cc8ab3fca79eabc06f3ca97db102a3b7db861d77848d39932a6f0ed598" +checksum = "de6a7af55881d599cd48c841916520318d5711df17514001aafd5f796f61e337" dependencies = [ "bytes 1.12.0", "derive-deftly", @@ -6530,9 +6531,9 @@ dependencies = [ [[package]] name = "tor-cell" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73a01cb2ac6697c12b647729d3d7db404e306d1e08a7a3aacdbee08e35170ebd" +checksum = "dad292658d194b145aaec268ffb91566b7d96c98971eea4980c1bf951d7d082b" dependencies = [ "amplify", "bitflags 2.13.0", @@ -6561,15 +6562,14 @@ dependencies = [ [[package]] name = "tor-cert" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162d44b975e445de099b3a495710689652bbd0a9a3a0e4946341acbb3138b22b" +checksum = "474e732207b1be44f9ae4ddf295fba3644cd3c09494d131846ff9c9a624ab263" dependencies = [ "caret", "derive_builder_fork_arti", "derive_more", "digest 0.10.7", - "saturating-time", "thiserror 2.0.18", "tor-bytes", "tor-checkable", @@ -6580,9 +6580,9 @@ dependencies = [ [[package]] name = "tor-chanmgr" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf15c4cbafa5745def4fced9e8e59148533a2bfc8d938e9fad7a0d6d16757712" +checksum = "b2bb088804f54de320ee97dd2a48ae319f0df9d9d0e37eef050d65488194a768" dependencies = [ "async-trait", "base64ct", @@ -6623,9 +6623,9 @@ dependencies = [ [[package]] name = "tor-checkable" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef81835cb0b2db460c694678eae8c4cbad5f31847e473aac42605f9b945fb31a" +checksum = "9505791bac25b31b8de88c0e2b02e1690e10ff1308583c6f61c320bab2677959" dependencies = [ "humantime 2.4.0", "signature", @@ -6636,9 +6636,9 @@ dependencies = [ [[package]] name = "tor-circmgr" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63cca1f18282f82b905b6a8c20c6f247787bc010d1b317d9eb98503e4352bf" +checksum = "7e986c7c4e1faae8a412b575ce66ff087050525a371db1034f0b24d70b459a2e" dependencies = [ "amplify", "async-trait", @@ -6686,9 +6686,9 @@ dependencies = [ [[package]] name = "tor-config" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a699adfb8bdb34699afcca7cbbd9d26329246940bc9dbc32f380c87578017350" +checksum = "224f85aa02b514c384ea5ec32f1b924233bea6b553195fd83da7f1e50a2e0d1b" dependencies = [ "amplify", "cfg-if 1.0.4", @@ -6720,9 +6720,9 @@ dependencies = [ [[package]] name = "tor-config-path" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38001695121fc778e67ac7a1c733d3158ee8942a55cfa343981b50d90f5daa" +checksum = "4232a56a8ce40a47ad6b42c9c7d9e6f4485e6e6a43b3149304c35bc295034107" dependencies = [ "directories", "serde", @@ -6734,9 +6734,9 @@ dependencies = [ [[package]] name = "tor-consdiff" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e9e3eb11e91ea4c1affe5e1f73628726c283942715739850546acb94bbe028" +checksum = "d5fcd0c631a66b45dca4421b8a06309dd02ae5d6aea5daa55399f68638b2abf3" dependencies = [ "derive_more", "digest 0.10.7", @@ -6751,9 +6751,9 @@ dependencies = [ [[package]] name = "tor-dirclient" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0027cd2a58abab9ebaf61c3c1da9b0fa9e6cbc914b88fedb7b3102bd4f4dd656" +checksum = "bf637d6a5d8c886f23ca2df50edfaa56443f23294428f037206fb3a0d07ba57e" dependencies = [ "async-compression", "base64ct", @@ -6780,9 +6780,9 @@ dependencies = [ [[package]] name = "tor-dircommon" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d2047b2f1ad3453c1060e5e39203bd87ead266e6873707bd7333b1d6bdac768" +checksum = "35e412167d54c3618c3261dc77f1a31fe20c518f59aca5abfdec5f600b6a9b1f" dependencies = [ "base64ct", "derive-deftly", @@ -6801,9 +6801,9 @@ dependencies = [ [[package]] name = "tor-dirmgr" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ebc162ccd628ef8e96912a9e42e848cd7305eb2a5a455a6002f54ad6826422" +checksum = "9b984d2b0f0a2764a1b24d7c4c203ef83600b071c0c5af36e20d4f63e80ce562" dependencies = [ "async-trait", "base64ct", @@ -6813,7 +6813,7 @@ dependencies = [ "educe", "event-listener", "fs-mistrust", - "fslock-guard", + "fslock-guard 0.7.0", "futures 0.3.32", "hex", "humantime 2.4.0", @@ -6858,9 +6858,9 @@ dependencies = [ [[package]] name = "tor-error" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a7ed4aee3e0613a75d420032116dac82d7702594e28566befdfa6707a8fd0e" +checksum = "1409b1a9085502c7797405a01fe3f3292a9827c9ed6a3f7c34fbb222572db5ac" dependencies = [ "derive_more", "futures 0.3.32", @@ -6876,9 +6876,9 @@ dependencies = [ [[package]] name = "tor-general-addr" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfee544a33dd3e67e0311cc46ef8f667cbf9c02c7c00ef4f25098a25b2b6e1cf" +checksum = "09f17e219140e587e169f71f53e528ca394c39e83593c9fbe9e9366cce7655e1" dependencies = [ "derive_more", "thiserror 2.0.18", @@ -6887,9 +6887,9 @@ dependencies = [ [[package]] name = "tor-guardmgr" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e8f52e4b1b08ba58970a9b9879b0b72c35dcba48bad389935f2778ed432d05" +checksum = "d56c43d7afa526449c6e6460d373a02624a24c78637ddd7c68e2ac1879ae587c" dependencies = [ "amplify", "base64ct", @@ -6932,9 +6932,9 @@ dependencies = [ [[package]] name = "tor-hsclient" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dc813f71e63fd324c4b1b1647ddc2fcc4b6e77c45091e6769ae80e591726ec" +checksum = "1ff8ba0afebc5f76b42240f76b2bfb92aa5862c19ff184492c59c654750ee275" dependencies = [ "async-trait", "derive-deftly", @@ -6977,9 +6977,9 @@ dependencies = [ [[package]] name = "tor-hscrypto" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82fd6a5a6ede5ece5e7f52708bdf0a007e8f975f9825689b376f038845f1e88" +checksum = "3761c1828e74818cba4eb661d1eb79d4f663b05e8e7d3bc6e35d754968282fc0" dependencies = [ "cipher 0.4.4", "data-encoding", @@ -7010,9 +7010,9 @@ dependencies = [ [[package]] name = "tor-hsrproxy" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4231a55048af49ae2a8fcb2d41ea0df986b68c26bf6128f97f7f1324d47498" +checksum = "cc0d8041225c2721fb68e8406c2121e3168ea3998148df253859fec70ea20741" dependencies = [ "derive-deftly", "futures 0.3.32", @@ -7039,9 +7039,9 @@ dependencies = [ [[package]] name = "tor-hsservice" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170b66c977d3b16a5ff79ec344c06392076f0326d2d65146a58c450c50395dae" +checksum = "5fa87ed8d1ccfe8447b4e65bf0e2dfcab6a8ac9d2eadf7b3f37c102e1c18e9bb" dependencies = [ "amplify", "async-trait", @@ -7098,9 +7098,9 @@ dependencies = [ [[package]] name = "tor-key-forge" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30f83389917e9908c43457c80d0b982ba6dcd3e6ea36a37b29167b7619d9eaa3" +checksum = "86c877b95e0679f151743bc2b706320a29e14bd18d9bcb92918df34948e2e9d2" dependencies = [ "derive-deftly", "derive_more", @@ -7120,9 +7120,9 @@ dependencies = [ [[package]] name = "tor-keymgr" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7c18fb26d112c6022b7d1de558a4045b574fecfa1c504045145d8e1a9dc01a" +checksum = "5f59fbec28e31d688822b4c00a6f7fd94539886baccd3ce1b35dc26699c37873" dependencies = [ "amplify", "arrayvec 0.7.8", @@ -7161,9 +7161,9 @@ dependencies = [ [[package]] name = "tor-linkspec" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "638a849a5d65d4e37adb6a2fa597a5a5c0decb749c17e76d3c44c4aefd696270" +checksum = "0e1cc8d5469c324d1e0761066d868363d3fdccb723b14cdc983a5aef9b67981b" dependencies = [ "base64ct", "by_address", @@ -7188,9 +7188,9 @@ dependencies = [ [[package]] name = "tor-llcrypto" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef0e63e805e9c1da61511a93eca502dc29bcb0b8635e9668ec750b63a1e53a1" +checksum = "d0015d521f5e30a2f74879fe4c615fc2c3ace822d7cc88c1dfed0ae816b25f15" dependencies = [ "aes", "base64ct", @@ -7231,9 +7231,9 @@ dependencies = [ [[package]] name = "tor-log-ratelim" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d0f759a875b6f9b6f3657a2626a70e2474e698a4404501033babe63ad888a6" +checksum = "7d16152b21f2e7b309dfbdcbe587c5adba9547d3119f2933b453d6edbce7e8e0" dependencies = [ "futures 0.3.32", "humantime 2.4.0", @@ -7247,9 +7247,9 @@ dependencies = [ [[package]] name = "tor-memquota" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c06f2e981120a4c1da1e361573cee4af618d05fa3c7ec37c139d766f1b8d47f" +checksum = "38ad2950d88e968572b0de6bb5702f5ed42f36555f362fb741c572c62441476e" dependencies = [ "cfg-if 1.0.4", "derive-deftly", @@ -7278,9 +7278,9 @@ dependencies = [ [[package]] name = "tor-memquota-cost" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86eb903fee638e9211daddaddc146f0c5e4f5605d79983548fce6fec596325c" +checksum = "993ad5bfd39fb629990da40c2b2086284b1481b7d4843652a959db1d9e5ac463" dependencies = [ "derive-deftly", "itertools 0.14.0", @@ -7290,9 +7290,9 @@ dependencies = [ [[package]] name = "tor-netdir" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8160580273eec3576158c3e833ff198870665dacebb373585892dde93cf9803" +checksum = "6700c5e590818d9873a13e06b49b95cf3a39fa8fb3968bd67ec3295cd584b97c" dependencies = [ "async-trait", "bitflags 2.13.0", @@ -7323,9 +7323,9 @@ dependencies = [ [[package]] name = "tor-netdoc" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51598bfd24866de2c51e0de7a6e2418f86c6278cfeba2f85295ce45367fd8e06" +checksum = "a497ce4c36f59e4c67c352e3b80cd4642f6c5e660bb003de1922be4c7ed03081" dependencies = [ "amplify", "base64ct", @@ -7339,7 +7339,6 @@ dependencies = [ "hex", "hostname-validator", "humantime 2.4.0", - "ipnet", "itertools 0.14.0", "memchr", "paste", @@ -7373,16 +7372,16 @@ dependencies = [ [[package]] name = "tor-persist" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ff75e20adb6a1a7ad07e16b55e56d5376706cc0090ab8831f008f1a89243f2" +checksum = "b6da1a96a1f6418faac1cd1892e029fd265cf50e67e941a4ba2580d92dd32d47" dependencies = [ "amplify", "derive-deftly", "derive_more", "filetime", "fs-mistrust", - "fslock-guard", + "fslock-guard 0.7.0", "futures 0.3.32", "itertools 0.14.0", "oneshot-fused-workaround", @@ -7402,9 +7401,9 @@ dependencies = [ [[package]] name = "tor-proto" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b602da8ba5a4189d6ff551f5dd9db949b503780140d175435658970a1ea54" +checksum = "a0a5fa0e1a365fc089a7f140f4142379dd74c91c15103c2f9d11ea711e16029e" dependencies = [ "amplify", "async-trait", @@ -7438,6 +7437,7 @@ dependencies = [ "static_assertions", "strum 0.28.0", "subtle", + "sync_wrapper", "thiserror 2.0.18", "tokio", "tokio-util", @@ -7468,9 +7468,9 @@ dependencies = [ [[package]] name = "tor-protover" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef84569d020c36eb322976f9a5dec1d3fa992db9739237ad1ae923b0ddeb298" +checksum = "c4f7f4ee15c9bfa0b700ae06e85fa52abddf17a35531cd3d3833412900e8da5c" dependencies = [ "caret", "paste", @@ -7482,9 +7482,9 @@ dependencies = [ [[package]] name = "tor-ptmgr" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66db88ee6c5ba60a077b592b7e566d465220de61ccfd473fba6567c577cad475" +checksum = "b4cc37ac22faff9e0233fa287b92819d8819f31cae85a50ff99c51dd549b31c0" dependencies = [ "async-trait", "cfg-if 1.0.4", @@ -7512,9 +7512,9 @@ dependencies = [ [[package]] name = "tor-relay-selection" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09778e3a45fdf49ca335fa16a0032abc5d4e1cacff9f46c3ed3e2a416d088aa6" +checksum = "5b565b6dec2e3edb7b033203a3662edb7b586e486d53e02af4295e05e7562870" dependencies = [ "rand 0.10.2", "serde", @@ -7526,9 +7526,9 @@ dependencies = [ [[package]] name = "tor-rtcompat" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "981279291476ad7ec309c874fa99b8d31250432a48ff6eea3f7405edbbd254e1" +checksum = "fcd7a4ff4898044b3ec693c0bf098251accf13c58741c44ed9364ce5faa89287" dependencies = [ "amplify", "async-native-tls", @@ -7561,9 +7561,9 @@ dependencies = [ [[package]] name = "tor-rtmock" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3ee9695304cc44cc0de1dc673c9a71419318e214dc04ed7883a058d050654d" +checksum = "8331e7d57bf7cf157cf3610ff6dbb5a591de5c3b22fa65ea6be18b08ff81477a" dependencies = [ "amplify", "assert_matches", @@ -7591,9 +7591,9 @@ dependencies = [ [[package]] name = "tor-socksproto" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf78ef55dbfccb5a95cd75eb433621947923653cb91c9941d5a0a5116e510ee" +checksum = "55f432eda94343bdadd73995f0b3082d96637dadb0e20c3f9a1bf2349f623d3c" dependencies = [ "amplify", "caret", @@ -7608,9 +7608,9 @@ dependencies = [ [[package]] name = "tor-units" -version = "0.44.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0325a93f3b35c5a9bda74705ce89b753e1c8e183af6945826188cef16687d80" +checksum = "8cf033ee8b22b11abd8272272e19ccbbf58c7b3152f8d224aa8d21f358c43d0d" dependencies = [ "derive-deftly", "derive_more", @@ -8099,9 +8099,9 @@ dependencies = [ [[package]] name = "web-time-compat" -version = "0.2.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af46dad42aa37be3b4a116fe613365f991b8bdae6cd6ce82d84fa9f9e8f70ae4" +checksum = "39819265f219f60a92312f2755262dba9fff180a4ec281556863d69fa36adc59" dependencies = [ "web-time", ] diff --git a/impls/Cargo.toml b/impls/Cargo.toml index bb4add2..b93b32f 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -32,15 +32,15 @@ sysinfo = "0.29" base64 = "0.12.0" url = "2.1" -arti-client = { version = "0.44.0", features = ["static", "pt-client", "onion-service-service", "onion-service-client"] } -tor-rtcompat = { version = "0.44.0", features = ["static"] } -fs-mistrust = "0.15.0" +arti-client = { version = "0.43.0", features = ["static", "pt-client", "onion-service-service", "onion-service-client"] } +tor-rtcompat = { version = "0.43.0", features = ["static"] } +fs-mistrust = "0.14.2" fslock-guard = "0.8" -tor-hsservice = "0.44.0" -tor-hsrproxy = "0.44.0" -tor-keymgr = "0.44.0" -tor-llcrypto = "0.44.0" -tor-hscrypto = "0.44.0" +tor-hsservice = "0.43.0" +tor-hsrproxy = "0.43.0" +tor-keymgr = "0.43.0" +tor-llcrypto = "0.43.0" +tor-hscrypto = "0.43.0" sha2 = "0.10.8" hyper = { version = "1.10.1", features = ["http1", "client"] } hyper-util = { version = "0.1.20", features = ["tokio"] } From 90d81a856a31c1b30f71c23889867f878e7b941f Mon Sep 17 00:00:00 2001 From: wiesche <120273695+wiesche89@users.noreply.github.com> Date: Mon, 21 Sep 2026 20:41:34 +0200 Subject: [PATCH 10/18] reject zero send amounts (#799) (cherry picked from commit 4a487390bdbcb9ba9176c689cef5bdc1e99c1714) --- controller/src/command.rs | 9 ++++-- controller/tests/invoice.rs | 19 ++++++++++++ controller/tests/transaction.rs | 48 ++++++++++++++++++++++++++++- libwallet/src/api_impl/foreign.rs | 4 +++ libwallet/src/api_impl/owner.rs | 12 ++++++++ libwallet/src/error.rs | 4 +++ libwallet/src/internal/selection.rs | 17 +++++----- tests/cmd_line_basic.rs | 5 +++ 8 files changed, 104 insertions(+), 14 deletions(-) diff --git a/controller/src/command.rs b/controller/src/command.rs index b645892..daeb256 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -372,12 +372,15 @@ where true, args.minimum_confirmations, )?; - if args.use_max_amount { - amount = wallet_info.amount_currently_spendable; - } if !info_updated && !update_skipped { warn!("Wallet info update failed: node connection error"); } + if args.use_max_amount { + amount = wallet_info.amount_currently_spendable; + if amount == 0 { + return Err(Error::GenericError("No spendable funds".to_string())); + } + } if args.estimate_selection_strategies { let strategies = estimate_strategies(args.use_max_amount) .iter() diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 006d5d4..b48f66e 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -113,6 +113,17 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { mask2, PathBuf::from(test_dir), |api, m| { + let error = api + .issue_invoice_tx( + m, + IssueInvoiceTxArgs { + amount: 0, + ..Default::default() + }, + ) + .unwrap_err(); + assert_eq!(error, libwallet::Error::InvalidAmount); + // Wallet 2 inititates an invoice transaction, requesting payment let args = IssueInvoiceTxArgs { amount: reward * 2, @@ -177,6 +188,14 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { selection_strategy_is_use_all: true, ..Default::default() }; + let mut zero_amount_slate = slate.clone(); + zero_amount_slate.amount = 0; + assert_eq!( + api.process_invoice_tx(m, &zero_amount_slate, args.clone()) + .unwrap_err(), + libwallet::Error::InvalidAmount + ); + slate = api.process_invoice_tx(m, &slate, args)?; api.tx_lock_outputs(m, &slate)?; Ok(()) diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 5425ef8..107c17d 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -112,10 +112,41 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> mask1, PathBuf::from(test_dir), |sender_api, m| { + // Do not allow to send 0 + let error = sender_api + .init_send_tx( + m, + InitTxArgs { + amount: 0, + ..Default::default() + }, + ) + .unwrap_err(); + assert_eq!(error, libwallet::Error::InvalidAmount); + + // Result amount can not be 0 when amount includes fee + let mut init_args = InitTxArgs { + amount_includes_fee: Some(true), + amount: 23_000_000, + estimate_only: Some(true), + ..Default::default() + }; + let est = sender_api.init_send_tx(m, init_args.clone())?; + assert_eq!(init_args.amount, est.fee_fields.fee()); + + init_args.estimate_only = None; + let error = sender_api.init_send_tx(m, init_args).unwrap_err(); + assert_eq!( + error, + libwallet::Error::GenericError( + "Transaction amount is too small to include fee".to_string() + ) + ); + // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { src_acct_name: None, - amount: amount, + amount, minimum_confirmations: 2, max_outputs: 500, num_change_outputs: 1, @@ -127,6 +158,21 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> assert_eq!(slate_i.state, SlateState::Standard1); assert_eq!(slate_i.version_info.block_header_version, header_version); + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + let mut zero_amount_slate = slate_i.clone(); + zero_amount_slate.amount = 0; + assert_eq!( + api.receive_tx(&zero_amount_slate, None, None).unwrap_err(), + libwallet::Error::InvalidAmount + ); + Ok(()) + }, + )?; + // Check we are creating a tx with the expected lock_height of 0. // We will check this produces a Plain kernel later. assert_eq!(0, slate.kernel_features); diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index 4547b87..5c63a52 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -65,6 +65,10 @@ where C: NodeClient, K: Keychain, { + if slate.amount == 0 { + return Err(Error::InvalidAmount); + } + let mut ret_slate = slate.clone(); if ret_slate.state != SlateState::Standard1 { return Err(Error::SlateState); diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 44aecae..58bfbd2 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -534,6 +534,10 @@ where C: NodeClient, K: Keychain, { + if args.amount == 0 { + return Err(Error::InvalidAmount); + } + let payment_proof_address = if let Some(a) = &args.payment_proof_recipient_address { if a.valid_network() { Some(a) @@ -657,6 +661,10 @@ where C: NodeClient, K: Keychain, { + if args.amount == 0 { + return Err(Error::InvalidAmount); + } + let parent_key_id = match args.dest_acct_name { Some(d) => { let pm = w.get_acct_path(d)?; @@ -710,6 +718,10 @@ where C: NodeClient, K: Keychain, { + if slate.amount == 0 { + return Err(Error::InvalidAmount); + } + let mut ret_slate = slate.clone(); check_ttl(w, &ret_slate)?; let parent_key_id = match args.src_acct_name { diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index 1c772bf..5300dd1 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -24,6 +24,10 @@ use grin_store; /// Wallet errors, mostly wrappers around underlying crypto or I/O errors. #[derive(Clone, Eq, PartialEq, Debug, thiserror::Error, Serialize, Deserialize)] pub enum Error { + /// Invalid transaction amount + #[error("Invalid amount (must be greater than zero)")] + InvalidAmount, + /// Not enough funds #[error("Not enough funds. Required: {needed_disp:?}, Available: {available_disp:?}")] NotEnoughFunds { diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index ac17dbd..bac6d92 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -73,9 +73,12 @@ where false, )?; if amount_includes_fee { - slate.amount = slate.amount.checked_sub(fee).ok_or(Error::GenericError( - "Transaction amount is too small to include fee".to_string(), - ))?; + let small_amount_err = + Error::GenericError("Transaction amount is too small to include fee".to_string()); + if slate.amount == fee { + return Err(small_amount_err); + } + slate.amount = slate.amount.checked_sub(fee).ok_or(small_amount_err)?; }; if fixed_fee.map(|f| fee != f).unwrap_or(false) { @@ -104,15 +107,9 @@ where context.add_input(&input.key_id, &input.mmr_index, input.value); } - let mut commits: HashMap> = HashMap::new(); - - // Store change output(s) and cached commits + // Store change output(s) for (change_amount, id, mmr_index) in &change_amounts_derivations { context.add_output(&id, &mmr_index, *change_amount); - commits.insert( - id.clone(), - wallet.calc_commit_for_cache(keychain_mask, *change_amount, &id)?, - ); } Ok(context) diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index 86f7675..a4d8308 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -147,6 +147,11 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let arg_vec = vec!["grin-wallet", "-p", "password2", "account"]; execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + // Return an explained error when trying to send max amount on empty wallet. + let arg_vec = vec!["grin-wallet", "-p", "password2", "send", "max"]; + let err = execute_command(&app, test_dir, "wallet2", &client2, arg_vec).unwrap_err(); + assert!(format!("{}", err).contains("No spendable funds")); + // Mine a bit into wallet 1 so we have something to send // (TODO: Be able to stop listeners so we can test this better) let wallet_config1 = config1.clone().members.wallet; From 50caa5ecc4c09b4caed3148cce9dbfcfdb0a8bdd Mon Sep 17 00:00:00 2001 From: wiesche <120273695+wiesche89@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:14:04 +0200 Subject: [PATCH 11/18] avoid empty wallet seeds (#801) * fix empty wallet seeds * mnemonic: return actual error with entropy size on recovering * seed: extend mnemonic error description * seed: clarify mnemonic errors * seed: clarify mnemonic errors, fix missing phrase test * Clean up seed handling add test --------- Co-authored-by: ardocrat (cherry picked from commit 51ce8e2f2a2698e988cb84edfdbf8e4da69c9617) --- api/src/owner.rs | 6 +- impls/src/error.rs | 4 +- impls/src/lifecycle/default.rs | 108 +++++++++++++++++++++++++++++++-- impls/src/lifecycle/seed.rs | 36 +++++++---- libwallet/src/types.rs | 2 +- 5 files changed, 134 insertions(+), 22 deletions(-) diff --git a/api/src/owner.rs b/api/src/owner.rs index 7ca6b5d..1296db8 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -1726,8 +1726,10 @@ where /// * `name`: Reserved for future use, use `None` for the time being. /// * `mnemonic`: If present, restore the wallet seed from the given mnemonic instead of creating /// a new random seed. - /// * `mnemonic_length`: Desired length of mnemonic in bytes (16 or 32, either 12 or 24 words). - /// Use 0 if mnemonic isn't being used. + /// * `mnemonic_length`: Entropy length in bytes: 16, 20, 24, 28 or 32 + /// (12, 15, 18, 21 or 24 words). Ignored when `mnemonic` is provided. + /// For a new random seed, 0 is also accepted and defaults to 32 bytes. + /// All other values return an error. /// * `password`: The password used to encrypt/decrypt the `wallet.seed` file /// /// # Returns diff --git a/impls/src/error.rs b/impls/src/error.rs index 4169e68..a22cb2a 100644 --- a/impls/src/error.rs +++ b/impls/src/error.rs @@ -75,8 +75,8 @@ pub enum Error { Encryption, /// BIP 39 word list - #[error("BIP39 Mnemonic (word list) Error")] - Mnemonic, + #[error("BIP39 Mnemonic (word list) Error: {}", _0)] + Mnemonic(String), /// Command line argument error #[error("{}", _0)] diff --git a/impls/src/lifecycle/default.rs b/impls/src/lifecycle/default.rs index 7514a30..ca11687 100644 --- a/impls/src/lifecycle/default.rs +++ b/impls/src/lifecycle/default.rs @@ -182,7 +182,7 @@ where &mut self, _name: Option<&str>, mnemonic: Option, - mnemonic_length: usize, + entropy_size: usize, password: ZeroingString, test_mode: bool, ) -> Result<(), Error> { @@ -196,16 +196,15 @@ where return Err(Error::WalletSeedExists(msg)); } } + let entropy_size = if entropy_size == 0 { 32 } else { entropy_size }; WalletSeed::init_file( &data_dir_name, - mnemonic_length, + entropy_size, mnemonic.clone(), password, test_mode, ) - .map_err(|_| { - Error::Lifecycle("Error creating wallet seed (is mnemonic valid?)".to_owned()) - })?; + .map_err(|e| Error::Lifecycle(format!("Error creating wallet seed: {}", e)))?; info!("Wallet seed file created"); let mut wallet: WalletBackend = match WalletBackend::new(&data_dir_name, self.node_client.clone()) { @@ -248,6 +247,9 @@ where let wallet_seed = WalletSeed::from_file(&data_dir_name, password).map_err(|_| { Error::Lifecycle("Error opening wallet (is password correct?)".to_owned()) })?; + if wallet_seed.is_empty() { + warn!("This wallet has an empty seed and its funds are unsafe. Move them to a new wallet."); + } let keychain = wallet_seed .derive_keychain(global::is_testnet()) .map_err(|_| Error::Lifecycle("Error deriving keychain".to_owned()))?; @@ -286,7 +288,7 @@ where .map_err(|_| Error::Lifecycle("Error opening wallet seed file".into()))?; let res = wallet_seed .to_mnemonic() - .map_err(|_| Error::Lifecycle("Error recovering wallet seed".into()))?; + .map_err(|e| Error::Lifecycle(format!("Error recovering wallet seed: {}. This wallet has no valid recovery phrase and its funds may be unsafe. Move them to a new wallet.", e)))?; Ok(ZeroingString::from(res)) } @@ -382,3 +384,97 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::keychain::ExtKeychain; + use crate::lifecycle::seed::{EncryptedWalletSeed, SEED_FILE}; + use crate::node_clients::HTTPNodeClient; + use std::fs::File; + use std::io::Write; + use std::time::Duration; + + fn provider(test_dir: &str) -> DefaultLCProvider { + let client = + HTTPNodeClient::new("http://127.0.0.1:1", None, Duration::from_secs(1)).unwrap(); + let mut provider = DefaultLCProvider::new(client); + provider.set_top_level_directory(test_dir).unwrap(); + provider + } + + #[test] + fn default_recovery_phrase() { + let test_dir = "test_output/default_recovery_phrase"; + let _ = fs::remove_dir_all(test_dir); + global::set_local_chain_type(global::ChainTypes::AutomatedTesting); + let mut provider = provider(test_dir); + let password = ZeroingString::from("test"); + + provider + .create_wallet(None, None, 0, password.clone(), false) + .unwrap(); + let mnemonic = provider.get_mnemonic(None, password).unwrap(); + + assert_eq!(mnemonic.split_whitespace().count(), 24); + fs::remove_dir_all(test_dir).unwrap(); + } + + #[test] + fn mnemonic_error_redacted() { + let test_dir = "test_output/mnemonic_error_redacted"; + let _ = fs::remove_dir_all(test_dir); + let mut provider = provider(test_dir); + let input = ZeroingString::from("INVALID_INPUT_MARKER ".repeat(12)); + let error = provider + .create_wallet(None, Some(input), 32, ZeroingString::from("test"), false) + .unwrap_err() + .to_string(); + + assert!(error.contains("invalid bip39 word")); + assert!(!error.contains("INVALID_INPUT_MARKER")); + assert!(!provider.wallet_exists(None).unwrap()); + fs::remove_dir_all(test_dir).unwrap(); + } + + #[test] + fn missing_recovery_phrase() { + let test_dir = "test_output/missing_recovery_phrase"; + let _ = fs::remove_dir_all(test_dir); + global::set_local_chain_type(global::ChainTypes::AutomatedTesting); + let mut provider = provider(test_dir); + let password = ZeroingString::from("test"); + let data_dir = PathBuf::from(test_dir).join(GRIN_WALLET_DIR); + fs::create_dir_all(data_dir.clone()).unwrap(); + + let seed_file_path = &format!( + "{}{}{}", + fmt_path(data_dir.to_str().unwrap().to_string()), + MAIN_SEPARATOR, + SEED_FILE, + ); + + let enc_seed = EncryptedWalletSeed::from_seed( + &WalletSeed::from_bytes(vec![].as_slice()), + password.clone(), + ) + .unwrap(); + let enc_seed_json = serde_json::to_string_pretty(&enc_seed).unwrap(); + let mut file = File::create(seed_file_path).unwrap(); + file.write_all(&enc_seed_json.as_bytes()).unwrap(); + + let error = match provider.get_mnemonic(None, password.clone()) { + Ok(_) => panic!("expected missing recovery phrase error"), + Err(error) => error, + }; + + assert_eq!( + error, + Error::Lifecycle("Error recovering wallet seed: BIP39 Mnemonic (word list) Error: invalid mnemonic/entropy length 0. This wallet has no valid recovery phrase and its funds may be unsafe. Move them to a new wallet.".into()) + ); + provider.open_wallet(None, password, false, false).unwrap(); + provider.close_wallet(None).unwrap(); + assert_eq!(fs::read_to_string(seed_file_path).unwrap(), enc_seed_json); + fs::remove_dir_all(test_dir).unwrap(); + } +} diff --git a/impls/src/lifecycle/seed.rs b/impls/src/lifecycle/seed.rs index 5dbbd6c..16f924a 100644 --- a/impls/src/lifecycle/seed.rs +++ b/impls/src/lifecycle/seed.rs @@ -39,11 +39,18 @@ impl WalletSeed { WalletSeed(bytes.to_vec()) } + pub(super) fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn from_mnemonic(word_list: util::ZeroingString) -> Result { let res = mnemonic::to_entropy(&word_list); match res { Ok(s) => Ok(WalletSeed::from_bytes(&s)), - Err(_) => Err(Error::Mnemonic.into()), + Err(mnemonic::Error::BadWord(_)) => { + Err(Error::Mnemonic("invalid bip39 word".to_owned())) + } + Err(e) => Err(Error::Mnemonic(format!("{}", e))), } } @@ -61,7 +68,7 @@ impl WalletSeed { let result = mnemonic::from_entropy(&self.0); match result { Ok(r) => Ok(r), - Err(_) => Err(Error::Mnemonic.into()), + Err(e) => Err(Error::Mnemonic(format!("{}", e))), } } @@ -76,14 +83,14 @@ impl WalletSeed { } pub fn init_new( - seed_length: usize, + entropy_size: usize, test_mode: bool, password: Option, - ) -> WalletSeed { + ) -> Result { let mut seed: Vec = vec![]; let mut rng = thread_rng(); if !test_mode { - for _ in 0..seed_length { + for _ in 0..entropy_size { seed.push(rng.gen()); } } else { @@ -93,7 +100,10 @@ impl WalletSeed { .as_bytes() .to_vec(); } - WalletSeed(seed) + let _ = mnemonic::from_entropy(seed.as_slice()) + .map(util::ZeroingString::from) + .map_err(|e| Error::Mnemonic(format!("{}", e)))?; + Ok(WalletSeed(seed)) } pub fn seed_file_exists(data_file_dir: &str) -> Result { @@ -168,7 +178,7 @@ impl WalletSeed { pub fn init_file( data_file_dir: &str, - seed_length: usize, + entropy_size: usize, recovery_phrase: Option, password: util::ZeroingString, test_mode: bool, @@ -193,7 +203,7 @@ impl WalletSeed { let seed = match recovery_phrase { Some(p) => WalletSeed::from_mnemonic(p)?, - None => WalletSeed::init_new(seed_length, test_mode, Some(password.clone())), + None => WalletSeed::init_new(entropy_size, test_mode, Some(password.clone()))?, }; let enc_seed = EncryptedWalletSeed::from_seed(&seed, password)?; @@ -277,7 +287,7 @@ impl EncryptedWalletSeed { let password = password.as_bytes(); let mut key = [0; 32]; pbkdf2::derive( - ring::pbkdf2::PBKDF2_HMAC_SHA512, + pbkdf2::PBKDF2_HMAC_SHA512, NonZeroU32::new(100).unwrap(), &salt, password, @@ -325,7 +335,7 @@ impl EncryptedWalletSeed { let password = password.as_bytes(); let mut key = [0; 32]; pbkdf2::derive( - ring::pbkdf2::PBKDF2_HMAC_SHA512, + pbkdf2::PBKDF2_HMAC_SHA512, NonZeroU32::new(100).unwrap(), &salt, password, @@ -359,8 +369,12 @@ mod tests { use crate::util::ZeroingString; #[test] fn wallet_seed_encrypt() { + // Wrong entropy size. + let wrong_entropy_size = WalletSeed::init_new(31, false, None).err(); + assert!(wrong_entropy_size.is_some()); + let password = ZeroingString::from("passwoid"); - let wallet_seed = WalletSeed::init_new(32, false, None); + let wallet_seed = WalletSeed::init_new(32, false, None).unwrap(); let mut enc_wallet_seed = EncryptedWalletSeed::from_seed(&wallet_seed, password.clone()).unwrap(); println!("EWS: {:?}", enc_wallet_seed); diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index ae86f1b..0aec740 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -81,7 +81,7 @@ where &mut self, name: Option<&str>, mnemonic: Option, - mnemonic_length: usize, + entropy_size: usize, password: ZeroingString, test_mode: bool, ) -> Result<(), Error>; From cec94917de9b3c10ac2f0bcf58be149c65455d71 Mon Sep 17 00:00:00 2001 From: wiesche <120273695+wiesche89@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:11:32 +0200 Subject: [PATCH 12/18] Show HTTP status when node requests fail (#820) (cherry picked from commit ec1e9910afe4c5852e0f0e614c88f4a20140f3d8) --- impls/src/client_utils/client.rs | 62 +++++++++++++++++++++++++++++--- impls/src/node_clients/http.rs | 19 ++++------ 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/impls/src/client_utils/client.rs b/impls/src/client_utils/client.rs index 94b7b37..20e787f 100644 --- a/impls/src/client_utils/client.rs +++ b/impls/src/client_utils/client.rs @@ -225,7 +225,7 @@ impl Client { IN: Serialize, { let json = serde_json::to_string(input) - .map_err(|_| Error::Internal("Could not serialize data to JSON".to_owned()))?; + .map_err(|e| Error::Internal(format!("Could not serialize data to JSON: {}", e)))?; self.build_request(url, Method::POST, api_secret, Some(json)) } @@ -235,7 +235,7 @@ impl Client { { let data = self.send_request(req)?; serde_json::from_str(&data) - .map_err(|_| Error::ResponseError("Cannot parse response".to_owned())) + .map_err(|e| Error::ResponseError(format!("Cannot parse response: {}", e))) } async fn handle_request_async(&self, req: RequestBuilder) -> Result @@ -244,7 +244,7 @@ impl Client { { let data = self.send_request_async(req).await?; let ser = serde_json::from_str(&data) - .map_err(|_| Error::ResponseError("Cannot parse response".to_owned()))?; + .map_err(|e| Error::ResponseError(format!("Cannot parse response: {}", e)))?; Ok(ser) } @@ -253,6 +253,10 @@ impl Client { .send() .await .map_err(|e| Error::RequestError(format!("Cannot make request: {}", e)))?; + let status = resp.status(); + if status.is_client_error() || status.is_server_error() { + return Err(Error::ResponseError(format!("HTTP {}", status))); + } let text = resp .text() .await @@ -261,7 +265,7 @@ impl Client { } pub fn send_request(&self, req: RequestBuilder) -> Result { - // This client is currently used both outside and inside of a tokio runtime + // This client is currently used both outside and inside the tokio runtime // context. In the latter case we are not allowed to do a blocking call to // our global runtime, which unfortunately means we have to spawn a new thread if Handle::try_current().is_ok() { @@ -275,3 +279,53 @@ impl Client { } } } + +#[cfg(test)] +mod tests { + use super::*; + use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; + use tokio::net::TcpListener; + + #[tokio::test] + async fn http_status() { + let client = Client { + client: ClientBuilder::new() + .no_proxy() + .timeout(Duration::from_secs(5)) + .build() + .unwrap(), + }; + for (status, body, expected) in [ + ("200 OK", "{}", Ok(serde_json::json!({}))), + ( + "401 Unauthorized", + "Unauthorized", + Err(Error::ResponseError("HTTP 401 Unauthorized".into())), + ), + ] { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = format!("http://{}", listener.local_addr().unwrap()); + let server = tokio::spawn(async move { + let (mut stream, _) = listener.accept().await.unwrap(); + let mut headers = BufReader::new(&mut stream).lines(); + while let Some(line) = headers.next_line().await.unwrap() { + if line.is_empty() { + break; + } + } + let response = format!( + "HTTP/1.1 {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", + status, + body.len(), + body + ); + stream.write_all(response.as_bytes()).await.unwrap(); + }); + assert_eq!( + client._get_async::(&url, None).await, + expected + ); + server.await.unwrap(); + } + } +} diff --git a/impls/src/node_clients/http.rs b/impls/src/node_clients/http.rs index 8b7ed0e..b28a30c 100644 --- a/impls/src/node_clients/http.rs +++ b/impls/src/node_clients/http.rs @@ -142,19 +142,12 @@ impl NodeClient for HTTPNodeClient { }, Err(e) => { // If node isn't available, allow offline functions - // unfortunately have to parse string due to error structure - let err_string = format!("{}", e); - return if err_string.contains("404") { - Some(NodeVersionInfo { - node_version: "1.0.0".into(), - block_header_version: 1, - verified: Some(false), - }) - } else { - error!("Unable to contact Node to get version info: {}, check your node is running", e); - warn!("Warning: a) Node is offline, or b) 'node_api_secret_path' in 'grin-wallet.toml' is set incorrectly"); - None - }; + error!( + "Unable to contact Node to get version info: {}, check your node is running", + e + ); + warn!("Warning: a) Node is offline, or b) 'node_api_secret_path' in 'grin-wallet.toml' is set incorrectly"); + return None; } }; self.node_version_info = Some(retval.clone()); From 2302135709df5f8f9a0d5aeeee7b7807258275e3 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 16 Sep 2026 15:53:57 +0300 Subject: [PATCH 13/18] Lock outputs before sending with Tor (#814) Co-authored-by: wiesche (cherry picked from commit 452a3ec51b302091e12fc1a7b2eb24c0ac2625b0) --- api/src/owner.rs | 73 +- controller/src/command.rs | 34 +- controller/src/controller.rs | 17 - controller/tests/accounts.rs | 294 +++---- controller/tests/build_chain.rs | 91 +-- controller/tests/build_output.rs | 35 +- controller/tests/check.rs | 648 +++++---------- controller/tests/common/mod.rs | 35 +- controller/tests/config_api.rs | 18 +- controller/tests/file.rs | 265 ++---- controller/tests/invoice.rs | 397 ++++----- controller/tests/late_lock.rs | 145 ++-- controller/tests/mwixnet.rs | 168 ++-- controller/tests/no_change.rs | 214 ++--- controller/tests/payment_proofs.rs | 136 ++-- controller/tests/repost.rs | 232 ++---- controller/tests/revert.rs | 289 +++---- controller/tests/self_send.rs | 103 +-- controller/tests/slatepack.rs | 512 +++++------- controller/tests/transaction.rs | 1108 ++++++++++++-------------- controller/tests/ttl_cutoff.rs | 155 ++-- controller/tests/tx_list_filter.rs | 434 +++++----- controller/tests/updater_thread.rs | 31 +- impls/src/adapters/tor.rs | 2 +- libwallet/src/mwixnet/onion/onion.rs | 2 +- tests/cmd_line_basic.rs | 248 +++--- tests/owner_v3_lifecycle.rs | 41 +- 27 files changed, 2275 insertions(+), 3452 deletions(-) diff --git a/api/src/owner.rs b/api/src/owner.rs index 1296db8..c180934 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -40,6 +40,7 @@ use grin_wallet_util::OnionV3Address; use chrono::prelude::*; use ed25519_dalek::SigningKey as DalekSecretKey; +use libwallet::wallet_lock; use std::convert::TryFrom; use std::fs::File; use std::io::Write; @@ -709,9 +710,9 @@ where /// is confirmed. This method also returns a function that will perform that locking, and it is /// up to the caller to decide the best time to call the lock function /// (via the [`tx_lock_outputs`](struct.Owner.html#method.tx_lock_outputs) method). - /// If the exchange method is intended to be synchronous (such as via a direct http call,) + /// If the exchange method is intended to be synchronous (such as via a direct http call) /// then the lock call can wait until the response is confirmed. If it is asynchronous, (such - /// as via file transfer,) the lock call should happen immediately (before the file is sent + /// as via file transfer) the lock call should happen immediately (before the file is sent /// to the recipient). /// /// If the `send_args` [`InitTxSendArgs`](../grin_wallet_libwallet/types/struct.InitTxSendArgs.html), @@ -719,7 +720,7 @@ where /// function will attempt to send the slate back to the sender using the slatepack sync /// send (TOR). If providing this argument, check the `state` field of the slate to see if the /// sync_send was successful (it should be S2 if the sync sent successfully). It will also post - /// the transction if the `post_tx` field is set. + /// the transaction if the `post_tx` field is set. /// /// # Arguments /// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if @@ -730,11 +731,15 @@ where /// # Returns /// * a result containing: /// * The transaction [Slate](../grin_wallet_libwallet/slate/struct.Slate.html), - /// which can be forwarded to the recieving party by any means. Once the caller is relatively - /// certain that the transaction has been sent to the recipient, the associated wallet - /// transaction outputs should be locked via a call to - /// [`tx_lock_outputs`](struct.Owner.html#method.tx_lock_outputs). This must be called before calling - /// [`finalize_tx`](struct.Owner.html#method.finalize_tx). + /// which can be forwarded to the receiving party by any means. With normal locking, an + /// attempted Tor send already locks the outputs, even if sending fails. Do not call + /// [`tx_lock_outputs`](struct.Owner.html#method.tx_lock_outputs) again when forwarding manually. + /// If sending was not requested or was skipped, lock the outputs before forwarding the slate. + /// With late locking, outputs are locked during finalization instead. + /// If `skip_tor` is omitted, `skip_send_attempt` in the Tor config decides whether to send. + /// With normal locking, a returned S1 slate alone does not tell you whether outputs are locked. + /// Set `skip_tor` explicitly or check [`retrieve_txs`](struct.Owner.html#method.retrieve_txs) + /// for the slate ID before locking manually. /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. /// /// # Remarks @@ -768,7 +773,7 @@ where /// if let Ok(slate) = result { /// // Send slate somehow /// // ... - /// // Lock our outputs if we're happy the slate was (or is being) sent + /// // Lock outputs for manual sending without late locking /// api_owner.tx_lock_outputs(None, &slate); /// } /// ``` @@ -785,32 +790,36 @@ where } _ => None, }; - let (slate, tor_config) = { - let mut w_lock = self.wallet_inst.lock(); - let tor_config = send_args - .as_ref() - .map(|_| crate::tor_config::load(&self.config_path())) - .transpose()?; - let w = w_lock.lc_provider()?.wallet_inst()?; - let slate = owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)?; - (slate, tor_config) - }; + + let tor_config = send_args + .as_ref() + .map(|_| crate::tor_config::load(&self.config_path())) + .transpose()?; + // Helper functionality. If send arguments exist, attempt to send sync and // finalize match send_args { Some(sa) => { - let tc = tor_config.ok_or_else(|| { - Error::TorConfig("Tor config was not loaded with send arguments".into()) - })?; - let can_send = tc.send_tor(sa.skip_tor); - if self.doctest_mode || !can_send || dest.is_none() { - return Ok(slate); - } + let slate = { + wallet_lock!(self.wallet_inst, w); + let late_lock = args.late_lock.unwrap_or(false); + let slate = owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)?; + let tc = tor_config.as_ref().ok_or_else(|| { + Error::TorConfig("Tor config was not loaded with send arguments".into()) + })?; + let can_send = tc.send_tor(sa.skip_tor); + if self.doctest_mode || !can_send || dest.is_none() { + return Ok(slate); + } + if !late_lock { + owner::tx_lock_outputs(w, keychain_mask, &slate)?; + } + slate + }; let res = - try_slatepack_sync_workflow(&slate, &dest.unwrap(), Some(tc), None, false); + try_slatepack_sync_workflow(&slate, &dest.unwrap(), tor_config, None, false); match res { Ok(s) => { - self.tx_lock_outputs(keychain_mask, &s)?; let ret_slate = self.finalize_tx(keychain_mask, &s)?; if sa.post_tx { let result = self.post_tx(keychain_mask, &ret_slate, sa.fluff); @@ -834,7 +843,11 @@ where } } } - None => Ok(slate), + None => { + wallet_lock!(self.wallet_inst, w); + let slate = owner::init_send_tx(w, keychain_mask, args, self.doctest_mode)?; + Ok(slate) + } } } @@ -2353,7 +2366,7 @@ where /// /// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if /// * `slatepack` - A string representing an armored slatepack - /// * `secret_indices` - Indices along this wallet's deriviation path with which to attempt + /// * `secret_indices` - Indices along this wallet's derivation path with which to attempt /// decryption. This function will attempt to use secret keys at each index along this path /// to attempt to decrypt the payload, returning an error if none of the keys match. /// diff --git a/controller/src/command.rs b/controller/src/command.rs index daeb256..97b59d5 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -439,7 +439,6 @@ where target_slate_version: args.target_slate_version, payment_proof_recipient_address, ttl_blocks: args.ttl_blocks, - send_args: None, late_lock: Some(args.late_lock), ..Default::default() }; @@ -494,14 +493,14 @@ where tor_config.bridge.bridge_line = Some(b); } - let output_sp = || -> Result<(), Error> { + let output_sp = |owner_api: &mut Owner, lock_outputs: bool| -> Result<(), Error> { Ok(output_slatepack( owner_api, keychain_mask, &slate, dest.clone(), args.outfile, - true, + lock_outputs, false, args.slatepack_qr, )?) @@ -509,7 +508,11 @@ where let can_send = tor_config.send_tor(args.skip_tor); if test_mode || !can_send || dest.is_none() { - return output_sp(); + return output_sp(owner_api, !args.late_lock); + } + + if !args.late_lock { + owner_api.tx_lock_outputs(keychain_mask, &slate)?; } let dest = dest.as_ref().unwrap(); @@ -517,7 +520,6 @@ where match res { Ok(s) => { - owner_api.tx_lock_outputs(keychain_mask, &s)?; let ret_slate = owner_api.finalize_tx(keychain_mask, &s)?; let result = owner_api.post_tx(keychain_mask, &ret_slate, args.fluff); match result { @@ -532,7 +534,13 @@ where } Err(e) => { error!("Error sending slate sync: {}", e); - output_sp()?; + output_sp(owner_api, false)?; + if !args.late_lock { + println!( + "Outputs are locked. To unlock them, cancel with `cancel -t {}`.", + slate.id + ); + } } } Ok(()) @@ -581,7 +589,18 @@ where println!(); if !finalizing { - println!("Slatepack data follows. Please provide this output to the other party"); + let cancel_hint = if lock { + format!( + " or cancel it manually with `cancel -t {}` command", + slate.id + ) + } else { + "".to_string() + }; + println!( + "Slatepack data follows. Please provide this output to the other party{}.", + cancel_hint + ); } else { println!("Slatepack data follows."); } @@ -1074,7 +1093,6 @@ where selection_strategy_is_use_all: args.selection_strategy == "all", refresh_outputs_from_node: !info_updated, ttl_blocks: args.ttl_blocks, - send_args: None, ..Default::default() }; let result = owner_api.process_invoice_tx(keychain_mask, &slate, init_args); diff --git a/controller/src/controller.rs b/controller/src/controller.rs index ef61152..49fa040 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -136,23 +136,6 @@ fn init_tor_listener( Ok(process) } -/// Instantiate wallet Owner API for a single-use (command line) call -/// Return a function containing a loaded API context to call -pub fn owner_single_use( - wallet: Arc>>>, - keychain_mask: Option<&SecretKey>, - config_path: PathBuf, - f: F, -) -> Result<(), Error> -where - L: WalletLCProvider<'static, C, K> + 'static, - F: FnOnce(&mut Owner, Option<&SecretKey>) -> Result<(), Error>, - C: NodeClient + 'static, - K: Keychain + 'static, -{ - f(&mut Owner::new(wallet, None, config_path), keychain_mask) -} - /// Instantiate wallet Foreign API for a single-use (command line) call /// Return a function containing a loaded API context to call pub fn foreign_single_use<'a, L, F, C, K>( diff --git a/controller/tests/accounts.rs b/controller/tests/accounts.rs index 59645f8..fd19c28 100644 --- a/controller/tests/accounts.rs +++ b/controller/tests/accounts.rs @@ -19,7 +19,6 @@ extern crate grin_wallet_impls as impls; use grin_core as core; use grin_keychain as keychain; -use std::path::PathBuf; use self::core::global; use self::keychain::{ExtKeychain, Keychain}; @@ -49,7 +48,8 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -62,7 +62,8 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -79,48 +80,30 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height // test default accounts exist - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let accounts = api.accounts(m)?; - assert_eq!(accounts[0].label, "default"); - assert_eq!(accounts[0].path, ExtKeychain::derive_key_id(2, 0, 0, 0, 0)); - Ok(()) - }, - )?; + { + let accounts = api1.accounts(mask1)?; + assert_eq!(accounts[0].label, "default"); + assert_eq!(accounts[0].path, ExtKeychain::derive_key_id(2, 0, 0, 0, 0)); + } // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let new_path = api.create_account_path(m, "account1").unwrap(); - assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); - let new_path = api.create_account_path(m, "account2").unwrap(); - assert_eq!(new_path, ExtKeychain::derive_key_id(2, 2, 0, 0, 0)); - let new_path = api.create_account_path(m, "account3").unwrap(); - assert_eq!(new_path, ExtKeychain::derive_key_id(2, 3, 0, 0, 0)); - // trying to add same label again should fail - let res = api.create_account_path(m, "account1"); - assert!(res.is_err()); - Ok(()) - }, - )?; + { + let new_path = api1.create_account_path(mask1, "account1").unwrap(); + assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); + let new_path = api1.create_account_path(mask1, "account2").unwrap(); + assert_eq!(new_path, ExtKeychain::derive_key_id(2, 2, 0, 0, 0)); + let new_path = api1.create_account_path(mask1, "account3").unwrap(); + assert_eq!(new_path, ExtKeychain::derive_key_id(2, 3, 0, 0, 0)); + // trying to add same label again should fail + let res = api1.create_account_path(mask1, "account1"); + assert!(res.is_err()); + } // add account to wallet 2 - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let new_path = api.create_account_path(m, "listener_account").unwrap(); - assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); - Ok(()) - }, - )?; + { + let new_path = api2.create_account_path(mask2, "listener_account").unwrap(); + assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); + } // Default wallet 2 to listen on that account { @@ -144,120 +127,85 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false); // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 5 * reward); - assert_eq!(wallet1_info.amount_currently_spendable, (5 - cm) * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 5); - Ok(()) - }, - )?; + { + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, 12); + assert_eq!(wallet1_info.total, 5 * reward); + assert_eq!(wallet1_info.amount_currently_spendable, (5 - cm) * reward); + // check tx log as well + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert_eq!(txs.len(), 5); + } // now check second account { - // let mut w_lock = wallet1.lock(); - // let lc = w_lock.lc_provider()?; - // let w = lc.wallet_inst()?; wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account1")?; } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let labels: Vec<_> = api.accounts(m)?.into_iter().map(|a| a.label).collect(); - assert_eq!(labels, ["account1", "default", "account2", "account3"]); - // check last confirmed height on this account is different from above (should be 0) - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 0); - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 7 * reward); - assert_eq!(wallet1_info.amount_currently_spendable, 7 * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 7); - Ok(()) - }, - )?; - + { + let labels: Vec<_> = api1.accounts(mask1)?.into_iter().map(|a| a.label).collect(); + assert_eq!(labels, ["account1", "default", "account2", "account3"]); + // check last confirmed height on this account is different from above (should be 0) + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, false, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, 0); + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, 12); + assert_eq!(wallet1_info.total, 7 * reward); + assert_eq!(wallet1_info.amount_currently_spendable, 7 * reward); + // check tx log as well + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert_eq!(txs.len(), 7); + } // should be nothing in default account { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("default")?; } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 0); - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 12); - assert_eq!(wallet1_info.total, 0,); - assert_eq!(wallet1_info.amount_currently_spendable, 0,); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 0); - Ok(()) - }, - )?; + { + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, false, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, 0); + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, 12); + assert_eq!(wallet1_info.total, 0,); + assert_eq!(wallet1_info.amount_currently_spendable, 0,); + // check tx log as well + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert_eq!(txs.len(), 0); + } // Send a tx to another wallet { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account1")?; } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: reward, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let mut slate = api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate)?; - api.tx_lock_outputs(m, &slate)?; - slate = api.finalize_tx(m, &slate)?; - api.post_tx(m, &slate, false)?; - Ok(()) - }, - )?; - - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 9); - Ok(()) - }, - )?; + { + let args = InitTxArgs { + src_acct_name: None, + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api1.init_send_tx(mask1, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate)?; + api1.tx_lock_outputs(mask1, &slate)?; + slate = api1.finalize_tx(mask1, &slate)?; + api1.post_tx(mask1, &slate, false)?; + } + { + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, 13); + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert_eq!(txs.len(), 9); + } // Use the requested account's stored height { wallet_inst!(wallet1, w); @@ -272,59 +220,41 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("account2")?; } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 12); - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - println!("{:?}", txs); - assert_eq!(txs.len(), 5); - Ok(()) - }, - )?; + { + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, false, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, 12); + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, 13); + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + println!("{:?}", txs); + assert_eq!(txs.len(), 5); + } // wallet 2 should only have this tx on the listener account - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - Ok(()) - }, - )?; + { + let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, 13); + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 1); + } // Default account on wallet 2 should be untouched { wallet_inst!(wallet2, w); w.set_parent_key_id_by_name("default")?; } - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet2_info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(wallet2_info.last_confirmed_height, 0); - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, 13); - assert_eq!(wallet2_info.total, 0,); - assert_eq!(wallet2_info.amount_currently_spendable, 0,); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 0); - Ok(()) - }, - )?; + { + let (_, wallet2_info) = api2.retrieve_summary_info(mask2, false, 1)?; + assert_eq!(wallet2_info.last_confirmed_height, 0); + let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, 13); + assert_eq!(wallet2_info.total, 0,); + assert_eq!(wallet2_info.amount_currently_spendable, 0,); + // check tx log as well + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 0); + } // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/build_chain.rs b/controller/tests/build_chain.rs index 7120e44..12210e8 100644 --- a/controller/tests/build_chain.rs +++ b/controller/tests/build_chain.rs @@ -21,9 +21,8 @@ extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; use grin_core as core; -use std::path::PathBuf; -use self::libwallet::{InitTxArgs, Slate}; +use self::libwallet::InitTxArgs; use impls::test_framework::{self, LocalWalletClient}; use rand::Rng; use std::sync::atomic::Ordering; @@ -48,7 +47,8 @@ fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwal "wallet1", None, &mut wallet_proxy, - true + true, + api1 ); let mask1 = (&mask1_i).as_ref(); debug!("Mask1: {:?}", mask1); @@ -60,7 +60,8 @@ fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwal "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); debug!("Mask2: {:?}", mask2); @@ -74,25 +75,8 @@ fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwal // Stop the scanning updater threads because it extends the time needed to build the chain // exponentially - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, _m| { - api.stop_updater()?; - Ok(()) - }, - )?; - - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, _m| { - api.stop_updater()?; - Ok(()) - }, - )?; + api1.stop_updater()?; + api2.stop_updater()?; // few values to keep things shorter let reward = core::consensus::REWARD; @@ -105,22 +89,14 @@ fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwal let mut wallet_1_has_funds = false; // Check wallet 1 contents - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!( - "Wallet 1 spendable - {}", - wallet1_info.amount_currently_spendable - ); - if wallet1_info.amount_currently_spendable > reward { - wallet_1_has_funds = true; - } - Ok(()) - }, - )?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + debug!( + "Wallet 1 spendable - {}", + wallet1_info.amount_currently_spendable + ); + if wallet1_info.amount_currently_spendable > reward { + wallet_1_has_funds = true; + } // let's say 1 in every 3 blocks has a transaction (i.e. random 0 here and wallet1 has funds) let transact = rng.gen_range(0, 2) == 0; @@ -134,30 +110,21 @@ fn build_chain(test_dir: &'static str, block_height: usize) -> Result<(), libwal let num_txs = rng.gen_range(0, 3); for _ in 0..num_txs { let amount: u64 = rng.gen_range(1, 10_000_000_001); - let mut slate = Slate::blank(1, false); debug!("Creating TX for {}", amount); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount, - minimum_confirmations: 1, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - Ok(()) - }, - )?; + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 1, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; + let slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + api1.tx_lock_outputs(mask1, &slate)?; + api1.finalize_tx(mask1, &slate)?; } } // let logging finish diff --git a/controller/tests/build_output.rs b/controller/tests/build_output.rs index 491e916..798732d 100644 --- a/controller/tests/build_output.rs +++ b/controller/tests/build_output.rs @@ -25,7 +25,6 @@ use grin_util::{secp, ZeroingString}; use grin_wallet_libwallet as libwallet; use impls::test_framework::LocalWalletClient; use rand::{thread_rng, Rng}; -use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -37,7 +36,7 @@ use common::{clean_output_dir, create_wallet_proxy, setup}; fn build_output_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // Generate seed so we can verify the blinding factor is derived correctly let seed: [u8; 32] = thread_rng().gen(); - let keychain = ExtKeychain::from_seed(&seed, false).unwrap(); + let keychain = ExtKeychain::from_seed(&seed, false)?; let mnemonic = mnemonic::from_entropy(&seed).unwrap(); // Create a new proxy to simulate server and wallet responses @@ -52,7 +51,8 @@ fn build_output_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error "wallet1", Some(ZeroingString::from(mnemonic)), &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -67,28 +67,19 @@ fn build_output_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); let features = OutputFeatures::Plain; let amount = 60_000_000_000; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - let built_output = sender_api.build_output(m, features, amount)?; + let built_output = api1.build_output(mask1, features, amount)?; - let key_id = built_output.key_id; - assert_eq!(key_id.to_path(), ExtKeychainPath::new(3, 0, 0, 0, 0)); + let key_id = built_output.key_id; + assert_eq!(key_id.to_path(), ExtKeychainPath::new(3, 0, 0, 0, 0)); - let blind = built_output.blind; - let key = keychain.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?; - assert_eq!(blind, BlindingFactor::from_secret_key(key.clone())); + let blind = built_output.blind; + let key = keychain.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?; + assert_eq!(blind, BlindingFactor::from_secret_key(key.clone())); - let output = built_output.output; - assert_eq!(output.features(), features); - assert_eq!(output.commitment(), secp.commit(amount, key)?); - output.verify_proof()?; - - Ok(()) - }, - )?; + let output = built_output.output; + assert_eq!(output.features(), features); + assert_eq!(output.commitment(), secp.commit(amount, key)?); + output.verify_proof()?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/check.rs b/controller/tests/check.rs index d526335..6d7fbdb 100644 --- a/controller/tests/check.rs +++ b/controller/tests/check.rs @@ -27,7 +27,6 @@ use impls::test_framework::{self, LocalWalletClient}; use impls::{PathToSlate, SlatePutter as _}; use libwallet::{InitTxArgs, NodeClient}; use std::cmp; -use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -66,7 +65,8 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -77,7 +77,8 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -89,34 +90,22 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { }); // few values to keep things shorter - let reward = core::consensus::REWARD; - let cm = global::coinbase_maturity() as u64; // assume all testing precedes soft fork height + let reward = consensus::REWARD; + let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "named_account_1")?; - api.create_account_path(m, "account_2")?; - api.create_account_path(m, "account_3")?; - api.set_active_account(m, "named_account_1")?; - Ok(()) - }, - )?; + { + api1.create_account_path(mask1, "named_account_1")?; + api1.create_account_path(mask1, "account_2")?; + api1.create_account_path(mask1, "account_3")?; + api1.set_active_account(mask1, "named_account_1")?; + } // add account to wallet 2 - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "account_1")?; - api.set_active_account(m, "account_1")?; - Ok(()) - }, - )?; + { + api2.create_account_path(mask2, "account_1")?; + api2.set_active_account(mask2, "account_1")?; + } // Do some mining let bh = 20u64; @@ -124,36 +113,21 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); // Sanity check contents - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); - assert_eq!(wallet1_info.total, c); - assert_eq!(txs.len(), bh as usize); - Ok(()) - }, - )?; + { + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); + // check tx log as well + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); + assert_eq!(wallet1_info.total, c); + assert_eq!(txs.len(), bh as usize); + } // Accidentally delete some outputs - let mut w1_outputs_commits = vec![]; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - w1_outputs_commits = api.retrieve_outputs(m, false, true, None)?.1; - Ok(()) - }, - )?; + let w1_outputs_commits = api1.retrieve_outputs(mask1, false, true, None)?.1; let w1_outputs: Vec = w1_outputs_commits.into_iter().map(|m| m.output).collect(); { @@ -170,108 +144,54 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { } // check we have a problem now - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); - assert_ne!(wallet1_info.total, c); - Ok(()) - }, - )?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); + assert_ne!(wallet1_info.total, c); // this should restore our missing outputs - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, true)?; - Ok(()) - }, - )?; + api1.scan(mask1, None, true)?; // check our outputs match again - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.total, bh * reward); - // And check account names haven't been splatted - let accounts = api.accounts(m)?; - assert_eq!(accounts.len(), 4); - assert!(api.set_active_account(m, "account_1").is_err()); - assert!(api.set_active_account(m, "named_account_1").is_ok()); - Ok(()) - }, - )?; - + { + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.total, bh * reward); + // And check account names haven't been splatted + let accounts = api1.accounts(mask1)?; + assert_eq!(accounts.len(), 4); + assert!(api1.set_active_account(mask1, "account_1").is_err()); + assert!(api1.set_active_account(mask1, "named_account_1").is_ok()); + } // perform a transaction, but don't let it finish - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // send to send - let args = InitTxArgs { - src_acct_name: None, - amount: reward * 2, - minimum_confirmations: cm, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // output tx file - let send_file = format!("{}/part_tx_1.tx", test_dir); - PathToSlate(send_file.into()).put_tx(&slate, false)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + { + // send to send + let args = InitTxArgs { + src_acct_name: None, + amount: reward * 2, + minimum_confirmations: cm, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api1.init_send_tx(mask1, args)?; + // output tx file + let send_file = format!("{}/part_tx_1.tx", test_dir); + PathToSlate(send_file.into()).put_tx(&slate, false)?; + api1.tx_lock_outputs(mask1, &slate)?; + } // check we're all locked - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.amount_currently_spendable, 0); - Ok(()) - }, - )?; - + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.amount_currently_spendable, 0); // unlock/restore - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, true)?; - Ok(()) - }, - )?; + api1.scan(mask1, None, true)?; // check spendable amount again - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); - Ok(()) - }, - )?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); // let logging finish stopper.store(false, Ordering::Relaxed); @@ -300,7 +220,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "miner", None, &mut wallet_proxy, - false + false, + _m_api ); let miner_mask = (&miner_mask_i).as_ref(); @@ -313,7 +234,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet1", seed_phrase, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -324,7 +246,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet2", seed_phrase, &mut wallet_proxy, - false + false, + _api2 ); let mask2 = (&mask2_i).as_ref(); // we'll restore into here @@ -336,7 +259,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet3", seed_phrase, &mut wallet_proxy, - false + false, + api3 ); let mask3 = (&mask3_i).as_ref(); // also restore into here @@ -348,7 +272,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet4", seed_phrase, &mut wallet_proxy, - false + false, + api4 ); let mask4 = (&mask4_i).as_ref(); // Simulate a recover from seed without restore into here @@ -360,7 +285,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet5", seed_phrase, &mut wallet_proxy, - false + false, + api5 ); //simulate a recover from seed without restore into here let mask5 = (&mask5_i).as_ref(); @@ -372,7 +298,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet6", seed_phrase, &mut wallet_proxy, - false + false, + api6 ); let mask6 = (&mask6_i).as_ref(); @@ -384,7 +311,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet7", seed_phrase, &mut wallet_proxy, - false + false, + api7 ); let mask7 = (&mask7_i).as_ref(); create_wallet_and_add!( @@ -395,7 +323,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet8", seed_phrase, &mut wallet_proxy, - false + false, + api8 ); let mask8 = (&mask8_i).as_ref(); create_wallet_and_add!( @@ -406,7 +335,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet9", seed_phrase, &mut wallet_proxy, - false + false, + api9 ); let mask9 = (&mask9_i).as_ref(); create_wallet_and_add!( @@ -417,7 +347,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er "wallet10", seed_phrase, &mut wallet_proxy, - false + false, + api10 ); let mask10 = (&mask10_i).as_ref(); @@ -468,18 +399,10 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er bh += 3; // 0) Check repair when all is okay should leave wallet contents alone - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, true)?; - let info = wallet_info!(wallet1.clone(), m)?; - assert_eq!(info.amount_currently_spendable, base_amount * 6); - assert_eq!(info.total, base_amount * 6); - Ok(()) - }, - )?; + api1.scan(mask1, None, true)?; + let info = wallet_info!(wallet1.clone(), mask1)?; + assert_eq!(info.amount_currently_spendable, base_amount * 6); + assert_eq!(info.total, base_amount * 6); // send some funds to wallet 2 send_to_dest!( @@ -523,53 +446,21 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er // seed + BIP32 path. // 1) a full restore should recover all of them: - wallet::controller::owner_single_use( - wallet3.clone(), - mask3, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, false)?; - Ok(()) - }, - )?; + api3.scan(mask3, None, false)?; - wallet::controller::owner_single_use( - wallet3.clone(), - mask3, - PathBuf::from(test_dir), - |api, m| { - let info = wallet_info!(wallet3.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); - assert_eq!(info.total, base_amount * 21); - Ok(()) - }, - )?; + let info = wallet_info!(wallet3.clone(), mask3)?; + let outputs = api3.retrieve_outputs(mask3, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); + assert_eq!(info.total, base_amount * 21); // 2) scan should recover them into a single wallet - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, true)?; - Ok(()) - }, - )?; + api1.scan(mask1, None, true)?; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let info = wallet_info!(wallet1.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); - Ok(()) - }, - )?; + let info = wallet_info!(wallet1.clone(), mask1)?; + let outputs = api1.retrieve_outputs(mask1, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); // 3) If I recover from seed and start using the wallet without restoring, // scan should restore the older outputs @@ -601,41 +492,17 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false); bh += cm as u64; - wallet::controller::owner_single_use( - wallet4.clone(), - mask4, - PathBuf::from(test_dir), - |api, m| { - let info = wallet_info!(wallet4.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 9); - assert_eq!(info.amount_currently_spendable, base_amount * 45); - Ok(()) - }, - )?; + let info = wallet_info!(wallet4.clone(), mask4)?; + let outputs = api4.retrieve_outputs(mask4, true, false, None)?.1; + assert_eq!(outputs.len(), 9); + assert_eq!(info.amount_currently_spendable, base_amount * 45); - wallet::controller::owner_single_use( - wallet5.clone(), - mask5, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, false)?; - Ok(()) - }, - )?; + api5.scan(mask5, None, false)?; - wallet::controller::owner_single_use( - wallet5.clone(), - mask5, - PathBuf::from(test_dir), - |api, m| { - let info = wallet_info!(wallet5.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 9); - assert_eq!(info.amount_currently_spendable, base_amount * (45)); - Ok(()) - }, - )?; + let info = wallet_info!(wallet5.clone(), mask5)?; + let outputs = api5.retrieve_outputs(mask5, true, false, None)?.1; + assert_eq!(outputs.len(), 9); + assert_eq!(info.amount_currently_spendable, base_amount * (45)); // 4) If I recover from seed and start using the wallet without restoring, // scan should restore the older outputs @@ -662,50 +529,20 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er )?; bh += 3; - let _ = test_framework::award_blocks_to_wallet( - &chain, - miner.clone(), - miner_mask, - cm as usize, - false, - ); + let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false); bh += cm as u64; - wallet::controller::owner_single_use( - wallet6.clone(), - mask6, - PathBuf::from(test_dir), - |api, m| { - let info = wallet_info!(wallet6.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 12); - assert_eq!(info.amount_currently_spendable, base_amount * 78); - Ok(()) - }, - )?; + let info = wallet_info!(wallet6.clone(), mask6)?; + let outputs = api6.retrieve_outputs(mask6, true, false, None)?.1; + assert_eq!(outputs.len(), 12); + assert_eq!(info.amount_currently_spendable, base_amount * 78); - wallet::controller::owner_single_use( - wallet6.clone(), - mask6, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, true)?; - Ok(()) - }, - )?; + api6.scan(mask6, None, true)?; - wallet::controller::owner_single_use( - wallet6.clone(), - mask6, - PathBuf::from(test_dir), - |api, m| { - let info = wallet_info!(wallet6.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 12); - assert_eq!(info.amount_currently_spendable, base_amount * (78)); - Ok(()) - }, - )?; + let info = wallet_info!(wallet6.clone(), mask6)?; + let outputs = api6.retrieve_outputs(mask6, true, false, None)?.1; + assert_eq!(outputs.len(), 12); + assert_eq!(info.amount_currently_spendable, base_amount * (78)); // 5) Start using same seed with a different account, amounts should // be distinct and restore should return funds from other account @@ -734,16 +571,8 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er bh += 3; // mix it up a bit - wallet::controller::owner_single_use( - wallet7.clone(), - mask7, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "account_1")?; - api.set_active_account(m, "account_1")?; - Ok(()) - }, - )?; + api7.create_account_path(mask7, "account_1")?; + api7.set_active_account(mask7, "account_1")?; send_to_dest!( miner.clone(), @@ -772,57 +601,33 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false); bh += cm as u64; - wallet::controller::owner_single_use( - wallet7.clone(), - mask7, - PathBuf::from(test_dir), - |api, m| { - let info = wallet_info!(wallet7.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 3); - assert_eq!(info.amount_currently_spendable, base_amount * 6); - api.set_active_account(m, "default")?; - let info = wallet_info!(wallet7.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 15); - assert_eq!(info.amount_currently_spendable, base_amount * 120); - Ok(()) - }, - )?; + let info = wallet_info!(wallet7.clone(), mask7)?; + let outputs = api7.retrieve_outputs(mask7, true, false, None)?.1; + assert_eq!(outputs.len(), 3); + assert_eq!(info.amount_currently_spendable, base_amount * 6); + api7.set_active_account(mask7, "default")?; + let info = wallet_info!(wallet7.clone(), mask7)?; + let outputs = api7.retrieve_outputs(mask7, true, false, None)?.1; + assert_eq!(outputs.len(), 15); + assert_eq!(info.amount_currently_spendable, base_amount * 120); - wallet::controller::owner_single_use( - wallet8.clone(), - mask8, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, false)?; - let info = wallet_info!(wallet8.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 15); - assert_eq!(info.amount_currently_spendable, base_amount * 120); - api.set_active_account(m, "account_1")?; - let info = wallet_info!(wallet8.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 3); - assert_eq!(info.amount_currently_spendable, base_amount * 6); - Ok(()) - }, - )?; + api8.scan(mask8, None, false)?; + let info = wallet_info!(wallet8.clone(), mask8)?; + let outputs = api8.retrieve_outputs(mask8, true, false, None)?.1; + assert_eq!(outputs.len(), 15); + assert_eq!(info.amount_currently_spendable, base_amount * 120); + api8.set_active_account(mask8, "account_1")?; + let info = wallet_info!(wallet8.clone(), mask8)?; + let outputs = api8.retrieve_outputs(mask8, true, false, None)?.1; + assert_eq!(outputs.len(), 3); + assert_eq!(info.amount_currently_spendable, base_amount * 6); // 6) Start using same seed with a different account, now overwriting // ids on account 2 as well, scan should get all outputs created // to now into 2 accounts - wallet::controller::owner_single_use( - wallet9.clone(), - mask9, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "account_1")?; - api.set_active_account(m, "account_1")?; - Ok(()) - }, - )?; + api9.create_account_path(mask9, "account_1")?; + api9.set_active_account(mask9, "account_1")?; send_to_dest!( miner.clone(), @@ -848,53 +653,37 @@ fn two_wallets_one_seed_impl(test_dir: &'static str) -> Result<(), libwallet::Er bh += 3; let _bh = bh; - wallet::controller::owner_single_use( - wallet9.clone(), - mask9, - PathBuf::from(test_dir), - |api, m| { - let info = wallet_info!(wallet9.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); - api.scan(m, None, true)?; - let info = wallet_info!(wallet9.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); + let info = wallet_info!(wallet9.clone(), mask9)?; + let outputs = api9.retrieve_outputs(mask9, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); + api9.scan(mask9, None, true)?; + let info = wallet_info!(wallet9.clone(), mask9)?; + let outputs = api9.retrieve_outputs(mask9, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); - api.set_active_account(m, "default")?; - let info = wallet_info!(wallet9.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 15); - assert_eq!(info.amount_currently_spendable, base_amount * 120); - Ok(()) - }, - )?; + api9.set_active_account(mask9, "default")?; + let info = wallet_info!(wallet9.clone(), mask9)?; + let outputs = api9.retrieve_outputs(mask9, true, false, None)?.1; + assert_eq!(outputs.len(), 15); + assert_eq!(info.amount_currently_spendable, base_amount * 120); let _ = test_framework::award_blocks_to_wallet(&chain, miner.clone(), miner_mask, cm, false); // 7) Ensure scan creates missing accounts - wallet::controller::owner_single_use( - wallet10.clone(), - mask10, - PathBuf::from(test_dir), - |api, m| { - api.scan(m, None, true)?; - api.set_active_account(m, "account_1")?; - let info = wallet_info!(wallet10.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 6); - assert_eq!(info.amount_currently_spendable, base_amount * 21); + api10.scan(mask10, None, true)?; + api10.set_active_account(mask10, "account_1")?; + let info = wallet_info!(wallet10.clone(), mask10)?; + let outputs = api10.retrieve_outputs(mask10, true, false, None)?.1; + assert_eq!(outputs.len(), 6); + assert_eq!(info.amount_currently_spendable, base_amount * 21); - api.set_active_account(m, "default")?; - let info = wallet_info!(wallet10.clone(), m)?; - let outputs = api.retrieve_outputs(m, true, false, None)?.1; - assert_eq!(outputs.len(), 15); - assert_eq!(info.amount_currently_spendable, base_amount * 120); - Ok(()) - }, - )?; + api10.set_active_account(mask10, "default")?; + let info = wallet_info!(wallet10.clone(), mask10)?; + let outputs = api10.retrieve_outputs(mask10, true, false, None)?.1; + assert_eq!(outputs.len(), 15); + assert_eq!(info.amount_currently_spendable, base_amount * 120); // let logging finish stopper.store(false, Ordering::Relaxed); @@ -918,7 +707,8 @@ fn output_scanning_impl(test_dir: &'static str) -> Result<(), libwallet::Error> "wallet1", None, &mut wallet_proxy, - false + false, + _api1 ); let mask1 = (&mask1_i).as_ref(); thread::spawn(move || { @@ -952,7 +742,7 @@ fn output_scanning_impl(test_dir: &'static str) -> Result<(), libwallet::Error> ); assert_eq!(outputs.2.len(), 14); - // mid range + // mid-range let ranges = client1.height_range_to_pmmr_indices(5, Some(14))?; assert_eq!(ranges, (8, 25)); let outputs = client1.get_outputs_by_pmmr_index(ranges.0, Some(ranges.1), 1000)?; @@ -1002,7 +792,8 @@ fn multi_batch_scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -1017,19 +808,10 @@ fn multi_batch_scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); - let mut output_commits = vec![]; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, bh * reward); - output_commits = api.retrieve_outputs(m, false, true, None)?.1; - Ok(()) - }, - )?; + let (_, info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, bh * reward); + let output_commits = api1.retrieve_outputs(mask1, false, true, None)?.1; { wallet_inst!(wallet1, w); @@ -1076,18 +858,10 @@ fn multi_batch_scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> } assert!(batches > 1); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, restored_outputs) = api.retrieve_outputs(m, false, true, None)?; - let (_, info) = api.retrieve_summary_info(m, false, 1)?; - assert_eq!(info.total, bh * reward); - assert_eq!(restored_outputs.len(), bh as usize); - Ok(()) - }, - )?; + let (_, restored_outputs) = api1.retrieve_outputs(mask1, false, true, None)?; + let (_, info) = api1.retrieve_summary_info(mask1, false, 1)?; + assert_eq!(info.total, bh * reward); + assert_eq!(restored_outputs.len(), bh as usize); stopper.store(false, Ordering::Relaxed); thread::sleep(Duration::from_millis(200)); @@ -1109,7 +883,8 @@ fn restore_corrupted_outputs_across_batches_impl( "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -1124,18 +899,9 @@ fn restore_corrupted_outputs_across_batches_impl( let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); - let mut output_commits = vec![]; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(info.total, bh * reward); - output_commits = api.retrieve_outputs(m, false, true, None)?.1; - Ok(()) - }, - )?; + let (_, info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert_eq!(info.total, bh * reward); + let output_commits = api1.retrieve_outputs(mask1, false, true, None)?.1; { wallet_inst!(wallet1, w); @@ -1173,30 +939,22 @@ fn restore_corrupted_outputs_across_batches_impl( assert_eq!(last_scanned.hash, ""); } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, outputs_after_first_batch) = api.retrieve_outputs(m, false, false, None)?; - assert_eq!(outputs_after_first_batch.len(), (bh - 1) as usize); + let (_, outputs_after_first_batch) = api1.retrieve_outputs(mask1, false, false, None)?; + assert_eq!(outputs_after_first_batch.len(), (bh - 1) as usize); - let (_, info) = api.retrieve_summary_info(m, true, 1)?; - let (_, restored_outputs) = api.retrieve_outputs(m, false, true, None)?; - let unique_keys = restored_outputs - .iter() - .map(|o| o.output.key_id.clone()) - .collect::>(); + let (_, info) = api1.retrieve_summary_info(mask1, true, 1)?; + let (_, restored_outputs) = api1.retrieve_outputs(mask1, false, true, None)?; + let unique_keys = restored_outputs + .iter() + .map(|o| o.output.key_id.clone()) + .collect::>(); - assert_eq!(info.total, bh * reward); - assert_eq!(restored_outputs.len(), bh as usize); - assert_eq!(unique_keys.len(), bh as usize); - assert!(restored_outputs - .iter() - .all(|o| o.output.status == libwallet::OutputStatus::Unspent)); - Ok(()) - }, - )?; + assert_eq!(info.total, bh * reward); + assert_eq!(restored_outputs.len(), bh as usize); + assert_eq!(unique_keys.len(), bh as usize); + assert!(restored_outputs + .iter() + .all(|o| o.output.status == libwallet::OutputStatus::Unspent)); stopper.store(false, Ordering::Relaxed); thread::sleep(Duration::from_millis(200)); diff --git a/controller/tests/common/mod.rs b/controller/tests/common/mod.rs index 725160e..716b79e 100644 --- a/controller/tests/common/mod.rs +++ b/controller/tests/common/mod.rs @@ -19,11 +19,15 @@ extern crate grin_wallet_libwallet as libwallet; use grin_core as core; use grin_keychain as keychain; use grin_util as util; +use std::env; +use std::path::PathBuf; use self::core::global; use self::core::global::ChainTypes; use self::keychain::ExtKeychain; use self::libwallet::WalletInst; +use grin_wallet_api::Owner; +use grin_wallet_config::{initial_setup_wallet, GRIN_WALLET_DIR}; use impls::test_framework::{LocalWalletClient, WalletProxy}; use impls::{DefaultLCProvider, DefaultWalletImpl}; use std::sync::Arc; @@ -41,9 +45,9 @@ macro_rules! wallet_inst { #[macro_export] macro_rules! create_wallet_and_add { - ($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $seed_phrase: expr, $proxy: expr, $create_mask: expr) => { + ($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $seed_phrase: expr, $proxy: expr, $create_mask: expr, $owner_api: ident) => { let $client = LocalWalletClient::new($name, $proxy.tx.clone()); - let ($wallet, $mask) = common::create_local_wallet( + let ($wallet, $mask, $owner_api) = common::create_local_wallet( $test_dir, $name, $seed_phrase.clone(), @@ -125,7 +129,18 @@ pub fn create_local_wallet( >, >, Option, + Owner, LocalWalletClient, ExtKeychain>, ) { + let mut current_dir; + current_dir = env::current_dir().unwrap_or_else(|e| { + panic!("Error creating config file: {}", e); + }); + current_dir.push(test_dir); + current_dir.push(name); + let config = + initial_setup_wallet(&ChainTypes::AutomatedTesting, Some(current_dir), true).unwrap(); + let mut wallet_config = config.clone().members.wallet; + let mut wallet = Box::new(DefaultWalletImpl::::new(client).unwrap()) as Box< dyn WalletInst< @@ -135,13 +150,25 @@ pub fn create_local_wallet( >, >; let lc = wallet.lc_provider().unwrap(); - let _ = lc.set_top_level_directory(&format!("{}/{}", test_dir, name)); + // legacy hack to avoid the need for changes in existing grin-wallet.toml files + // remove `wallet_data` from end of path as + // new lifecycle provider assumes grin_wallet.toml is in root of data directory + let mut top_level_wallet_dir = PathBuf::from(wallet_config.clone().data_file_dir); + if top_level_wallet_dir.ends_with(GRIN_WALLET_DIR) { + top_level_wallet_dir.pop(); + wallet_config.data_file_dir = top_level_wallet_dir.to_str().unwrap().into(); + } + let _ = lc.set_top_level_directory(&wallet_config.data_file_dir); lc.create_wallet(None, mnemonic, 32, ZeroingString::from(""), false) .unwrap(); let mask = lc .open_wallet(None, ZeroingString::from(""), create_mask, false) .unwrap(); - (Arc::new(Mutex::new(wallet)), mask) + + let wallet = Arc::new(Mutex::new(wallet)); + let owner_api = Owner::new(wallet.clone(), None, config.config_file_path.clone()); + + (wallet, mask, owner_api) } #[allow(dead_code)] diff --git a/controller/tests/config_api.rs b/controller/tests/config_api.rs index 5936a7f..6d1ac20 100644 --- a/controller/tests/config_api.rs +++ b/controller/tests/config_api.rs @@ -21,9 +21,9 @@ use config::config::{get_global_config, reload_global_config}; use config::GlobalWalletConfig; use impls::test_framework::LocalWalletClient; use libwallet::{InitTxArgs, InitTxSendArgs}; -use std::fs; use std::path::{Path, PathBuf}; use std::sync::atomic::Ordering; +use std::{env, fs}; mod common; use common::{clean_output_dir, create_wallet_proxy, setup}; @@ -63,7 +63,8 @@ fn tor_disable() { "wallet", None, &mut wallet_proxy, - false + false, + _api ); let path = PathBuf::from(test_dir).join("grin-wallet.toml"); let mut config = @@ -94,6 +95,7 @@ fn tor_disable() { ); assert_eq!(cached, stored); + drop(_api); drop(owner); drop(wallet); drop(wallet_proxy); @@ -113,7 +115,8 @@ fn send_preflight() { "wallet", None, &mut wallet_proxy, - false + false, + _api ); let owner = api::Owner::new( wallet.clone(), @@ -144,6 +147,7 @@ fn send_preflight() { assert!(after_txs.is_empty()); assert_eq!(snapshot(&wallet_data), before_data); + drop(_api); drop(owner); drop(wallet); drop(wallet_proxy); @@ -163,9 +167,13 @@ fn directory_change() { "wallet", None, &mut wallet_proxy, - false + false, + _api ); - let old_dir = PathBuf::from(test_dir).join("wallet"); + let old_dir = env::current_dir().unwrap_or_else(|e| { + panic!("Error creating config file: {}", e); + }); + let old_dir = old_dir.join(test_dir).join("wallet"); let old_config = old_dir.join("grin-wallet.toml"); let new_dir = PathBuf::from(test_dir).join("other"); let owner = api::Owner::new(wallet.clone(), None, old_config.clone()); diff --git a/controller/tests/file.rs b/controller/tests/file.rs index e313407..a4e1fc4 100644 --- a/controller/tests/file.rs +++ b/controller/tests/file.rs @@ -27,7 +27,7 @@ use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; -use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate}; +use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs}; #[macro_use] mod common; @@ -50,7 +50,8 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -61,7 +62,8 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -76,28 +78,12 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - }, - )?; + api1.create_account_path(mask1, "mining")?; + api1.create_account_path(mask1, "listener")?; // add some accounts - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "account1")?; - api.create_account_path(m, "account2")?; - Ok(()) - }, - )?; + api2.create_account_path(mask2, "account1")?; + api2.create_account_path(mask2, "account2")?; // Get some mining done { @@ -122,32 +108,24 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), }; // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // output tx file - PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api1.init_send_tx(mask1, args)?; + // output tx file + PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; + api1.tx_lock_outputs(mask1, &slate)?; // Get some mining done { @@ -169,52 +147,28 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), }, )?; - // wallet 1 finalises and posts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let mut slate = PathToSlate(receive_file.into()).get_tx()?.0; - slate = api.finalize_tx(m, &slate)?; - // Output final file for reference - PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - }, - )?; + // wallet 1 finalizes and posts + let mut slate = PathToSlate(receive_file.into()).get_tx()?.0; + slate = api1.finalize_tx(mask1, &slate)?; + // Output final file for reference + PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; + api1.post_tx(mask1, &slate, false)?; + bh += 1; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Check total in mining account - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); // Check total in 'wallet 2' account - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, bh); - assert_eq!(wallet2_info.total, 2 * reward); - Ok(()) - }, - )?; + let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * reward); // Now other types of exchange, for reference // Invoice transaction @@ -231,44 +185,27 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), ), }; - let mut slate = Slate::blank(2, true); + let args = IssueInvoiceTxArgs { + amount: 1000000000, + ..Default::default() + }; + let mut slate = api2.issue_invoice_tx(mask2, args)?; + PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let args = IssueInvoiceTxArgs { - amount: 1000000000, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; - Ok(()) - }, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = PathToSlate((&send_file).into()).get_tx()?.0; + slate = api1.process_invoice_tx(mask1, &slate, args)?; + api1.tx_lock_outputs(mask1, &slate)?; + PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - slate = PathToSlate((&send_file).into()).get_tx()?.0; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; - Ok(()) - }, - )?; wallet::controller::foreign_single_use( wallet2.clone(), PathBuf::from(test_dir), @@ -281,15 +218,8 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), Ok(()) }, )?; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - }, - )?; + + api1.post_tx(mask1, &slate, false)?; // Standard, with payment proof let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); @@ -305,40 +235,22 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), format!("{}/standard_pp_S3.txbin", test_dir), ), }; - let mut slate = Slate::blank(2, true); - let mut address = None; - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - address = Some(api.get_slatepack_address(m, 0)?); - Ok(()) - }, - )?; + let address = Some(api2.get_slatepack_address(mask2, 0)?); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - payment_proof_recipient_address: address.clone(), - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: address.clone(), + ..Default::default() + }; + let mut slate = api1.init_send_tx(mask1, args)?; + PathToSlate((&send_file).into()).put_tx(&slate, use_bin)?; + api1.tx_lock_outputs(mask1, &slate)?; wallet::controller::foreign_single_use( wallet2.clone(), @@ -352,21 +264,12 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), }, )?; - // wallet 1 finalises and posts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - slate = PathToSlate(receive_file.into()).get_tx()?.0; - slate = api.finalize_tx(m, &slate)?; - // Output final file for reference - PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - }, - )?; + // wallet 1 finalizes and posts + slate = PathToSlate(receive_file.into()).get_tx()?.0; + slate = api1.finalize_tx(mask1, &slate)?; + // Output final file for reference + PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; + api1.post_tx(mask1, &slate, false)?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index b48f66e..8b76998 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -22,7 +22,7 @@ use grin_wallet_libwallet as libwallet; use std::path::PathBuf; use impls::test_framework::{self, LocalWalletClient}; -use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, SlateState}; +use libwallet::{InitTxArgs, IssueInvoiceTxArgs, SlateState}; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -46,7 +46,8 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - true + true, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -57,7 +58,8 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet2", None, &mut wallet_proxy, - true + true, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -72,16 +74,8 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - }, - )?; + api1.create_account_path(mask1, "mining")?; + api1.create_account_path(mask1, "listener")?; // Get some mining done { @@ -93,46 +87,28 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, _bh as usize, false); // Sanity check wallet 1 contents - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, _bh); - assert_eq!(wallet1_info.total, _bh * reward); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, _bh); + assert_eq!(wallet1_info.total, _bh * reward); - let mut slate = Slate::blank(2, true); - - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let error = api - .issue_invoice_tx( - m, - IssueInvoiceTxArgs { - amount: 0, - ..Default::default() - }, - ) - .unwrap_err(); - assert_eq!(error, libwallet::Error::InvalidAmount); - - // Wallet 2 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward * 2, + let error = api2 + .issue_invoice_tx( + mask2, + IssueInvoiceTxArgs { + amount: 0, ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - Ok(()) - }, - )?; + }, + ) + .unwrap_err(); + assert_eq!(error, libwallet::Error::InvalidAmount); + + // Wallet 2 initiates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + let mut slate = api2.issue_invoice_tx(mask2, args)?; assert_eq!(slate.state, SlateState::Invoice1); let receive_result = wallet::controller::foreign_single_use( @@ -142,16 +118,8 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { |api| api.receive_tx(&slate, None, None).map(|_| ()), ); assert_eq!(receive_result, Err(libwallet::Error::SlateState)); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, txs) = api.retrieve_txs(m, false, None, Some(slate.id), None)?; - assert!(txs.is_empty()); - Ok(()) - }, - )?; + let (_, txs) = api1.retrieve_txs(mask1, false, None, Some(slate.id), None)?; + assert!(txs.is_empty()); // receive must reject every slate state except S1 for state in [ @@ -173,51 +141,34 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(res, Err(libwallet::Error::SlateState), "state {}", state); } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let mut zero_amount_slate = slate.clone(); - zero_amount_slate.amount = 0; - assert_eq!( - api.process_invoice_tx(m, &zero_amount_slate, args.clone()) - .unwrap_err(), - libwallet::Error::InvalidAmount - ); - - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut zero_amount_slate = slate.clone(); + zero_amount_slate.amount = 0; + assert_eq!( + api1.process_invoice_tx(mask1, &zero_amount_slate, args.clone()) + .unwrap_err(), + libwallet::Error::InvalidAmount + ); + slate = api1.process_invoice_tx(mask1, &slate, args)?; + api1.tx_lock_outputs(mask1, &slate)?; assert_eq!(slate.state, SlateState::Invoice2); - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - assert_eq!( - api.tx_lock_outputs(m, &slate), - Err(libwallet::Error::SlateState) - ); - let (_, txs) = api.retrieve_txs(m, false, None, None, None)?; - assert_eq!(txs.len(), 1); - assert_eq!(txs[0].tx_type, libwallet::TxLogEntryType::TxReceived); - Ok(()) - }, - )?; + assert_eq!( + api2.tx_lock_outputs(mask2, &slate), + Err(libwallet::Error::SlateState) + ); + let (_, txs) = api2.retrieve_txs(mask2, false, None, None, None)?; + assert_eq!(txs.len(), 1); + assert_eq!(txs[0].tx_type, libwallet::TxLogEntryType::TxReceived); // wallet 2 finalizes and posts wallet::controller::foreign_single_use( @@ -233,86 +184,54 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(slate.state, SlateState::Invoice3); // wallet 1 posts so wallet 2 doesn't get the mined amount - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - }, - )?; + api1.post_tx(mask1, &slate, false)?; _bh += 1; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); _bh += 3; // Check transaction log for wallet 2 - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - assert!(txs.len() == 1); - println!( - "last confirmed height: {}, bh: {}", - wallet2_info.last_confirmed_height, _bh - ); - assert!(refreshed); - Ok(()) - }, - )?; + let (_, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + let (refreshed, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert!(refreshed); + assert_eq!(txs.len(), 1); + println!( + "last confirmed height: {}, bh: {}", + wallet2_info.last_confirmed_height, _bh + ); + assert!(refreshed); // Check transaction log for wallet 1, ensure only 1 entry // exists - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - assert_eq!(txs.len() as u64, _bh + 1); - println!( - "Wallet 1: last confirmed height: {}, bh: {}", - wallet1_info.last_confirmed_height, _bh - ); - Ok(()) - }, - )?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + let (refreshed, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert!(refreshed); + assert_eq!(txs.len() as u64, _bh + 1); + println!( + "Wallet 1: last confirmed height: {}, bh: {}", + wallet1_info.last_confirmed_height, _bh + ); // Test self-sending - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // Wallet 1 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward * 2, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - println!("Self invoice slate init: {}", slate); - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + // Wallet 1 initiates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api1.issue_invoice_tx(mask1, args)?; + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + println!("Self invoice slate init: {}", slate); + slate = api1.process_invoice_tx(mask1, &slate, args)?; + api1.tx_lock_outputs(mask1, &slate)?; println!("Self invoice slate after process: {}", slate); @@ -329,112 +248,66 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { )?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); - //bh += 3; - // As above, but use owner API to finalize - let mut slate = Slate::blank(2, true); - - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - // Wallet 2 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward * 2, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - Ok(()) - }, - )?; + // Wallet 2 initiates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api2.issue_invoice_tx(mask2, args)?; assert_eq!(slate.state, SlateState::Invoice1); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = api1.process_invoice_tx(mask1, &slate, args)?; + api1.tx_lock_outputs(mask1, &slate)?; assert_eq!(slate.state, SlateState::Invoice2); - // wallet 2 finalizes via owner API - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - // Wallet 2 receives the invoice transaction - slate = api.finalize_tx(m, &slate)?; - Ok(()) - }, - )?; + // Wallet 2 receives and finalizes via owner API + slate = api2.finalize_tx(mask2, &slate)?; assert_eq!(slate.state, SlateState::Invoice3); // test that payee can only cancel once let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); _bh += 3; - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - // Wallet 2 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward * 2, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - Ok(()) - }, - )?; + // Wallet 2 initiates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api2.issue_invoice_tx(mask2, args)?; assert_eq!(slate.state, SlateState::Invoice1); let orig_slate = slate.clone(); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - slate = api.process_invoice_tx(m, &slate, args.clone())?; - api.tx_lock_outputs(m, &slate)?; + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = api1.process_invoice_tx(mask1, &slate, args.clone())?; + api1.tx_lock_outputs(mask1, &slate)?; - // Wallet 1 cancels the invoice transaction - api.cancel_tx(m, None, Some(slate.id))?; + // Wallet 1 cancels the invoice transaction + api1.cancel_tx(mask1, None, Some(slate.id))?; - // Wallet 1 attempts to repay again - let res = api.process_invoice_tx(m, &orig_slate, args); - assert!(res.is_err()); - - Ok(()) - }, - )?; + // Wallet 1 attempts to repay again + let res = api1.process_invoice_tx(mask1, &orig_slate, args); + assert!(res.is_err()); assert_eq!(slate.state, SlateState::Invoice2); // let logging finish diff --git a/controller/tests/late_lock.rs b/controller/tests/late_lock.rs index 2d1ec29..8783610 100644 --- a/controller/tests/late_lock.rs +++ b/controller/tests/late_lock.rs @@ -18,9 +18,8 @@ extern crate grin_wallet_controller as wallet; extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; -use self::libwallet::{InitTxArgs, Slate}; +use self::libwallet::InitTxArgs; use impls::test_framework::{self, LocalWalletClient}; -use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -28,6 +27,7 @@ use std::time::Duration; #[macro_use] mod common; use common::{clean_output_dir, create_wallet_proxy, setup}; +use libwallet::InitTxSendArgs; /// self send impl fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { @@ -46,7 +46,8 @@ fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -57,7 +58,8 @@ fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -69,26 +71,8 @@ fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { }); // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "mining")?; - Ok(()) - }, - )?; - - // add some accounts - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "account1")?; - Ok(()) - }, - )?; + api1.create_account_path(mask1, "mining")?; + api2.create_account_path(mask2, "account1")?; // Get some mining done { @@ -102,70 +86,75 @@ fn late_lock_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false)?; - let mut slate = Slate::blank(2, false); let amount = 100_000_000_000; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - late_lock: Some(true), - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - println!("S1 SLATE: {}", slate_i); - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - println!("S2 SLATE: {}", slate); + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + late_lock: Some(true), + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; + println!("S1 SLATE: {}", slate_i); + let mut slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + println!("S2 SLATE: {}", slate); - // Note we don't call `tx_lock_outputs` on the sender side here, - // as the outputs will only be locked during finalization + // Note we don't call `tx_lock_outputs` on the sender side here, + // as the outputs will only be locked during finalization - slate = sender_api.finalize_tx(m, &slate)?; - println!("S3 SLATE: {}", slate); + slate = api1.finalize_tx(mask1, &slate)?; + println!("S3 SLATE: {}", slate); - // Now post tx to our node for inclusion in the next block. - sender_api.post_tx(m, &slate, true)?; - - Ok(()) - }, - )?; + // Now post tx to our node for inclusion in the next block. + api1.post_tx(mask1, &slate, true)?; test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false)?; // update/test contents of both accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - // Reward from mining 11 blocks, minus the amount sent. - // Note: We mined the block containing the tx, so fees are effectively refunded. - assert_eq!(560_000_000_000, wallet_info.amount_currently_spendable); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + // Reward from mining 11 blocks, minus the amount sent. + // Note: We mined the block containing the tx, so fees are effectively refunded. + assert_eq!(560_000_000_000, wallet_info.amount_currently_spendable); - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (wallet2_refreshed, wallet_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(amount, wallet_info.amount_currently_spendable); - Ok(()) - }, - )?; + // Make sure outputs are not locked on failed send with arguments. + // Fail sending without starting Tor + api1.set_tor_config(Some(grin_wallet_config::TorConfig { + use_integrated: Some(false), + socks_proxy_addr: "invalid".into(), + ..Default::default() + }))?; + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + late_lock: Some(true), + send_args: Some(InitTxSendArgs { + dest: "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp".into(), + post_tx: false, + fluff: false, + skip_tor: Some(false), + }), + ..Default::default() + }; + let slate = api1.init_send_tx(mask1, args)?; + let (_, txs) = api1.retrieve_txs(mask1, false, None, Some(slate.id), None)?; + assert!(txs.is_empty()); + // Make sure spendable balance not changed. + let (wallet1_refreshed, wallet_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(560_000_000_000, wallet_info.amount_currently_spendable); + + let (wallet2_refreshed, wallet_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(amount, wallet_info.amount_currently_spendable); // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/mwixnet.rs b/controller/tests/mwixnet.rs index 60e4bd8..5ae886b 100644 --- a/controller/tests/mwixnet.rs +++ b/controller/tests/mwixnet.rs @@ -50,7 +50,8 @@ fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - true + true, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -65,16 +66,8 @@ fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - }, - )?; + api1.create_account_path(mask1, "mining")?; + api1.create_account_path(mask1, "listener")?; // Get some mining done { @@ -86,123 +79,84 @@ fn mwixnet_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use( + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api1.init_send_tx(mask1, args)?; + api1.tx_lock_outputs(mask1, &slate)?; + // Send directly to self + wallet::controller::foreign_single_use( wallet1.clone(), - mask1, PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let mut slate = api.init_send_tx(m, args)?; - api.tx_lock_outputs(m, &slate)?; - // Send directly to self - wallet::controller::foreign_single_use( - wallet1.clone(), - PathBuf::from(test_dir), - mask1_i.clone(), - |api| { - slate = api.receive_tx(&slate, Some("listener"), None)?; - Ok(()) - }, - )?; - slate = api.finalize_tx(m, &slate)?; - api.post_tx(m, &slate, false)?; // mines a block - bh += 1; + mask1_i.clone(), + |api| { + slate = api.receive_tx(&slate, Some("listener"), None)?; Ok(()) }, )?; + slate = api1.finalize_tx(mask1, &slate)?; + api1.post_tx(mask1, &slate, false)?; // mines a block + bh += 1; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Check total in mining account - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); // Check total in 'listener' account { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, 2 * reward); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, 2 * reward); // Recipient wallet creates a mwixnet request from the last output - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let secp_locked = util::static_secp_instance(); - let secp = secp_locked.lock(); - let server_pubkey_str_1 = - "97444ae673bb92c713c1a2f7b8882ffbfc1c67401a280a775dce1a8651584332"; - let server_pubkey_str_2 = - "0c9414341f2140ed34a5a12a6479bf5a6404820d001ab81d9d3e8cc38f049b4e"; - let server_pubkey_str_3 = - "b58ece97d60e71bb7e53218400b0d67bfe6a3cb7d3b4a67a44f8fb7c525cbca5"; - let server_key_1 = - SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_1).unwrap()) - .unwrap(); - let server_key_2 = - SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_2).unwrap()) - .unwrap(); - let server_key_3 = - SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_3).unwrap()) - .unwrap(); - let params = MixnetReqCreationParams { - server_keys: vec![server_key_1, server_key_2, server_key_3], - fee_per_hop: 50_000_000, - }; - let outputs = api.retrieve_outputs(mask1, false, false, None)?; - // get last output - let last_output = outputs.1[outputs.1.len() - 1].clone(); + let secp_locked = util::static_secp_instance(); + let secp = secp_locked.lock(); + let server_pubkey_str_1 = "97444ae673bb92c713c1a2f7b8882ffbfc1c67401a280a775dce1a8651584332"; + let server_pubkey_str_2 = "0c9414341f2140ed34a5a12a6479bf5a6404820d001ab81d9d3e8cc38f049b4e"; + let server_pubkey_str_3 = "b58ece97d60e71bb7e53218400b0d67bfe6a3cb7d3b4a67a44f8fb7c525cbca5"; + let server_key_1 = + SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_1).unwrap())?; + let server_key_2 = + SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_2).unwrap())?; + let server_key_3 = + SecretKey::from_slice(&secp, &grin_util::from_hex(&server_pubkey_str_3).unwrap())?; + let params = MixnetReqCreationParams { + server_keys: vec![server_key_1, server_key_2, server_key_3], + fee_per_hop: 50_000_000, + }; + let outputs = api1.retrieve_outputs(mask1, false, false, None)?; + // get last output + let last_output = outputs.1[outputs.1.len() - 1].clone(); - let mwixnet_req = api.create_mwixnet_req(m, ¶ms, &last_output.commit, true)?; + let mwixnet_req = api1.create_mwixnet_req(mask1, ¶ms, &last_output.commit, true)?; - println!("MWIXNET REQ: {:?}", mwixnet_req); + println!("MWIXNET REQ: {:?}", mwixnet_req); - // check output we created comsig for is indeed locked - let outputs = api.retrieve_outputs(mask1, false, false, None)?; - // get last output - let last_output = outputs.1[outputs.1.len() - 1].clone(); - assert!(last_output.output.status == libwallet::OutputStatus::Locked); - - Ok(()) - }, - )?; + // check output we created comsig for is indeed locked + let outputs = api1.retrieve_outputs(mask1, false, false, None)?; + // get last output + let last_output = outputs.1[outputs.1.len() - 1].clone(); + assert_eq!(last_output.output.status, libwallet::OutputStatus::Locked); // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/no_change.rs b/controller/tests/no_change.rs index aa1a56e..503691e 100644 --- a/controller/tests/no_change.rs +++ b/controller/tests/no_change.rs @@ -22,7 +22,7 @@ use std::path::PathBuf; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; -use libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate}; +use libwallet::{InitTxArgs, IssueInvoiceTxArgs}; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -44,7 +44,8 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -57,7 +58,8 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -77,109 +79,67 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let fee = core::libtx::tx_fee(1, 1, 1); // send a single block's worth of transactions with minimal strategy - let mut slate = Slate::blank(2, false); - let mut stored_excess = None; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: reward - fee, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - slate = api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate)?; - api.tx_lock_outputs(m, &slate)?; - slate = api.finalize_tx(m, &slate)?; - println!("Posted Slate: {:?}", slate); - stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); - api.post_tx(m, &slate, false)?; - Ok(()) - }, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: reward - fee, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + let mut slate = api1.init_send_tx(mask1, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate)?; + api1.tx_lock_outputs(mask1, &slate)?; + slate = api1.finalize_tx(mask1, &slate)?; + println!("Posted Slate: {:?}", slate); + let mut stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); + api1.post_tx(mask1, &slate, false)?; // ensure stored excess is correct in both wallets // Wallet 1 calculated the excess with the full slate // Wallet 2 only had the excess provided by // wallet 1 // Refresh and check transaction log for wallet 1 - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(refreshed); - let tx = txs[0].clone(); - println!("SIMPLE SEND - SENDING WALLET"); - println!("{:?}", tx); - println!(); - assert!(tx.confirmed); - assert_eq!(stored_excess, tx.kernel_excess); - Ok(()) - }, - )?; + let (refreshed, txs) = api1.retrieve_txs(mask1, true, None, Some(slate.id), None)?; + assert!(refreshed); + let tx = txs[0].clone(); + println!("SIMPLE SEND - SENDING WALLET"); + println!("{:?}", tx); + println!(); + assert!(tx.confirmed); + assert_eq!(stored_excess, tx.kernel_excess); // Refresh and check transaction log for wallet 2 - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(refreshed); - let tx = txs[0].clone(); - println!("SIMPLE SEND - RECEIVING WALLET"); - println!("{:?}", tx); - println!(); - assert!(tx.confirmed); - assert_eq!(stored_excess, tx.kernel_excess); - Ok(()) - }, - )?; + let (refreshed, txs) = api2.retrieve_txs(mask2, true, None, Some(slate.id), None)?; + assert!(refreshed); + let tx = txs[0].clone(); + println!("SIMPLE SEND - RECEIVING WALLET"); + println!("{:?}", tx); + println!(); + assert!(tx.confirmed); + assert_eq!(stored_excess, tx.kernel_excess); // ensure invoice TX works as well with no change - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - // Wallet 2 inititates an invoice transaction, requesting payment - let args = IssueInvoiceTxArgs { - amount: reward - fee, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - Ok(()) - }, - )?; + // Wallet 2 initiates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward - fee, + ..Default::default() + }; + slate = api2.issue_invoice_tx(mask2, args)?; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // Wallet 1 receives the invoice transaction - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + slate = api1.process_invoice_tx(mask1, &slate, args)?; + api1.tx_lock_outputs(mask1, &slate)?; // wallet 2 finalizes and posts wallet::controller::foreign_single_use( @@ -192,54 +152,30 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { Ok(()) }, )?; - wallet::controller::owner_single_use( - wallet2.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - println!("Invoice Posted TX: {}", slate); - stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); - api.post_tx(m, &slate, false)?; - Ok(()) - }, - )?; + println!("Invoice Posted TX: {}", slate); + stored_excess = Some(slate.tx.as_ref().unwrap().body.kernels[0].excess); + api2.post_tx(mask2, &slate, false)?; // check wallet 2's version - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(refreshed); - for tx in txs { - stored_excess = tx.kernel_excess; - println!("Wallet 2: {:?}", tx); - println!(); - assert!(tx.confirmed); - assert_eq!(stored_excess, tx.kernel_excess); - } - Ok(()) - }, - )?; + let (refreshed, txs) = api2.retrieve_txs(mask2, true, None, Some(slate.id), None)?; + assert!(refreshed); + for tx in txs { + stored_excess = tx.kernel_excess; + println!("Wallet 2: {:?}", tx); + println!(); + assert!(tx.confirmed); + assert_eq!(stored_excess, tx.kernel_excess); + } // Refresh and check transaction log for wallet 1 - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(refreshed); - for tx in txs { - println!("Wallet 1: {:?}", tx); - println!(); - assert_eq!(stored_excess, tx.kernel_excess); - assert!(tx.confirmed); - } - Ok(()) - }, - )?; + let (refreshed, txs) = api1.retrieve_txs(mask1, true, None, Some(slate.id), None)?; + assert!(refreshed); + for tx in txs { + println!("Wallet 1: {:?}", tx); + println!(); + assert_eq!(stored_excess, tx.kernel_excess); + assert!(tx.confirmed); + } // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/payment_proofs.rs b/controller/tests/payment_proofs.rs index 651e486..5c4eeaf 100644 --- a/controller/tests/payment_proofs.rs +++ b/controller/tests/payment_proofs.rs @@ -21,7 +21,6 @@ extern crate grin_wallet_util; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; use libwallet::{InitTxArgs, Slate}; -use std::path::PathBuf; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -45,7 +44,8 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -58,7 +58,8 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -77,99 +78,74 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); - let mut address = None; - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - address = Some(api.get_slatepack_address(m, 0)?); - Ok(()) - }, - )?; + let address = Some(api2.get_slatepack_address(mask2, 0)?); println!("Public address is: {:?}", address); let amount = 60_000_000_000; let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - payment_proof_recipient_address: address.clone(), - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: address.clone(), + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; - assert_eq!( - slate_i.payment_proof.as_ref().unwrap().receiver_address, - address.as_ref().unwrap().pub_key, - ); - println!( - "Sender addr: {:?}", - slate_i.payment_proof.as_ref().unwrap().sender_address - ); + assert_eq!( + slate_i.payment_proof.as_ref().unwrap().receiver_address, + address.as_ref().unwrap().pub_key, + ); + println!( + "Sender addr: {:?}", + slate_i.payment_proof.as_ref().unwrap().sender_address + ); - // Check we are creating a tx with kernel features 0 - // We will check this produces a Plain kernel later. - assert_eq!(0, slate.kernel_features); + // Check we are creating a tx with kernel features 0 + // We will check this produces a Plain kernel later. + assert_eq!(0, slate.kernel_features); - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + api1.tx_lock_outputs(mask1, &slate)?; - // Ensure what's stored in TX log for payment proof is correct - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - assert!(txs[0].payment_proof.is_some()); - let pp = txs[0].clone().payment_proof.unwrap(); - assert_eq!( - pp.receiver_address, - slate_i.payment_proof.as_ref().unwrap().receiver_address - ); - assert!(pp.receiver_signature.is_some()); - assert_eq!(pp.sender_address_path, 0); - assert_eq!(pp.sender_signature, None); + // Ensure what's stored in TX log for payment proof is correct + let (_, txs) = api1.retrieve_txs(mask1, true, None, Some(slate.id), None)?; + assert!(txs[0].payment_proof.is_some()); + let pp = txs[0].clone().payment_proof.unwrap(); + assert_eq!( + pp.receiver_address, + slate_i.payment_proof.as_ref().unwrap().receiver_address + ); + assert!(pp.receiver_signature.is_some()); + assert_eq!(pp.sender_address_path, 0); + assert_eq!(pp.sender_signature, None); - // check we should get an error at this point since proof is not complete - let pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id)); - assert!(pp.is_err()); + // check we should get an error at this point since proof is not complete + let pp = api1.retrieve_payment_proof(mask1, true, None, Some(slate.id)); + assert!(pp.is_err()); - slate = sender_api.finalize_tx(m, &slate)?; - sender_api.post_tx(m, &slate, true)?; - Ok(()) - }, - )?; + slate = api1.finalize_tx(mask1, &slate)?; + api1.post_tx(mask1, &slate, true)?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // Check payment proof here - let mut pp = sender_api.retrieve_payment_proof(m, true, None, Some(slate.id))?; + // Check payment proof here + let mut pp = api1.retrieve_payment_proof(mask1, true, None, Some(slate.id))?; - println!("Payment proof: {:?}", pp); + println!("Payment proof: {:?}", pp); - // verify, should be good - let res = sender_api.verify_payment_proof(m, &pp)?; - assert_eq!(res, (true, false)); + // verify, should be good + let res = api1.verify_payment_proof(mask1, &pp)?; + assert_eq!(res, (true, false)); - // Modify values, should not be good - pp.amount = 20; - let res = sender_api.verify_payment_proof(m, &pp); - assert!(res.is_err()); - Ok(()) - }, - )?; + // Modify values, should not be good + pp.amount = 20; + let res = api1.verify_payment_proof(mask1, &pp); + assert!(res.is_err()); // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/repost.rs b/controller/tests/repost.rs index 0718364..7f3a39b 100644 --- a/controller/tests/repost.rs +++ b/controller/tests/repost.rs @@ -21,7 +21,7 @@ extern crate grin_wallet_libwallet as libwallet; use grin_core as core; use std::path::PathBuf; -use self::libwallet::{InitTxArgs, Slate}; +use self::libwallet::InitTxArgs; use impls::test_framework::{self, LocalWalletClient}; use impls::{PathToSlate, SlateGetter as _, SlatePutter as _}; use std::sync::atomic::Ordering; @@ -49,7 +49,8 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -60,7 +61,8 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -75,28 +77,11 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - }, - )?; + api1.create_account_path(mask1, "mining")?; + api1.create_account_path(mask1, "listener")?; - // add some accounts - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "account1")?; - api.create_account_path(m, "account2")?; - Ok(()) - }, - )?; + api2.create_account_path(mask2, "account1")?; + api2.create_account_path(mask2, "account2")?; // Get some mining done { @@ -110,34 +95,24 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let send_file = format!("{}/part_tx_1.tx", test_dir); let receive_file = format!("{}/part_tx_2.tx", test_dir); - let mut slate = Slate::blank(2, false); - // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - PathToSlate((&send_file).into()).put_tx(&slate, false)?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api1.init_send_tx(mask1, args)?; + PathToSlate((&send_file).into()).put_tx(&slate, false)?; + api1.tx_lock_outputs(mask1, &slate)?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; @@ -167,69 +142,37 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> } // wallet 1 finalize - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - slate = PathToSlate((&receive_file).into()).get_tx()?.0; - slate = api.finalize_tx(m, &slate)?; - Ok(()) - }, - )?; + slate = PathToSlate((&receive_file).into()).get_tx()?.0; + slate = api1.finalize_tx(mask1, &slate)?; // Now repost from cached - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - println!("TXS[0]: {:?}", txs[0]); - let stored_tx = api.get_stored_tx(m, None, Some(&txs[0].tx_slate_id.unwrap()))?; - println!("Stored tx: {:?}", stored_tx); - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - }, - )?; + let (_, txs) = api1.retrieve_txs(mask1, true, None, Some(slate.id), None)?; + println!("TXS[0]: {:?}", txs[0]); + let stored_tx = api1.get_stored_tx(mask1, None, Some(&txs[0].tx_slate_id.unwrap()))?; + println!("Stored tx: {:?}", stored_tx); + api1.post_tx(mask1, &slate, false)?; + bh += 1; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // update/test contents of both accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, bh); - assert_eq!(wallet2_info.total, 2 * reward); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, 2 * reward); - // as above, but syncronously + // as above, but synchronously { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("mining")?; @@ -239,78 +182,45 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> w.set_parent_key_id_by_name("account1")?; } - let mut slate = Slate::blank(2, false); let amount = 60_000_000_000; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - Ok(()) - }, - )?; + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + api1.tx_lock_outputs(mask1, &slate)?; + slate = api1.finalize_tx(mask1, &slate)?; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Now repost from cached - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let stored_tx_slate = api.get_stored_tx(m, Some(txs[0].id), None)?.unwrap(); - api.post_tx(m, &stored_tx_slate, false)?; - bh += 1; - Ok(()) - }, - )?; + let (_, txs) = api1.retrieve_txs(mask1, true, None, Some(slate.id), None)?; + let stored_tx_slate = api1.get_stored_tx(mask1, Some(txs[0].id), None)?.unwrap(); + api1.post_tx(mask1, &stored_tx_slate, false)?; + bh += 1; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; - // - // update/test contents of both accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 4); - Ok(()) - }, - )?; - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, bh); - assert_eq!(wallet2_info.total, 2 * amount); - Ok(()) - }, - )?; + // update/test contents of both accounts + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 4); + + let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * amount); // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/revert.rs b/controller/tests/revert.rs index ef5a887..6c4c8a6 100644 --- a/controller/tests/revert.rs +++ b/controller/tests/revert.rs @@ -24,14 +24,13 @@ use grin_core::global; use grin_keychain::ExtKeychain; use grin_util::secp::key::SecretKey; use grin_util::Mutex; -use grin_wallet_controller::controller::owner_single_use as owner; +use grin_wallet_api::Owner; use grin_wallet_impls::test_framework::*; use grin_wallet_impls::{DefaultLCProvider, PathToSlate, SlatePutter}; use grin_wallet_libwallet as libwallet; use grin_wallet_libwallet::api_impl::types::InitTxArgs; use grin_wallet_libwallet::WalletInst; use log::error; -use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; @@ -63,6 +62,8 @@ fn revert( Option, Wallet, Option, + Owner, LocalWalletClient, ExtKeychain>, + Owner, LocalWalletClient, ExtKeychain>, ), libwallet::Error, > { @@ -82,7 +83,8 @@ fn revert( "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = mask1_i.as_ref(); @@ -94,28 +96,23 @@ fn revert( "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = mask2_i.as_ref(); // Set the wallet proxy listener running - std::thread::spawn(move || { + thread::spawn(move || { if let Err(e) = wallet_proxy.run() { error!("Wallet Proxy error: {}", e); } }); - owner(wallet1.clone(), mask1, PathBuf::from(test_dir), |api, m| { - api.create_account_path(m, "a")?; - api.set_active_account(m, "a")?; - Ok(()) - })?; + api1.create_account_path(mask1, "a")?; + api1.set_active_account(mask1, "a")?; - owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { - api.create_account_path(m, "b")?; - api.set_active_account(m, "b")?; - Ok(()) - })?; + api2.create_account_path(mask2, "b")?; + api2.set_active_account(mask2, "b")?; let reward = core::consensus::REWARD; let cm = global::coinbase_maturity() as u64; @@ -126,76 +123,60 @@ fn revert( award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false)?; // Sanity check contents - owner(wallet1.clone(), mask1, PathBuf::from(test_dir), |api, m| { - let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, bh * reward); - assert_eq!(info.amount_currently_spendable, (bh - cm) * reward); - assert_eq!(info.amount_reverted, 0); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); - assert_eq!(info.total, c); - assert_eq!(txs.len(), bh as usize); - Ok(()) - })?; + let (refreshed, info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(refreshed); + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, bh * reward); + assert_eq!(info.amount_currently_spendable, (bh - cm) * reward); + assert_eq!(info.amount_reverted, 0); + // check tx log as well + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); + assert_eq!(info.total, c); + assert_eq!(txs.len(), bh as usize); - owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { - let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, 0); - assert_eq!(info.amount_currently_spendable, 0); - assert_eq!(info.amount_reverted, 0); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 0); - Ok(()) - })?; + let (refreshed, info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, 0); + assert_eq!(info.amount_currently_spendable, 0); + assert_eq!(info.amount_reverted, 0); + // check tx log as well + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 0); // Send some funds - let mut tx = None; - owner(wallet1.clone(), mask1, PathBuf::from(test_dir), |api, m| { - // send to send - let args = InitTxArgs { - src_acct_name: None, - amount: sent, - minimum_confirmations: cm, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // output tx file - let send_file = format!("{}/part_tx_1.tx", test_dir); - PathToSlate(send_file.into()).put_tx(&slate, false)?; - api.tx_lock_outputs(m, &slate)?; - let slate = client1.send_tx_slate_direct("wallet2", &slate)?; - let slate = api.finalize_tx(m, &slate)?; - tx = slate.tx; - - Ok(()) - })?; - let tx = tx.expect("tx from slate"); + // send to send + let args = InitTxArgs { + src_acct_name: None, + amount: sent, + minimum_confirmations: cm, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + let slate = api1.init_send_tx(mask1, args)?; + // output tx file + let send_file = format!("{}/part_tx_1.tx", test_dir); + PathToSlate(send_file.into()).put_tx(&slate, false)?; + api1.tx_lock_outputs(mask1, &slate)?; + let slate = client1.send_tx_slate_direct("wallet2", &slate)?; + let slate = api1.finalize_tx(mask1, &slate)?; + let tx = slate.tx.expect("tx from slate"); // Check funds have been received - owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { - let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, 0); - assert_eq!(info.amount_currently_spendable, 0); - assert_eq!(info.amount_reverted, 0); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - let tx = &txs[0]; - assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived); - assert!(!tx.confirmed); - Ok(()) - })?; + let (refreshed, info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, 0); + assert_eq!(info.amount_currently_spendable, 0); + assert_eq!(info.amount_reverted, 0); + // check tx log as well + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 1); + assert_eq!(txs[0].tx_type, libwallet::TxLogEntryType::TxReceived); + assert!(!&txs[0].confirmed); // Update parallel chain assert_eq!(chain2.head_header().unwrap().height, 0); @@ -223,23 +204,19 @@ fn revert( let bh = bh + 1; // Check funds have been confirmed - owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { - let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, sent); - assert_eq!(info.amount_currently_spendable, sent); - assert_eq!(info.amount_reverted, 0); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - let tx = &txs[0]; - assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived); - assert!(tx.confirmed); - assert!(tx.kernel_excess.is_some()); - assert!(tx.reverted_after.is_none()); - Ok(()) - })?; + let (refreshed, info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, sent); + assert_eq!(info.amount_currently_spendable, sent); + assert_eq!(info.amount_reverted, 0); + // check tx log as well + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 1); + assert_eq!(txs[0].tx_type, libwallet::TxLogEntryType::TxReceived); + assert!(&txs[0].confirmed); + assert!(&txs[0].kernel_excess.is_some()); + assert!(&txs[0].reverted_after.is_none()); // Attach more blocks to the parallel chain, making it the longest one award_block_to_wallet(&chain2, &[], wallet1.clone(), mask1)?; @@ -258,32 +235,29 @@ fn revert( let bh = bh + 1; // Check funds have been reverted - owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { - api.scan(m, None, false)?; - let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, 0); - assert_eq!(info.amount_currently_spendable, 0); - assert_eq!(info.amount_reverted, sent); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - let tx = &txs[0]; - assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReverted); - assert!(!tx.confirmed); - assert!(tx.reverted_after.is_some()); - Ok(()) - })?; + api2.scan(mask2, None, false)?; + let (refreshed, info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, 0); + assert_eq!(info.amount_currently_spendable, 0); + assert_eq!(info.amount_reverted, sent); + // check tx log as well + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 1); + assert_eq!(txs[0].tx_type, libwallet::TxLogEntryType::TxReverted); + assert!(!&txs[0].confirmed); + assert!(&txs[0].reverted_after.is_some()); stopper2.store(false, Ordering::Relaxed); Ok(( - chain, stopper, sent, bh, tx, wallet1, mask1_i, wallet2, mask2_i, + chain, stopper, sent, bh, tx, wallet1, mask1_i, wallet2, mask2_i, api1, api2, )) } fn revert_reconfirm_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - let (chain, stopper, sent, bh, tx, wallet1, mask1_i, wallet2, mask2_i) = revert(test_dir)?; + let (chain, stopper, sent, bh, tx, wallet1, mask1_i, _wallet2, mask2_i, _api1, api2) = + revert(test_dir)?; let mask1 = mask1_i.as_ref(); let mask2 = mask2_i.as_ref(); @@ -293,22 +267,19 @@ fn revert_reconfirm_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let bh = bh + 1; // Check funds have been confirmed again - owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { - let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, sent); - assert_eq!(info.amount_currently_spendable, sent); - assert_eq!(info.amount_reverted, 0); - // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - let tx = &txs[0]; - assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived); - assert!(tx.confirmed); - assert!(tx.reverted_after.is_none()); - Ok(()) - })?; + let (refreshed, info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, sent); + assert_eq!(info.amount_currently_spendable, sent); + assert_eq!(info.amount_reverted, 0); + // check tx log as well + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 1); + let tx = &txs[0]; + assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived); + assert!(tx.confirmed); + assert!(tx.reverted_after.is_none()); // let logging finish stopper.store(false, Ordering::Relaxed); @@ -317,41 +288,37 @@ fn revert_reconfirm_impl(test_dir: &'static str) -> Result<(), libwallet::Error> } fn revert_cancel_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { - let (_, stopper, sent, bh, _, _, _, wallet2, mask2_i) = revert(test_dir)?; + let (_, stopper, sent, bh, _, _, _, _wallet2, mask2_i, _api1, api2) = revert(test_dir)?; let mask2 = mask2_i.as_ref(); - // Cancelling tx - owner(wallet2.clone(), mask2, PathBuf::from(test_dir), |api, m| { - // Sanity check - let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, 0); - assert_eq!(info.amount_currently_spendable, 0); - assert_eq!(info.amount_reverted, sent); + // Sanity check + let (refreshed, info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, 0); + assert_eq!(info.amount_currently_spendable, 0); + assert_eq!(info.amount_reverted, sent); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - let tx = &txs[0]; + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 1); + let tx = &txs[0]; - // Cancel - api.cancel_tx(m, Some(tx.id), None)?; + // Cancel + api2.cancel_tx(mask2, Some(tx.id), None)?; - // Check updated summary info - let (refreshed, info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(info.last_confirmed_height, bh); - assert_eq!(info.total, 0); - assert_eq!(info.amount_currently_spendable, 0); - assert_eq!(info.amount_reverted, 0); + // Check updated summary info + let (refreshed, info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + assert_eq!(info.last_confirmed_height, bh); + assert_eq!(info.total, 0); + assert_eq!(info.amount_currently_spendable, 0); + assert_eq!(info.amount_reverted, 0); - // Check updated tx log - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert_eq!(txs.len(), 1); - let tx = &txs[0]; - assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceivedCancelled); - Ok(()) - })?; + // Check updated tx log + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert_eq!(txs.len(), 1); + let tx = &txs[0]; + assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceivedCancelled); // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/self_send.rs b/controller/tests/self_send.rs index 6cf572e..c6ca437 100644 --- a/controller/tests/self_send.rs +++ b/controller/tests/self_send.rs @@ -48,7 +48,8 @@ fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - true + true, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -63,16 +64,8 @@ fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - }, - )?; + api1.create_account_path(mask1, "mining")?; + api1.create_account_path(mask1, "listener")?; // Get some mining done { @@ -84,78 +77,54 @@ fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); // Should have 5 in account1 (5 spendable), 5 in account (2 spendable) - wallet::controller::owner_single_use( + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api1.init_send_tx(mask1, args)?; + api1.tx_lock_outputs(mask1, &slate)?; + // Send directly to self + wallet::controller::foreign_single_use( wallet1.clone(), - mask1, PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let mut slate = api.init_send_tx(m, args)?; - api.tx_lock_outputs(m, &slate)?; - // Send directly to self - wallet::controller::foreign_single_use( - wallet1.clone(), - PathBuf::from(test_dir), - mask1_i.clone(), - |api| { - slate = api.receive_tx(&slate, Some("listener"), None)?; - Ok(()) - }, - )?; - slate = api.finalize_tx(m, &slate)?; - api.post_tx(m, &slate, false)?; // mines a block - bh += 1; + mask1_i.clone(), + |api| { + slate = api.receive_tx(&slate, Some("listener"), None)?; Ok(()) }, )?; + slate = api1.finalize_tx(mask1, &slate)?; + api1.post_tx(mask1, &slate, false)?; // mines a block + bh += 1; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Check total in mining account - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); // Check total in 'listener' account { wallet_inst!(wallet1, w); w.set_parent_key_id_by_name("listener")?; } - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, 2 * reward); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, 2 * reward); // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/slatepack.rs b/controller/tests/slatepack.rs index 9bdd740..d53f1c4 100644 --- a/controller/tests/slatepack.rs +++ b/controller/tests/slatepack.rs @@ -100,7 +100,8 @@ fn slatepack_exchange_test_impl( "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -111,7 +112,8 @@ fn slatepack_exchange_test_impl( "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -126,28 +128,11 @@ fn slatepack_exchange_test_impl( let reward = core::consensus::REWARD; // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - }, - )?; + api1.create_account_path(mask1, "mining")?; + api1.create_account_path(mask1, "listener")?; - // add some accounts - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "account1")?; - api.create_account_path(m, "account2")?; - Ok(()) - }, - )?; + api2.create_account_path(mask2, "account1")?; + api2.create_account_path(mask2, "account2")?; // Get some mining done { @@ -160,19 +145,9 @@ fn slatepack_exchange_test_impl( let (recipients_1, dec_key_1, sender_1) = match use_encryption { true => { - let mut rec_address = SlatepackAddress::random(); - let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - sec_key = api.get_slatepack_secret_key(m, 0)?; - let pub_key = edDalekPublicKey::from(&sec_key); - rec_address = SlatepackAddress::new(&pub_key); - Ok(()) - }, - )?; + let sec_key = api1.get_slatepack_secret_key(mask1, 0)?; + let pub_key = edDalekPublicKey::from(&sec_key); + let rec_address = SlatepackAddress::new(&pub_key); ( vec![rec_address.clone()], Some(sec_key), @@ -184,19 +159,9 @@ fn slatepack_exchange_test_impl( let (recipients_2, dec_key_2, sender_2) = match use_encryption { true => { - let mut rec_address = SlatepackAddress::random(); - let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]); - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - sec_key = api.get_slatepack_secret_key(m, 0)?; - let pub_key = edDalekPublicKey::from(&sec_key); - rec_address = SlatepackAddress::new(&pub_key); - Ok(()) - }, - )?; + let sec_key = api2.get_slatepack_secret_key(mask2, 0)?; + let pub_key = edDalekPublicKey::from(&sec_key); + let rec_address = SlatepackAddress::new(&pub_key); ( vec![rec_address.clone()], Some(sec_key), @@ -219,39 +184,31 @@ fn slatepack_exchange_test_impl( ), }; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward); - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // output tx file - output_slatepack( - &slate, - &send_file, - use_armored, - use_bin, - sender_1.clone(), - recipients_2.clone(), - )?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward); + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api1.init_send_tx(mask1, args)?; + // output tx file + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_1.clone(), + recipients_2.clone(), )?; + api1.tx_lock_outputs(mask1, &slate)?; // Get some mining done { @@ -286,52 +243,27 @@ fn slatepack_exchange_test_impl( )?; // wallet 1 finalizes and posts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, mut slate) = - slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; - slate = api.finalize_tx(m, &slate)?; - // Output final file for reference - output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - }, - )?; + let (_, mut slate) = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; + slate = api1.finalize_tx(mask1, &slate)?; + // Output final file for reference + output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; + api1.post_tx(mask1, &slate, false)?; + bh += 1; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); bh += 3; // Check total in mining account - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet1_refreshed); - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.total, bh * reward - reward * 2); - Ok(()) - }, - )?; + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(wallet1_refreshed); + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.total, bh * reward - reward * 2); // Check total in 'wallet 2' account - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.last_confirmed_height, bh); - assert_eq!(wallet2_info.total, 2 * reward); - Ok(()) - }, - )?; + let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.last_confirmed_height, bh); + assert_eq!(wallet2_info.total, 2 * reward); // Now other types of exchange, for reference // Invoice transaction @@ -348,61 +280,43 @@ fn slatepack_exchange_test_impl( ), }; - let mut slate = Slate::blank(2, true); - - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let args = IssueInvoiceTxArgs { - amount: 1000000000, - ..Default::default() - }; - slate = api.issue_invoice_tx(m, args)?; - output_slatepack( - &slate, - &send_file, - use_armored, - use_bin, - sender_2.clone(), - recipients_1.clone(), - )?; - Ok(()) - }, + let args = IssueInvoiceTxArgs { + amount: 1000000000, + ..Default::default() + }; + let mut slate = api2.issue_invoice_tx(mask2, args)?; + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_2.clone(), + recipients_1.clone(), )?; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let res = slate_from_packed(&send_file, use_armored, (&dec_key_1).as_ref())?; - slatepack = res.0; - slate = res.1; - slate = api.process_invoice_tx(m, &slate, args)?; - api.tx_lock_outputs(m, &slate)?; - output_slatepack( - &slate, - &receive_file, - use_armored, - use_bin, - sender_1.clone(), - match slatepack.sender.clone() { - Some(s) => vec![s.clone()], - None => vec![], - }, - )?; - Ok(()) + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let res = slate_from_packed(&send_file, use_armored, (&dec_key_1).as_ref())?; + slatepack = res.0; + slate = res.1; + slate = api1.process_invoice_tx(mask1, &slate, args)?; + api1.tx_lock_outputs(mask1, &slate)?; + output_slatepack( + &slate, + &receive_file, + use_armored, + use_bin, + sender_1.clone(), + match slatepack.sender.clone() { + Some(s) => vec![s.clone()], + None => vec![], }, )?; wallet::controller::foreign_single_use( @@ -418,15 +332,7 @@ fn slatepack_exchange_test_impl( Ok(()) }, )?; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - }, - )?; + api1.post_tx(mask1, &slate, false)?; // Standard, with payment proof let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); @@ -443,47 +349,29 @@ fn slatepack_exchange_test_impl( ), }; - let mut slate = Slate::blank(2, true); - let mut address = None; - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - address = Some(api.get_slatepack_address(m, 0)?); - Ok(()) - }, - )?; + let address = Some(api2.get_slatepack_address(mask2, 0)?); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // send to send - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - payment_proof_recipient_address: address.clone(), - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - output_slatepack( - &slate, - &send_file, - use_armored, - use_bin, - sender_1, - recipients_2.clone(), - )?; - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, + // send to send + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + payment_proof_recipient_address: address.clone(), + ..Default::default() + }; + let mut slate = api1.init_send_tx(mask1, args)?; + output_slatepack( + &slate, + &send_file, + use_armored, + use_bin, + sender_1, + recipients_2.clone(), )?; + api1.tx_lock_outputs(mask1, &slate)?; wallet::controller::foreign_single_use( wallet2.clone(), @@ -509,22 +397,13 @@ fn slatepack_exchange_test_impl( }, )?; - // wallet 1 finalises and posts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let res = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; - slate = res.1; - slate = api.finalize_tx(m, &slate)?; - // Output final file for reference - output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; - api.post_tx(m, &slate, false)?; - bh += 1; - Ok(()) - }, - )?; + // wallet 1 finalizes and posts + let res = slate_from_packed(&receive_file, use_armored, (&dec_key_1).as_ref())?; + slate = res.1; + slate = api1.finalize_tx(mask1, &slate)?; + // Output final file for reference + output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; + api1.post_tx(mask1, &slate, false)?; // let logging finish stopper.store(false, Ordering::Relaxed); @@ -550,7 +429,8 @@ fn slatepack_api_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -569,32 +449,24 @@ fn slatepack_api_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), - amount: reward * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate = api.init_send_tx(m, args)?; - // create an encrypted slatepack (just encrypted for self) - let enc_addr = api.get_slatepack_address(m, 0)?; - let slatepack = api.create_slatepack_message(m, &slate, Some(0), vec![enc_addr])?; - println!("{}", slatepack); - let slatepack_raw = api.decode_slatepack_message(m, slatepack.clone(), vec![0])?; - println!("{}", slatepack_raw); - let decoded_slate = api.slate_from_slatepack_message(m, slatepack, vec![0])?; - println!("{}", decoded_slate); - Ok(()) - }, - )?; + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api1.init_send_tx(mask1, args)?; + // create an encrypted slatepack (just encrypted for self) + let enc_addr = api1.get_slatepack_address(mask1, 0)?; + let slatepack = api1.create_slatepack_message(mask1, &slate, Some(0), vec![enc_addr])?; + println!("{}", slatepack); + let slatepack_raw = api1.decode_slatepack_message(mask1, slatepack.clone(), vec![0])?; + println!("{}", slatepack_raw); + let decoded_slate = api1.slate_from_slatepack_message(mask1, slatepack, vec![0])?; + println!("{}", decoded_slate); // let logging finish stopper.store(false, Ordering::Relaxed); @@ -609,8 +481,7 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet: let stopper = wallet_proxy.running.clone(); let config_path = PathBuf::from(test_dir).join("grin-wallet.toml"); GlobalWalletConfig::for_chain(&core::global::ChainTypes::AutomatedTesting, &config_path) - .write_to_file(config_path.to_str().unwrap(), false, None, None) - .unwrap(); + .write_to_file(config_path.to_str().unwrap(), false, None, None)?; create_wallet_and_add!( client1, @@ -620,7 +491,8 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet: "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -632,7 +504,8 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet: "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -644,62 +517,57 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet: let reward = core::consensus::REWARD; let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 6, false); - let mut slate = Slate::blank(2, true); - - wallet::controller::owner_single_use(wallet2.clone(), mask2, config_path.clone(), |api, m| { - slate = api.issue_invoice_tx( - m, - IssueInvoiceTxArgs { - amount: reward, - ..Default::default() - }, - )?; - Ok(()) - })?; - - wallet::controller::owner_single_use(wallet1.clone(), mask1, config_path, |api, m| { - let args = InitTxArgs { - src_acct_name: Some("mining".to_owned()), + let slate = api2.issue_invoice_tx( + mask2, + IssueInvoiceTxArgs { amount: reward, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, ..Default::default() - }; + }, + )?; - let mut wrong_net_args = args.clone(); - wrong_net_args.send_args = Some(InitTxSendArgs { - dest: "grin1dvge9z4uqgqlpspmljrd7smh3grrw9xu2r9lkz3u67s3emj3ud2sd5gk9p".to_string(), - post_tx: false, - fluff: false, - skip_tor: Some(true), - }); - assert!(api.init_send_tx(m, wrong_net_args.clone()).is_err()); - assert!(api.process_invoice_tx(m, &slate, wrong_net_args).is_err()); + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; - let mut invalid_args = args.clone(); - invalid_args.send_args = Some(InitTxSendArgs { - dest: "tgrinaddr10qlk22rxjap2ny8qltc2tl996kenxr3hhwuu6hrzs6tdq08yaqgqnlumr7" - .to_string(), - post_tx: false, - fluff: false, - skip_tor: Some(true), - }); - assert!(api.init_send_tx(m, invalid_args.clone()).is_err()); - assert!(api.process_invoice_tx(m, &slate, invalid_args).is_err()); + let mut wrong_net_args = args.clone(); + wrong_net_args.send_args = Some(InitTxSendArgs { + dest: "grin1dvge9z4uqgqlpspmljrd7smh3grrw9xu2r9lkz3u67s3emj3ud2sd5gk9p".to_string(), + post_tx: false, + fluff: false, + skip_tor: Some(true), + }); + assert!(api1.init_send_tx(mask1, wrong_net_args.clone()).is_err()); + assert!(api1 + .process_invoice_tx(mask1, &slate, wrong_net_args) + .is_err()); - let mut valid_args = args; - valid_args.send_args = Some(InitTxSendArgs { - dest: "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp".to_string(), - post_tx: false, - fluff: false, - skip_tor: Some(true), - }); - api.process_invoice_tx(m, &slate, valid_args.clone())?; - api.init_send_tx(m, valid_args)?; - Ok(()) - })?; + let mut invalid_args = args.clone(); + invalid_args.send_args = Some(InitTxSendArgs { + dest: "tgrinaddr10qlk22rxjap2ny8qltc2tl996kenxr3hhwuu6hrzs6tdq08yaqgqnlumr7".to_string(), + post_tx: false, + fluff: false, + skip_tor: Some(true), + }); + assert!(api1.init_send_tx(mask1, invalid_args.clone()).is_err()); + assert!(api1 + .process_invoice_tx(mask1, &slate, invalid_args) + .is_err()); + + let mut valid_args = args; + valid_args.send_args = Some(InitTxSendArgs { + dest: "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp".to_string(), + post_tx: false, + fluff: false, + skip_tor: Some(true), + }); + api1.process_invoice_tx(mask1, &slate, valid_args.clone())?; + api1.init_send_tx(mask1, valid_args)?; stopper.store(false, Ordering::Relaxed); proxy_thread.join().expect("wallet proxy thread panicked"); @@ -710,7 +578,7 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet: fn slatepack_exchange_json() { let test_dir = "test_output/slatepack_exchange_json"; setup(test_dir); - // Json output + // JSON output if let Err(e) = slatepack_exchange_test_impl(test_dir, false, false, false) { panic!("Libwallet Error: {}", e); } @@ -743,7 +611,7 @@ fn slatepack_exchange_armored() { fn slatepack_exchange_json_enc() { let test_dir = "test_output/slatepack_exchange_json_enc"; setup(test_dir); - // Json output + // JSON output if let Err(e) = slatepack_exchange_test_impl(test_dir, false, false, true) { panic!("Libwallet Error: {}", e); } @@ -776,7 +644,7 @@ fn slatepack_exchange_armored_enc() { fn slatepack_api() { let test_dir = "test_output/slatepack_api"; setup(test_dir); - // Json output + // JSON output if let Err(e) = slatepack_api_impl(test_dir) { panic!("Libwallet Error: {}", e); } diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 107c17d..6613095 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -33,6 +33,8 @@ use std::time::Duration; mod common; use common::{clean_output_dir, create_wallet_proxy, setup}; +use grin_wallet_api::Owner; +use libwallet::InitTxSendArgs; /// Exercises the Transaction API fully with a test NodeClient operating /// directly on a chain instance @@ -51,7 +53,8 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> "wallet1", None, &mut wallet_proxy, - true + true, + api1 ); let mask1 = (&mask1_i).as_ref(); println!("Mask1: {:?}", mask1); @@ -63,7 +66,8 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); println!("Mask2: {:?}", mask2); @@ -81,459 +85,425 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> // mine a few blocks let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false); - // Check wallet 1 contents are as expected - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!( - "Wallet 1 Info Pre-Transaction, after {} blocks: {:?}", - wallet1_info.last_confirmed_height, wallet1_info - ); - assert!(wallet1_refreshed); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm) * reward - ); - assert_eq!(wallet1_info.amount_immature, cm * reward); - Ok(()) - }, - )?; + // Check wallet1 contents are as expected + { + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + debug!( + "Wallet 1 Info Pre-Transaction, after {} blocks: {:?}", + wallet1_info.last_confirmed_height, wallet1_info + ); + assert!(wallet1_refreshed); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm) * reward + ); + assert_eq!(wallet1_info.amount_immature, cm * reward); + } // assert wallet contents // and a single use api for a send command let amount = 60_000_000_000; let header_version = core::consensus::header_version(chain.head().unwrap().height).0; let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // Do not allow to send 0 - let error = sender_api - .init_send_tx( - m, - InitTxArgs { - amount: 0, - ..Default::default() - }, - ) - .unwrap_err(); - assert_eq!(error, libwallet::Error::InvalidAmount); + { + // Do not allow to send 0 + let error = api1 + .init_send_tx( + mask1, + InitTxArgs { + amount: 0, + ..Default::default() + }, + ) + .unwrap_err(); + assert_eq!(error, libwallet::Error::InvalidAmount); - // Result amount can not be 0 when amount includes fee - let mut init_args = InitTxArgs { - amount_includes_fee: Some(true), - amount: 23_000_000, - estimate_only: Some(true), - ..Default::default() - }; - let est = sender_api.init_send_tx(m, init_args.clone())?; - assert_eq!(init_args.amount, est.fee_fields.fee()); + // Result amount can not be 0 when amount includes fee + let mut init_args = InitTxArgs { + amount_includes_fee: Some(true), + amount: 23_000_000, + estimate_only: Some(true), + ..Default::default() + }; + let est = api1.init_send_tx(mask1, init_args.clone())?; + assert_eq!(init_args.amount, est.fee_fields.fee()); - init_args.estimate_only = None; - let error = sender_api.init_send_tx(m, init_args).unwrap_err(); - assert_eq!( - error, - libwallet::Error::GenericError( - "Transaction amount is too small to include fee".to_string() - ) - ); + init_args.estimate_only = None; + let error = api1.init_send_tx(mask1, init_args).unwrap_err(); + assert_eq!( + error, + libwallet::Error::GenericError( + "Transaction amount is too small to include fee".to_string() + ) + ); - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; - assert_eq!(slate_i.state, SlateState::Standard1); - assert_eq!(slate_i.version_info.block_header_version, header_version); + assert_eq!(slate_i.state, SlateState::Standard1); + assert_eq!(slate_i.version_info.block_header_version, header_version); - wallet::controller::foreign_single_use( + wallet::controller::foreign_single_use( + wallet2.clone(), + PathBuf::from(test_dir), + mask2_i.clone(), + |api| { + let mut zero_amount_slate = slate_i.clone(); + zero_amount_slate.amount = 0; + assert_eq!( + api.receive_tx(&zero_amount_slate, None, None).unwrap_err(), + libwallet::Error::InvalidAmount + ); + Ok(()) + }, + )?; + + // Check we are creating a tx with the expected lock_height of 0. + // We will check this produces a Plain kernel later. + assert_eq!(0, slate.kernel_features); + + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + assert_eq!(slate.state, SlateState::Standard2); + + // wallet2 already has this slate, so accepted versions fail on the duplicate check + for bhv in 1..=GRIN_BLOCK_HEADER_VERSION + 1 { + let mut s = slate_i.clone(); + s.version_info.block_header_version = bhv; + let res = wallet::controller::foreign_single_use( wallet2.clone(), PathBuf::from(test_dir), mask2_i.clone(), - |api| { - let mut zero_amount_slate = slate_i.clone(); - zero_amount_slate.amount = 0; - assert_eq!( - api.receive_tx(&zero_amount_slate, None, None).unwrap_err(), - libwallet::Error::InvalidAmount - ); - Ok(()) - }, - )?; - - // Check we are creating a tx with the expected lock_height of 0. - // We will check this produces a Plain kernel later. - assert_eq!(0, slate.kernel_features); - - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - assert_eq!(slate.state, SlateState::Standard2); - - // wallet2 already has this slate, so accepted versions fail on the duplicate check - for bhv in 1..=GRIN_BLOCK_HEADER_VERSION + 1 { - let mut s = slate_i.clone(); - s.version_info.block_header_version = bhv; - let res = wallet::controller::foreign_single_use( - wallet2.clone(), - PathBuf::from(test_dir), - mask2_i.clone(), - |api| api.receive_tx(&s, None, None).map(|_| ()), - ); - if bhv <= GRIN_BLOCK_HEADER_VERSION { - assert!(matches!( - res, - Err(libwallet::Error::TransactionAlreadyReceived(_)) - )); - } else { - assert!(matches!(res, Err(libwallet::Error::Compatibility(_)))); - } - } - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - assert_eq!(slate.state, SlateState::Standard3); - - // Check we have a single kernel and that it is a Plain kernel (no lock_height). - // fees for 7 inputs, 2 outputs, 1 kernel (weight 52) - assert_eq!(slate.tx_or_err()?.kernels().len(), 1); - assert_eq!( - slate - .tx_or_err()? - .kernels() - .first() - .map(|k| k.features) - .unwrap(), - transaction::KernelFeatures::Plain { - fee: 26_000_000.into() - } + |api| api.receive_tx(&s, None, None).map(|_| ()), ); + if bhv <= GRIN_BLOCK_HEADER_VERSION { + assert!(matches!( + res, + Err(libwallet::Error::TransactionAlreadyReceived(_)) + )); + } else { + assert!(matches!(res, Err(libwallet::Error::Compatibility(_)))); + } + } + api1.tx_lock_outputs(mask1, &slate)?; + slate = api1.finalize_tx(mask1, &slate)?; + assert_eq!(slate.state, SlateState::Standard3); - Ok(()) - }, - )?; + // Check we have a single kernel and that it is a Plain kernel (no lock_height). + // fees for 7 inputs, 2 outputs, 1 kernel (weight 52) + assert_eq!(slate.tx_or_err()?.kernels().len(), 1); + assert_eq!( + slate + .tx_or_err()? + .kernels() + .first() + .map(|k| k.features) + .unwrap(), + transaction::KernelFeatures::Plain { + fee: 26_000_000.into() + } + ); + } // Check transaction log for wallet 1 - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let fee = core::libtx::tx_fee( - wallet1_info.last_confirmed_height as usize - cm as usize, - 2, - 1, - ); - // we should have a transaction entry for this slate - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(!tx.confirmed); - assert!(tx.confirmation_ts.is_none()); - assert_eq!(tx.amount_debited - tx.amount_credited, fee + amount); - println!("tx: {:?}", tx); - assert_eq!(Some(fee.try_into().unwrap()), tx.fee); - Ok(()) - }, - )?; + { + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + let (refreshed, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert!(refreshed); + let fee = core::libtx::tx_fee( + wallet1_info.last_confirmed_height as usize - cm as usize, + 2, + 1, + ); + // we should have a transaction entry for this slate + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(!tx.confirmed); + assert!(tx.confirmation_ts.is_none()); + assert_eq!(tx.amount_debited - tx.amount_credited, fee + amount); + println!("tx: {:?}", tx); + assert_eq!(Some(fee.try_into().unwrap()), tx.fee); + } // Check transaction log for wallet 2 - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - // we should have a transaction entry for this slate - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(!tx.confirmed); - assert!(tx.confirmation_ts.is_none()); - assert_eq!(amount, tx.amount_credited); - assert_eq!(0, tx.amount_debited); - assert_eq!(None, tx.fee); - Ok(()) - }, - )?; + { + let (refreshed, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert!(refreshed); + // we should have a transaction entry for this slate + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(!tx.confirmed); + assert!(tx.confirmation_ts.is_none()); + assert_eq!(amount, tx.amount_credited); + assert_eq!(0, tx.amount_debited); + assert_eq!(None, tx.fee); + } // post transaction - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.post_tx(m, &slate, false)?; - Ok(()) - }, - )?; + api1.post_tx(mask1, &slate, false)?; - // Check wallet 1 contents are as expected - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!( - "Wallet 1 Info Post Transaction, after {} blocks: {:?}", - wallet1_info.last_confirmed_height, wallet1_info - ); - let fee = core::libtx::tx_fee( - wallet1_info.last_confirmed_height as usize - 1 - cm as usize, - 2, - 1, - ); - assert!(wallet1_refreshed); - // wallet 1 received fees, so amount should be the same - assert_eq!( - wallet1_info.total, - amount * wallet1_info.last_confirmed_height - amount - ); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm) * reward - amount - fee - ); - assert_eq!(wallet1_info.amount_immature, cm * reward + fee); + // Check wallet1 contents are as expected + { + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + debug!( + "Wallet 1 Info Post Transaction, after {} blocks: {:?}", + wallet1_info.last_confirmed_height, wallet1_info + ); + let fee = core::libtx::tx_fee( + wallet1_info.last_confirmed_height as usize - 1 - cm as usize, + 2, + 1, + ); + assert!(wallet1_refreshed); + // wallet 1 received fees, so amount should be the same + assert_eq!( + wallet1_info.total, + amount * wallet1_info.last_confirmed_height - amount + ); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm) * reward - amount - fee + ); + assert_eq!(wallet1_info.amount_immature, cm * reward + fee); - // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(tx.confirmed); - assert!(tx.confirmation_ts.is_some()); - - Ok(()) - }, - )?; + // check tx log entry is confirmed + let (refreshed, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert!(refreshed); + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(tx.confirmed); + assert!(tx.confirmation_ts.is_some()); + } // mine a few more blocks let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); // refresh wallets and retrieve info/tests for each wallet after maturity - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!("Wallet 1 Info: {:?}", wallet1_info); - assert!(wallet1_refreshed); - assert_eq!( - wallet1_info.total, - amount * wallet1_info.last_confirmed_height - amount - ); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm - 1) * reward - ); - Ok(()) - }, - )?; + { + let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + debug!("Wallet 1 Info: {:?}", wallet1_info); + assert!(wallet1_refreshed); + assert_eq!( + wallet1_info.total, + amount * wallet1_info.last_confirmed_height - amount + ); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm - 1) * reward + ); + } - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.amount_currently_spendable, amount); + { + let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.amount_currently_spendable, amount); - // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(tx.confirmed); - assert!(tx.confirmation_ts.is_some()); - Ok(()) - }, - )?; + // check tx log entry is confirmed + let (refreshed, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert!(refreshed); + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(tx.confirmed); + assert!(tx.confirmation_ts.is_some()); + } // Estimate fee and locked amount for a transaction - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - let init_args = InitTxArgs { - src_acct_name: None, - amount: amount * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - estimate_only: Some(true), - ..Default::default() - }; - let est = sender_api.init_send_tx(m, init_args)?; - assert_eq!(est.amount, 600_000_000_000); - // fees for 5 inputs, 2 outputs, 1 kernel (weight 50) - assert_eq!(est.fee_fields.fee(), 25_000_000); + { + let init_args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + estimate_only: Some(true), + ..Default::default() + }; + let est = api1.init_send_tx(mask1, init_args)?; + assert_eq!(est.amount, 600_000_000_000); + // fees for 5 inputs, 2 outputs, 1 kernel (weight 50) + assert_eq!(est.fee_fields.fee(), 25_000_000); - let init_args = InitTxArgs { - src_acct_name: None, - amount: amount * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, //select smallest number - estimate_only: Some(true), - ..Default::default() - }; - let est = sender_api.init_send_tx(m, init_args)?; - assert_eq!(est.amount, 180_000_000_000); - // fees for 3 inputs, 2 outputs, 1 kernel (weight 48) - assert_eq!(est.fee_fields.fee(), 24_000_000); - - Ok(()) - }, - )?; + let init_args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, //select smallest number + estimate_only: Some(true), + ..Default::default() + }; + let est = api1.init_send_tx(mask1, init_args)?; + assert_eq!(est.amount, 180_000_000_000); + // fees for 3 inputs, 2 outputs, 1 kernel (weight 48) + assert_eq!(est.fee_fields.fee(), 24_000_000); + } // Send another transaction, but don't post to chain immediately and use // the stored transaction instead - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount * 2, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - Ok(()) - }, - )?; + { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; + slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + api1.tx_lock_outputs(mask1, &slate)?; + slate = api1.finalize_tx(mask1, &slate)?; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - let (refreshed, _wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - let (_, txs) = sender_api.retrieve_txs(m, true, None, None, None)?; - // find the transaction - let tx = txs - .iter() - .find(|t| t.tx_slate_id == Some(slate.id)) - .unwrap(); - let stored_tx_slate = sender_api - .get_stored_tx(m, None, Some(&tx.tx_slate_id.unwrap()))? - .unwrap(); - sender_api.post_tx(m, &stored_tx_slate, false)?; - let (_, wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?; - // should be mined now - assert_eq!( - wallet1_info.total, - amount * wallet1_info.last_confirmed_height - amount * 3 - ); - Ok(()) - }, - )?; + let (refreshed, _wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(refreshed); + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + // find the transaction + let tx = txs + .iter() + .find(|t| t.tx_slate_id == Some(slate.id)) + .unwrap(); + let stored_tx_slate = api1 + .get_stored_tx(mask1, None, Some(&tx.tx_slate_id.unwrap()))? + .unwrap(); + api1.post_tx(mask1, &stored_tx_slate, false)?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + // should be mined now + assert_eq!( + wallet1_info.total, + amount * wallet1_info.last_confirmed_height - amount * 3 + ); + } // mine a few more blocks let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); // check wallet2 has stored transaction - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(wallet2_refreshed); - assert_eq!(wallet2_info.amount_currently_spendable, amount * 3); + { + let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet2_refreshed); + assert_eq!(wallet2_info.amount_currently_spendable, amount * 3); - // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let tx = tx.unwrap(); - assert!(tx.confirmed); - assert!(tx.confirmation_ts.is_some()); - Ok(()) - }, - )?; + // check tx log entry is confirmed + let (refreshed, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert!(refreshed); + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let tx = tx.unwrap(); + assert!(tx.confirmed); + assert!(tx.confirmation_ts.is_some()); + } // try to send a transaction with amount inclusive of fees, but amount too // small to cover fees. Should fail. - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: 1, - amount_includes_fee: Some(true), - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let res = sender_api.init_send_tx(m, args); - assert!(res.is_err()); - Ok(()) - }, - )?; + { + let args = InitTxArgs { + src_acct_name: None, + amount: 1, + amount_includes_fee: Some(true), + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let res = api1.init_send_tx(mask1, args); + assert!(res.is_err()); + } + + // Multiple send attempts, make sure same outputs not locked. + // Fail sending without starting Tor + api2.set_tor_config(Some(grin_wallet_config::TorConfig { + use_integrated: Some(false), + socks_proxy_addr: "invalid".into(), + ..Default::default() + }))?; + let mut handles = vec![]; + + let api2_1 = Owner::new(api2.wallet_inst.clone(), None, api2.config_path()); + handles.push(thread::spawn(move || { + let args = InitTxArgs { + src_acct_name: None, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + amount: 1, + send_args: Some(InitTxSendArgs { + dest: "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp".into(), + post_tx: false, + fluff: false, + skip_tor: Some(false), + }), + ..Default::default() + }; + api2_1.init_send_tx(None, args) + })); + let api2_2 = Owner::new(api2.wallet_inst.clone(), None, api2.config_path()); + handles.push(thread::spawn(move || { + let args = InitTxArgs { + src_acct_name: None, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + amount: 1, + send_args: Some(InitTxSendArgs { + dest: "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp".into(), + post_tx: false, + fluff: false, + skip_tor: Some(false), + }), + ..Default::default() + }; + api2_2.init_send_tx(None, args) + })); + let results: Vec<_> = handles + .into_iter() + .map(|handle| handle.join().unwrap()) + .collect(); + assert_eq!(results.iter().filter(|r| { r.is_ok() }).count(), 1); + let err_res = results.iter().find(|r| r.is_err()).unwrap(); + assert!(matches!( + err_res.as_ref().err().unwrap(), + libwallet::Error::NotEnoughFunds { .. } + )); // try to build a transaction with amount inclusive of fees. Confirm that tx // amount + fee is equal to the originally specified amount let amount = 60_000_000_000; - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - amount_includes_fee: Some(true), - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - assert_eq!(slate_i.state, SlateState::Standard1); - let total_spend: u64 = slate_i.amount + slate_i.fee_fields.fee(); - assert_eq!(amount, total_spend); - Ok(()) - }, - )?; + { + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount, + amount_includes_fee: Some(true), + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; + assert_eq!(slate_i.state, SlateState::Standard1); + let total_spend: u64 = slate_i.amount + slate_i.fee_fields.fee(); + assert_eq!(amount, total_spend); + } // let logging finish stopper.store(false, Ordering::Relaxed); @@ -557,7 +527,8 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -568,7 +539,8 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -586,159 +558,121 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false); let amount = 30_000_000_000; - let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ..Default::default() - }; + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - Ok(()) - }, - )?; + let slate_i = api1.init_send_tx(mask1, args)?; + let mut slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + api1.tx_lock_outputs(mask1, &slate)?; + slate = api1.finalize_tx(mask1, &slate)?; // Check transaction log for wallet 1 - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - println!( - "last confirmed height: {}", - wallet1_info.last_confirmed_height - ); - assert!(refreshed); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - // we should have a transaction entry for this slate - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - let mut locked_count = 0; - let mut unconfirmed_count = 0; - // get the tx entry, check outputs are as expected - let (_, output_mappings) = - api.retrieve_outputs(m, true, false, Some(tx.unwrap().id))?; - for m in output_mappings.clone() { - if m.output.status == OutputStatus::Locked { - locked_count = locked_count + 1; - } - if m.output.status == OutputStatus::Unconfirmed { - unconfirmed_count = unconfirmed_count + 1; - } + { + let (refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + println!( + "last confirmed height: {}", + wallet1_info.last_confirmed_height + ); + assert!(refreshed); + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + // we should have a transaction entry for this slate + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + let mut locked_count = 0; + let mut unconfirmed_count = 0; + // get the tx entry, check outputs are as expected + let (_, output_mappings) = + api1.retrieve_outputs(mask1, true, false, Some(tx.unwrap().id))?; + for m in output_mappings.clone() { + if m.output.status == OutputStatus::Locked { + locked_count = locked_count + 1; } - assert_eq!(output_mappings.len(), 3); - assert_eq!(locked_count, 2); - assert_eq!(unconfirmed_count, 1); - - Ok(()) - }, - )?; + if m.output.status == OutputStatus::Unconfirmed { + unconfirmed_count = unconfirmed_count + 1; + } + } + assert_eq!(output_mappings.len(), 3); + assert_eq!(locked_count, 2); + assert_eq!(unconfirmed_count, 1); + } // Check transaction log for wallet 2 - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - let mut unconfirmed_count = 0; - let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); - assert!(tx.is_some()); - // get the tx entry, check outputs are as expected - let (_, outputs) = api.retrieve_outputs(m, true, false, Some(tx.unwrap().id))?; - for m in outputs.clone() { - if m.output.status == OutputStatus::Unconfirmed { - unconfirmed_count = unconfirmed_count + 1; - } + { + let (refreshed, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + assert!(refreshed); + let mut unconfirmed_count = 0; + let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); + assert!(tx.is_some()); + // get the tx entry, check outputs are as expected + let (_, outputs) = api2.retrieve_outputs(mask2, true, false, Some(tx.unwrap().id))?; + for m in outputs.clone() { + if m.output.status == OutputStatus::Unconfirmed { + unconfirmed_count = unconfirmed_count + 1; } - assert_eq!(outputs.len(), 1); - assert_eq!(unconfirmed_count, 1); - let (refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - assert_eq!(wallet2_info.amount_currently_spendable, 0,); - assert_eq!(wallet2_info.amount_awaiting_finalization, amount); - Ok(()) - }, - )?; + } + assert_eq!(outputs.len(), 1); + assert_eq!(unconfirmed_count, 1); + let (refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + assert_eq!(wallet2_info.amount_currently_spendable, 0,); + assert_eq!(wallet2_info.amount_awaiting_finalization, amount); + } // wallet 1 is bold and doesn't ever post the transaction // mine a few more blocks let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false); // Wallet 1 decides to roll back instead - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - // can't roll back coinbase - let res = api.cancel_tx(m, Some(1), None); - assert!(res.is_err()); - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let tx = txs - .iter() - .find(|t| t.tx_slate_id == Some(slate.id)) - .unwrap(); - api.cancel_tx(m, Some(tx.id), None)?; - let (refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - println!( - "last confirmed height: {}", - wallet1_info.last_confirmed_height - ); - // check all eligible inputs should be now be spendable - println!("cm: {}", cm); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm) * reward - ); - // can't roll back again - let res = api.cancel_tx(m, Some(tx.id), None); - assert!(res.is_err()); - - Ok(()) - }, - )?; - + { + // can't roll back coinbase + let res = api1.cancel_tx(mask1, Some(1), None); + assert!(res.is_err()); + let (_, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + let tx = txs + .iter() + .find(|t| t.tx_slate_id == Some(slate.id)) + .unwrap(); + api1.cancel_tx(mask1, Some(tx.id), None)?; + let (refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + assert!(refreshed); + println!( + "last confirmed height: {}", + wallet1_info.last_confirmed_height + ); + // check all eligible inputs should be now be spendable + println!("cm: {}", cm); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm) * reward + ); + // can't roll back again + let res = api1.cancel_tx(mask1, Some(tx.id), None); + assert!(res.is_err()); + } // Wallet 2 rolls back - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let tx = txs - .iter() - .find(|t| t.tx_slate_id == Some(slate.id)) - .unwrap(); - api.cancel_tx(m, Some(tx.id), None)?; - let (refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - assert!(refreshed); - // check all eligible inputs should be now be spendable - assert_eq!(wallet2_info.amount_currently_spendable, 0,); - assert_eq!(wallet2_info.total, 0,); - // can't roll back again - let res = api.cancel_tx(m, Some(tx.id), None); - assert!(res.is_err()); - - Ok(()) - }, - )?; + { + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + let tx = txs + .iter() + .find(|t| t.tx_slate_id == Some(slate.id)) + .unwrap(); + api2.cancel_tx(mask2, Some(tx.id), None)?; + let (refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(refreshed); + // check all eligible inputs should be now be spendable + assert_eq!(wallet2_info.amount_currently_spendable, 0,); + assert_eq!(wallet2_info.total, 0,); + // can't roll back again + let res = api2.cancel_tx(mask2, Some(tx.id), None); + assert!(res.is_err()); + } // let logging finish stopper.store(false, Ordering::Relaxed); @@ -760,7 +694,8 @@ fn big_amount_error(test_dir: &'static str) -> Result<(), libwallet::Error> { "wallet1", None, &mut wallet_proxy, - true + true, + api1 ); let mask1 = (&mask1_i).as_ref(); println!("Mask1: {:?}", mask1); @@ -783,50 +718,35 @@ fn big_amount_error(test_dir: &'static str) -> Result<(), libwallet::Error> { let min_confirmations = 1; // Check wallet 1 content are as expected - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (wallet1_refreshed, wallet1_info) = - api.retrieve_summary_info(m, true, min_confirmations)?; - println!( - "Wallet 1 Info Pre-Transaction, after {} blocks: {:?}", - wallet1_info.last_confirmed_height, wallet1_info - ); - assert!(wallet1_refreshed); - assert_eq!( - wallet1_info.amount_currently_spendable, - (wallet1_info.last_confirmed_height - cm) * reward - ); - assert_eq!(wallet1_info.amount_immature, cm * reward); - Ok(()) - }, - )?; + { + let (wallet1_refreshed, wallet1_info) = + api1.retrieve_summary_info(mask1, true, min_confirmations)?; + println!( + "Wallet 1 Info Pre-Transaction, after {} blocks: {:?}", + wallet1_info.last_confirmed_height, wallet1_info + ); + assert!(wallet1_refreshed); + assert_eq!( + wallet1_info.amount_currently_spendable, + (wallet1_info.last_confirmed_height - cm) * reward + ); + assert_eq!(wallet1_info.amount_immature, cm * reward); + } // test selecting max amount - let result = wallet::controller::owner_single_use( - wallet1.clone(), + let result = api1.init_send_tx( mask1, - PathBuf::from(test_dir), - |api, m| { - api.init_send_tx( - m, - InitTxArgs { - amount: (reward * outputs_num as u64) - (cm * reward), - amount_includes_fee: Some(true), - minimum_confirmations: 0, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - refresh_outputs_from_node: true, - ..InitTxArgs::default() - }, - )?; - Ok(()) + InitTxArgs { + amount: (reward * outputs_num as u64) - (cm * reward), + amount_includes_fee: Some(true), + minimum_confirmations: 0, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + refresh_outputs_from_node: true, + ..InitTxArgs::default() }, ); - println!("{:?}", result); let max_tx_weight = global::max_tx_weight(); @@ -844,19 +764,11 @@ fn big_amount_error(test_dir: &'static str) -> Result<(), libwallet::Error> { Ok(_) => {} Err(e) => match e { libwallet::Error::BigAmountError(a, fee, num_inputs) => { - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, e_a, e_fee, e_num_inputs) = - api.estimate_max_sendable(m, true, min_confirmations)?; - assert_eq!(e_a, a - fee); - assert_eq!(e_fee, fee); - assert_eq!(e_num_inputs, num_inputs); - Ok(()) - }, - )?; + let (_, e_a, e_fee, e_num_inputs) = + api1.estimate_max_sendable(mask1, true, min_confirmations)?; + assert_eq!(e_a, a - fee); + assert_eq!(e_fee, fee); + assert_eq!(e_num_inputs, num_inputs); } _ => {} }, diff --git a/controller/tests/ttl_cutoff.rs b/controller/tests/ttl_cutoff.rs index faa3990..4c9bd12 100644 --- a/controller/tests/ttl_cutoff.rs +++ b/controller/tests/ttl_cutoff.rs @@ -20,8 +20,7 @@ extern crate grin_wallet_util; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient}; -use libwallet::{InitTxArgs, Slate, TxLogEntryType}; -use std::path::PathBuf; +use libwallet::{InitTxArgs, TxLogEntryType}; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; @@ -45,7 +44,8 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); @@ -58,7 +58,8 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -78,126 +79,76 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); let amount = 60_000_000_000; - let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ttl_blocks: Some(2), - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ttl_blocks: Some(2), + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; + let mut slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + api1.tx_lock_outputs(mask1, &slate)?; - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let tx = txs[0].clone(); + let (_, txs) = api1.retrieve_txs(mask1, true, None, Some(slate.id), None)?; + let tx = txs[0].clone(); - assert_eq!(tx.ttl_cutoff_height, Some(12)); - Ok(()) - }, - )?; + assert_eq!(tx.ttl_cutoff_height, Some(12)); // Now mine past the block, and check again. Transaction should be gone. let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let tx = txs[0].clone(); + let (_, txs) = api1.retrieve_txs(mask1, true, None, Some(slate.id), None)?; + let tx = txs[0].clone(); - assert_eq!(tx.ttl_cutoff_height, Some(12)); - assert_eq!(tx.tx_type, TxLogEntryType::TxSentCancelled); - Ok(()) - }, - )?; + assert_eq!(tx.ttl_cutoff_height, Some(12)); + assert_eq!(tx.tx_type, TxLogEntryType::TxSentCancelled); // Should also be gone in wallet 2, and output gone - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |sender_api, m| { - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let tx = txs[0].clone(); - let outputs = sender_api.retrieve_outputs(m, false, true, None)?.1; - assert_eq!(outputs.len(), 0); + let (_, txs) = api2.retrieve_txs(mask2, true, None, Some(slate.id), None)?; + let tx = txs[0].clone(); + let outputs = api2.retrieve_outputs(mask2, false, true, None)?.1; + assert_eq!(outputs.len(), 0); - assert_eq!(tx.ttl_cutoff_height, Some(12)); - assert_eq!(tx.tx_type, TxLogEntryType::TxReceivedCancelled); - Ok(()) - }, - )?; + assert_eq!(tx.ttl_cutoff_height, Some(12)); + assert_eq!(tx.tx_type, TxLogEntryType::TxReceivedCancelled); // try again, except try and send off the transaction for completion beyond the expiry - let mut slate = Slate::blank(1, false); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: true, - ttl_blocks: Some(2), - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - sender_api.tx_lock_outputs(m, &slate_i)?; - slate = slate_i; + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ttl_blocks: Some(2), + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; + api1.tx_lock_outputs(mask1, &slate_i)?; + slate = slate_i; - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - let tx = txs[0].clone(); + let (_, txs) = api1.retrieve_txs(mask1, true, None, Some(slate.id), None)?; + let tx = txs[0].clone(); - assert_eq!(tx.ttl_cutoff_height, Some(14)); - Ok(()) - }, - )?; + assert_eq!(tx.ttl_cutoff_height, Some(14)); // Mine past the ttl block and try to send let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); // Wallet 2 will need to have updated past the TTL - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |sender_api, m| { - let (_, _) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; - Ok(()) - }, - )?; + let (_, _) = api2.retrieve_txs(mask2, true, None, Some(slate.id), None)?; // And when wallet 1 sends, should be rejected - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |_sender_api, _m| { - let res = client1.send_tx_slate_direct("wallet2", &slate); - println!("Send after TTL result is: {:?}", res); - assert!(res.is_err()); - Ok(()) - }, - )?; + let res = client1.send_tx_slate_direct("wallet2", &slate); + println!("Send after TTL result is: {:?}", res); + assert!(res.is_err()); // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/tx_list_filter.rs b/controller/tests/tx_list_filter.rs index c7fa9c8..cc63c15 100644 --- a/controller/tests/tx_list_filter.rs +++ b/controller/tests/tx_list_filter.rs @@ -23,210 +23,183 @@ use grin_core as core; use grin_keychain as keychain; use grin_util as util; use libwallet::{RetrieveTxQueryArgs, RetrieveTxQuerySortField}; -use std::path::PathBuf; -use self::libwallet::{InitTxArgs, Slate}; +use self::libwallet::InitTxArgs; use impls::test_framework::{self, LocalWalletClient}; -use std::sync::{atomic::Ordering, Arc}; +use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; use util::secp::key::SecretKey; -use util::Mutex; use self::keychain::ExtKeychain; -use self::libwallet::WalletInst; use impls::DefaultLCProvider; mod common; use common::{clean_output_dir, create_wallet_proxy, setup}; +use grin_wallet_api::Owner; fn test_wallet_tx_filtering( - wallet: Arc< - Mutex< - Box< - dyn WalletInst< - 'static, - DefaultLCProvider, - LocalWalletClient, - ExtKeychain, - >, - >, - >, - >, mask: Option<&SecretKey>, - test_dir: &'static str, + api: Owner, LocalWalletClient, ExtKeychain>, ) -> Result<(), libwallet::Error> { - wallet::controller::owner_single_use( - wallet.clone(), - mask, - PathBuf::from(test_dir), - |api, _m| { - let query = |args| { - api.retrieve_txs(mask, true, None, None, Some(args)) - .map(|(_, txs)| txs) - }; - let txs = query(RetrieveTxQueryArgs::default())?; - assert!(!txs.is_empty()); - let before_first_tx = - txs.iter().map(|tx| tx.creation_ts).min().unwrap() - Duration::from_secs(1); - let mut tx_query_args = RetrieveTxQueryArgs::default(); + let query = |args| { + api.retrieve_txs(mask, true, None, None, Some(args)) + .map(|(_, txs)| txs) + }; + let txs = query(RetrieveTxQueryArgs::default())?; + assert!(!txs.is_empty()); + let before_first_tx = + txs.iter().map(|tx| tx.creation_ts).min().unwrap() - Duration::from_secs(1); + let mut tx_query_args = RetrieveTxQueryArgs::default(); - // Max creation time before all test transactions - tx_query_args.max_creation_timestamp = Some(before_first_tx); - assert!(query(tx_query_args.clone())?.is_empty()); + // Max creation time before all test transactions + tx_query_args.max_creation_timestamp = Some(before_first_tx); + assert!(query(tx_query_args.clone())?.is_empty()); - // Min confirmation time should not affect creation time - tx_query_args.max_creation_timestamp = None; - tx_query_args.min_confirmed_timestamp = Some(before_first_tx); - assert_eq!(query(tx_query_args)?.len(), txs.len()); + // Min confirmation time should not affect creation time + tx_query_args.max_creation_timestamp = None; + tx_query_args.min_confirmed_timestamp = Some(before_first_tx); + assert_eq!(query(tx_query_args)?.len(), txs.len()); - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_id = Some(5); + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_id = Some(5); - // Min ID - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results[0].id, 5); - assert_eq!(tx_results[tx_results.len() - 1].id, 33); + // Min ID + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results[0].id, 5); + assert_eq!(tx_results[tx_results.len() - 1].id, 33); - // Max ID - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(20); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results[0].id, 5); - assert_eq!(tx_results[tx_results.len() - 1].id, 20); + // Max ID + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(20); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results[0].id, 5); + assert_eq!(tx_results[tx_results.len() - 1].id, 20); - // Exclude 1 cancelled - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.exclude_cancelled = Some(true); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(50); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 28); + // Exclude 1 canceled + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.exclude_cancelled = Some(true); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(50); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 28); - // Exclude 1 cancelled, show confirmed only - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.exclude_cancelled = Some(true); - tx_query_args.include_confirmed_only = Some(true); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(50); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 14); + // Exclude 1 canceled, show confirmed only + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.exclude_cancelled = Some(true); + tx_query_args.include_confirmed_only = Some(true); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(50); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 14); - // show outstanding only (including cancelled) - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.exclude_cancelled = Some(false); - tx_query_args.include_outstanding_only = Some(true); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(50); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 15); + // show outstanding only (including canceled) + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.exclude_cancelled = Some(false); + tx_query_args.include_outstanding_only = Some(true); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(50); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 15); - // outstanding only and confirmed only should give empty set - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.exclude_cancelled = Some(false); - tx_query_args.include_outstanding_only = Some(true); - tx_query_args.include_confirmed_only = Some(true); - tx_query_args.min_id = Some(5); - tx_query_args.max_id = Some(50); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 0); + // outstanding only and confirmed only should give empty set + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.exclude_cancelled = Some(false); + tx_query_args.include_outstanding_only = Some(true); + tx_query_args.include_confirmed_only = Some(true); + tx_query_args.min_id = Some(5); + tx_query_args.max_id = Some(50); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 0); - // include sent only - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.include_sent_only = Some(true); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 15); + // include sent only + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.include_sent_only = Some(true); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 15); - // include received only (none in this set) - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.include_received_only = Some(true); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 0); + // include received only (none in this set) + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.include_received_only = Some(true); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 0); - // include reverted only (none in this set) - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.include_reverted_only = Some(true); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 0); + // include reverted only (none in this set) + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.include_reverted_only = Some(true); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 0); - // include coinbase only - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.include_coinbase_only = Some(true); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 19); + // include coinbase only + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.include_coinbase_only = Some(true); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 19); - // Amounts - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 27); + // Amounts + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 27); - // amount, should see as above with coinbases excluded - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000); - tx_query_args.max_amount = Some(60_000_000_000 - 1); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 8); + // amount, should see as above with coinbases excluded + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_amount = Some(60_000_000_000 - 59_963_300_000); + tx_query_args.max_amount = Some(60_000_000_000 - 1); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 8); - // Amount - should only see coinbase (incoming) - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.min_amount = Some(60_000_000_000); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results.len(), 19); + // Amount - should only see coinbase (incoming) + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.min_amount = Some(60_000_000_000); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results.len(), 19); - // sort order - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; + // sort order + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; - assert_eq!(tx_results[0].id, 33); - assert_eq!(tx_results[tx_results.len() - 1].id, 0); + assert_eq!(tx_results[0].id, 33); + assert_eq!(tx_results[tx_results.len() - 1].id, 0); - // change sort field to amount desc, should have coinbases first - let mut tx_query_args = RetrieveTxQueryArgs::default(); - tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc); - tx_query_args.sort_field = Some(RetrieveTxQuerySortField::TotalAmount); - let tx_results = api - .retrieve_txs(mask, true, None, None, Some(tx_query_args))? - .1; - assert_eq!(tx_results[0].amount_credited, 60_000_000_000); - - /*for entry in tx_results.iter() { - println!("{:?}", entry); - }*/ - - Ok(()) - }, - )?; + // change sort field to amount desc, should have coinbases first + let mut tx_query_args = RetrieveTxQueryArgs::default(); + tx_query_args.sort_order = Some(libwallet::RetrieveTxQuerySortOrder::Desc); + tx_query_args.sort_field = Some(RetrieveTxQuerySortField::TotalAmount); + let tx_results = api + .retrieve_txs(mask, true, None, None, Some(tx_query_args))? + .1; + assert_eq!(tx_results[0].amount_credited, 60_000_000_000); Ok(()) } @@ -248,7 +221,8 @@ fn build_chain_for_tx_filtering( "wallet1", None, &mut wallet_proxy, - true + true, + api1 ); let mask1 = (&mask1_i).as_ref(); debug!("Mask1: {:?}", mask1); @@ -260,7 +234,8 @@ fn build_chain_for_tx_filtering( "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); debug!("Mask2: {:?}", mask2); @@ -274,25 +249,8 @@ fn build_chain_for_tx_filtering( // Stop the scanning updater threads because it extends the time needed to build the chain // exponentially - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, _m| { - api.stop_updater()?; - Ok(()) - }, - )?; - - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, _m| { - api.stop_updater()?; - Ok(()) - }, - )?; + api1.stop_updater()?; + api2.stop_updater()?; // few values to keep things shorter let reward = core::consensus::REWARD; @@ -304,22 +262,14 @@ fn build_chain_for_tx_filtering( let mut wallet_1_has_funds = false; // Check wallet 1 contents - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - debug!( - "Wallet 1 spendable - {}", - wallet1_info.amount_currently_spendable - ); - if wallet1_info.amount_currently_spendable > reward { - wallet_1_has_funds = true; - } - Ok(()) - }, - )?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + debug!( + "Wallet 1 spendable - {}", + wallet1_info.amount_currently_spendable + ); + if wallet1_info.amount_currently_spendable > reward { + wallet_1_has_funds = true; + } if !wallet_1_has_funds { let _ = @@ -331,62 +281,44 @@ fn build_chain_for_tx_filtering( let num_txs = 1; for _ in 0..num_txs { let amount: u64 = i as u64 * 1_000_000; - let mut slate = Slate::blank(1, false); debug!("Creating TX for {}", amount); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { - // note this will increment the block count as part of the transaction "Posting" - let args = InitTxArgs { - src_acct_name: None, - amount: amount, - minimum_confirmations: 1, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - slate = sender_api.finalize_tx(m, &slate)?; - Ok(()) - }, - )?; - } - } - - // Cancel a tx for filtering testing - let amount: u64 = 1_000_000; - let mut slate = Slate::blank(1, false); - debug!("Creating TX for {}", amount); - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |sender_api, m| { // note this will increment the block count as part of the transaction "Posting" let args = InitTxArgs { src_acct_name: None, - amount: amount, + amount, minimum_confirmations: 1, max_outputs: 500, num_change_outputs: 1, selection_strategy_is_use_all: false, ..Default::default() }; - let slate_i = sender_api.init_send_tx(m, args)?; - slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; - sender_api.tx_lock_outputs(m, &slate)?; - sender_api.cancel_tx(m, Some(33), None)?; - Ok(()) - }, - )?; + let slate_i = api1.init_send_tx(mask1, args)?; + let slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + api1.tx_lock_outputs(mask1, &slate)?; + api1.finalize_tx(mask1, &slate)?; + } + } + + // Cancel a tx for filtering testing + let amount: u64 = 1_000_000; + debug!("Creating TX for {}", amount); + // note this will increment the block count as part of the transaction "Posting" + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 1, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + let slate_i = api1.init_send_tx(mask1, args)?; + let slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; + api1.tx_lock_outputs(mask1, &slate)?; + api1.cancel_tx(mask1, Some(33), None)?; // Perform actual testing - test_wallet_tx_filtering(wallet1, mask1, test_dir)?; + test_wallet_tx_filtering(mask1, api1)?; // let logging finish stopper.store(false, Ordering::Relaxed); diff --git a/controller/tests/updater_thread.rs b/controller/tests/updater_thread.rs index 2beebab..8e33355 100644 --- a/controller/tests/updater_thread.rs +++ b/controller/tests/updater_thread.rs @@ -46,7 +46,8 @@ fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err "wallet1", None, &mut wallet_proxy, - false + false, + api1 ); let mask1 = (&mask1_i).as_ref(); create_wallet_and_add!( @@ -57,7 +58,8 @@ fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err "wallet2", None, &mut wallet_proxy, - false + false, + api2 ); let mask2 = (&mask2_i).as_ref(); @@ -69,28 +71,11 @@ fn updater_thread_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err }); // add some accounts - wallet::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "mining")?; - api.create_account_path(m, "listener")?; - Ok(()) - }, - )?; + api1.create_account_path(mask1, "mining")?; + api1.create_account_path(mask1, "listener")?; - // add some accounts - wallet::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - api.create_account_path(m, "account1")?; - api.create_account_path(m, "account2")?; - Ok(()) - }, - )?; + api2.create_account_path(mask2, "account1")?; + api2.create_account_path(mask2, "account2")?; // Get some mining done { diff --git a/impls/src/adapters/tor.rs b/impls/src/adapters/tor.rs index 1394dd7..63f56c0 100644 --- a/impls/src/adapters/tor.rs +++ b/impls/src/adapters/tor.rs @@ -124,7 +124,7 @@ impl TorSlateSender { let res: String = self.post(url, req).map_err(|e| { let mut report = format!("Performing version check (is recipient listening?): {}", e); let err_string = format!("{}", e); - if err_string.contains("404") { + if !self.config.use_integrated.unwrap_or(false) && err_string.contains("404") { // Report that the other version of the wallet is out of date report = "Other wallet is incompatible and requires an upgrade. \ Please urge the other wallet owner to upgrade and try the transaction again." diff --git a/libwallet/src/mwixnet/onion/onion.rs b/libwallet/src/mwixnet/onion/onion.rs index 8159232..1958e42 100644 --- a/libwallet/src/mwixnet/onion/onion.rs +++ b/libwallet/src/mwixnet/onion/onion.rs @@ -366,7 +366,7 @@ impl From for OnionError { } #[cfg(test)] -pub mod tests { +mod tests { use super::*; use crate::mwixnet::onion::crypto::secp::random_secret; use crate::mwixnet::onion::{new_hop, Hop}; diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index a4d8308..f54987c 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -21,9 +21,9 @@ extern crate log; extern crate grin_wallet; use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; -use std::path::PathBuf; use clap::App; +use std::sync::mpsc; use std::thread; use std::time::Duration; @@ -32,9 +32,23 @@ use grin_wallet_impls::DefaultLCProvider; mod common; use common::{clean_output_dir, execute_command, initial_setup_wallet, instantiate_wallet, setup}; +use grin_wallet_api::Owner; +use grin_wallet_libwallet::{OutputStatus, TxLogEntryType}; /// command line tests fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller::Error> { + let (logs_tx, logs_rx) = mpsc::sync_channel(100); + let panic_hook = std::panic::take_hook(); + grin_util::init_logger( + Some(grin_util::logger::LoggingConfig { + log_to_file: false, + stdout_log_level: log::Level::Error, + tui_running: Some(true), + ..Default::default() + }), + Some(logs_tx), + ); + std::panic::set_hook(panic_hook); setup(test_dir); // Create a new proxy to simulate server and wallet responses let mut wallet_proxy: WalletProxy< @@ -158,15 +172,9 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let (wallet1, mask1_i) = instantiate_wallet(wallet_config1, client1.clone(), "password1", "default")?; let mask1 = (&mask1_i).as_ref(); - grin_wallet_controller::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "mining")?; - Ok(()) - }, - )?; + let api1 = Owner::new(wallet1.clone(), None, config1.config_file_path.clone()); + + api1.set_active_account(mask1, "mining")?; let mut bh = 10u64; let _ = @@ -236,23 +244,16 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: "default", )?; let mask1 = (&mask1_i).as_ref(); + let api1 = Owner::new(wallet1.clone(), None, config1.config_file_path.clone()); // Check our transaction log, should have 10 entries - grin_wallet_controller::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "mining")?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - assert_eq!(txs.len(), bh as usize); - for t in txs { - assert!(t.kernel_excess.is_some()); - } - Ok(()) - }, - )?; + api1.set_active_account(mask1, "mining")?; + let (refreshed, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert!(refreshed); + assert_eq!(txs.len(), bh as usize); + for t in txs { + assert!(t.kernel_excess.is_some()); + } let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false); bh += 10; @@ -273,33 +274,17 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: "default", )?; let mask2 = (&mask2_i).as_ref(); + let api2 = Owner::new(wallet2.clone(), None, config2.config_file_path.clone()); - grin_wallet_controller::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "account_1")?; - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - assert_eq!(wallet1_info.last_confirmed_height, bh); - assert_eq!(wallet1_info.amount_currently_spendable, 10_000_000_000); - Ok(()) - }, - )?; + api2.set_active_account(mask2, "account_1")?; + let (_, wallet1_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert_eq!(wallet1_info.last_confirmed_height, bh); + assert_eq!(wallet1_info.amount_currently_spendable, 10_000_000_000); // Send to wallet 2 with --amount_includes_fee - let mut old_balance = 0; - grin_wallet_controller::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "mining")?; - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - old_balance = wallet1_info.amount_currently_spendable; - Ok(()) - }, - )?; + api1.set_active_account(mask1, "mining")?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + let old_balance = wallet1_info.amount_currently_spendable; let arg_vec = vec![ "grin-wallet", "-p", @@ -349,22 +334,14 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // Check the new balance of wallet 1 reduced by EXACTLY the tx amount (instead of amount + fee) // This confirms that the TX amount was correctly computed to allow for the fee - grin_wallet_controller::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "mining")?; - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - // make sure the new balance is exactly equal to the old balance - the tx amount + the amount mined since then - let amt_mined = 10 * 60_000_000_000; - assert_eq!( - wallet1_info.amount_currently_spendable + 10_000_000_000, - old_balance + amt_mined - ); - Ok(()) - }, - )?; + api1.set_active_account(mask1, "mining")?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?; + // make sure the new balance is exactly equal to the old balance - the tx amount + the amount mined since then + let amt_mined = 10 * 60_000_000_000; + assert_eq!( + wallet1_info.amount_currently_spendable + 10_000_000_000, + old_balance + amt_mined + ); // Send encrypted from wallet 1 to wallet 2 // output wallet 2's address for test creation purposes, @@ -435,19 +412,12 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: "default", )?; let mask1 = (&mask1_i).as_ref(); + let api1 = Owner::new(wallet1.clone(), None, config1.config_file_path.clone()); - grin_wallet_controller::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "mining")?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - assert_eq!(txs.len(), bh as usize); - Ok(()) - }, - )?; + api1.set_active_account(mask1, "mining")?; + let (refreshed, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert!(refreshed); + assert_eq!(txs.len(), bh as usize); // Send to self let arg_vec = vec![ @@ -508,19 +478,12 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: "default", )?; let mask1 = (&mask1_i).as_ref(); + let api1 = Owner::new(wallet1.clone(), None, config1.config_file_path.clone()); - grin_wallet_controller::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "mining")?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; - assert!(refreshed); - assert_eq!(txs.len(), bh as usize + 1); - Ok(()) - }, - )?; + api1.set_active_account(mask1, "mining")?; + let (refreshed, txs) = api1.retrieve_txs(mask1, true, None, None, None)?; + assert!(refreshed); + assert_eq!(txs.len(), bh as usize + 1); // Another file exchange, don't send, but unlock with repair command let arg_vec = vec![ @@ -649,24 +612,15 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; // get tx output via -tx parameter - let mut tx_id = "".to_string(); - grin_wallet_controller::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "default")?; - let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; - let some_tx_id = txs[0].tx_slate_id.clone(); - assert!(some_tx_id.is_some()); - tx_id = some_tx_id.unwrap().to_string().clone(); - Ok(()) - }, - )?; + api2.set_active_account(mask2, "default")?; + let (_, txs) = api2.retrieve_txs(mask2, true, None, None, None)?; + let some_tx_id = txs[0].tx_slate_id.clone(); + assert!(some_tx_id.is_some()); + let tx_id = some_tx_id.unwrap().to_string().clone(); let arg_vec = vec!["grin-wallet", "-p", "password2", "txs", "-t", &tx_id[..]]; execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; - // bit of mining + // a bit of mining let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false); // Test wallet sweep @@ -717,21 +671,77 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // Check wallet 1 is now empty, except for immature coinbase outputs from recent mining), // and recently matured coinbase outputs, which were not mature at time of spending. // This confirms that the TX amount was correctly computed to allow for the fee - grin_wallet_controller::controller::owner_single_use( - wallet1.clone(), - mask1, - PathBuf::from(test_dir), - |api, m| { - api.set_active_account(m, "mining")?; - let (_, wallet1_info) = api.retrieve_summary_info(m, true, 10)?; - // Entire 'spendable' wallet balance should have been swept, except the coinbase outputs - // which matured in the last batch of mining. Check that the new spendable balance is - // exactly equal to those matured coins. - let amt_mined = 10 * 60_000_000_000; - assert_eq!(wallet1_info.amount_currently_spendable, amt_mined); - Ok(()) - }, - )?; + api1.set_active_account(mask1, "mining")?; + let (_, wallet1_info) = api1.retrieve_summary_info(mask1, true, 10)?; + // Entire 'spendable' wallet balance should have been swept, except the coinbase outputs + // which matured in the last batch of mining. Check that the new spendable balance is + // exactly equal to those matured coins. + let amt_mined = 10 * 60_000_000_000; + assert_eq!(wallet1_info.amount_currently_spendable, amt_mined); + + // Failed send, with --late-lock, make sure outputs not locked (amount not changed). + api2.set_active_account(mask2, "account_1")?; + let (_, txs_before) = api2.retrieve_txs(mask2, false, None, None, None)?; + let (_, wallet1_info) = api2.retrieve_summary_info(mask2, true, 1)?; + let old_balance = wallet1_info.amount_currently_spendable; + let mut arg_vec = vec![ + "grin-wallet", + "-p", + "password2", + "-a", + "account_1", + "send", + "-d", + "tgrin1xtxavwfgs48ckf3gk8wwgcndmn0nt4tvkl8a7ltyejjcy2mc6nfs9gm2lp", + "1", + "--late-lock", + ]; + let args = app.clone().get_matches_from(arg_vec.clone()); + let mut config = initial_setup_wallet(test_dir, "wallet2"); + config.members.tor = Some(grin_wallet_config::TorConfig { + use_integrated: Some(false), + socks_proxy_addr: "invalid".into(), + ..Default::default() + }); + let send = |args: &clap::ArgMatches| -> Result<(), grin_wallet_controller::Error> { + logs_rx.try_iter().for_each(drop); + grin_wallet::cmd::wallet_args::wallet_command( + args, + config.clone(), + client2.clone(), + false, + |_| {}, + )?; + assert!(logs_rx.try_iter().any(|entry| { + entry.log.contains("Error sending slate sync:") && entry.log.contains("AddrParseError") + })); + Ok(()) + }; + send(&args)?; + api2.set_active_account(mask2, "account_1")?; + let (_, txs_after) = api2.retrieve_txs(mask2, false, None, None, None)?; + assert_eq!(txs_before.len(), txs_after.len()); + let (_, wallet1_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert_eq!(old_balance, wallet1_info.amount_currently_spendable); + + // The same failure without late locking leaves one transaction and locked outputs + arg_vec.pop(); + let args = app.clone().get_matches_from(arg_vec); + send(&args)?; + let (_, txs_after) = api2.retrieve_txs(mask2, false, None, None, None)?; + assert_eq!(txs_after.len(), txs_before.len() + 1); + let tx = txs_after + .iter() + .find(|tx| !txs_before.iter().any(|before| before.id == tx.id)) + .unwrap(); + assert_eq!(tx.tx_type, TxLogEntryType::TxSent); + assert!(tx.tx_slate_id.is_some()); + let (_, outputs) = api2.retrieve_outputs(mask2, false, false, Some(tx.id))?; + assert!(outputs + .iter() + .any(|output| output.output.status == OutputStatus::Locked)); + let (_, wallet_info) = api2.retrieve_summary_info(mask2, true, 1)?; + assert!(wallet_info.amount_currently_spendable < old_balance); // let logging finish thread::sleep(Duration::from_millis(200)); diff --git a/tests/owner_v3_lifecycle.rs b/tests/owner_v3_lifecycle.rs index 997bdb3..bf6711e 100644 --- a/tests/owner_v3_lifecycle.rs +++ b/tests/owner_v3_lifecycle.rs @@ -19,7 +19,7 @@ extern crate log; extern crate grin_wallet; -use grin_wallet_api::{ECDHPubkey, JsonId}; +use grin_wallet_api::{ECDHPubkey, JsonId, Owner}; use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy}; use clap::App; @@ -64,7 +64,7 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { >, >, > = Arc::new(Mutex::new(WalletProxy::new(test_dir))); - let (chain, wallet2, mask2_i) = { + let (chain, wallet2, mask2_i, config2) = { let mut wallet_proxy = wallet_proxy_a.lock(); let chain = wallet_proxy.chain.clone(); @@ -118,7 +118,7 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { ) .unwrap(); }); - (chain, wallet2, mask2_i) + (chain, wallet2, mask2_i, config2) }; // give a bit for wallet to init and populate proxy with wallet via callback in thread above thread::sleep(Duration::from_millis(500)); @@ -402,27 +402,20 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { let mut slate: Slate = res.unwrap().into(); // give this slate over to wallet 2 manually - grin_wallet_controller::controller::owner_single_use( - wallet2.clone(), - mask2, - PathBuf::from(test_dir), - |api, m| { - let args = InitTxArgs { - src_acct_name: None, - amount: slate.amount, - minimum_confirmations: 1, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - ..Default::default() - }; - let res = api.process_invoice_tx(m, &slate, args); - assert!(res.is_ok()); - slate = res.unwrap(); - api.tx_lock_outputs(m, &slate)?; - Ok(()) - }, - )?; + let api2 = Owner::new(wallet2.clone(), None, config2.config_file_path.clone()); + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 1, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + ..Default::default() + }; + let res = api2.process_invoice_tx(mask2, &slate, args); + assert!(res.is_ok()); + slate = res?; + api2.tx_lock_outputs(mask2, &slate)?; //16) Finalize the invoice tx (to foreign api) // (Tests that foreign API on same port also has its stored mask updated) From 57a01674ffa780608fe6aa1c81d3111f080b0dcb Mon Sep 17 00:00:00 2001 From: wiesche Date: Fri, 2 Oct 2026 22:15:25 +0200 Subject: [PATCH 14/18] isolate tests (cherry picked from commit 3a2ad4edbfbca29bd4191fd30cab23e15fa47cde) --- tests/common/mod.rs | 5 ++++- tests/owner_v3_lifecycle.rs | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 85cbe74..313a795 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -299,8 +299,11 @@ where { let args = app.clone().get_matches_from(arg_vec); let _ = get_wallet_subcommand(test_dir, wallet_name, args.clone()); + // Avoid conflicts with local wallets + let wallet_dir = PathBuf::from(test_dir).join(format!("{}_bootstrap", wallet_name)); let mut config = - config::initial_setup_wallet(&ChainTypes::AutomatedTesting, None, true).unwrap(); + config::initial_setup_wallet(&ChainTypes::AutomatedTesting, Some(wallet_dir), true) + .unwrap(); let mut wallet_config = config.clone().members.wallet; wallet_config.chain_type = None; wallet_config.api_secret_path = None; diff --git a/tests/owner_v3_lifecycle.rs b/tests/owner_v3_lifecycle.rs index bf6711e..363c6ed 100644 --- a/tests/owner_v3_lifecycle.rs +++ b/tests/owner_v3_lifecycle.rs @@ -148,7 +148,7 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { let value: ECDHPubkey = res.unwrap(); let shared_key = derive_ecdh_key(sec_key_str, &value.ecdh_pubkey); - // 2) get the top level directory, should default to ~/.grin/auto + // 2) get the top level directory let req = include_str!("data/v3_reqs/get_top_level.req.json"); let res = send_request_enc::( &JsonId::StrId(String::from("1")), @@ -159,7 +159,13 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { )?; println!("RES 2: {:?}", res); assert!(res.is_ok()); - assert!(res.unwrap().contains("auto")); + assert_eq!( + PathBuf::from(res.unwrap()).canonicalize().unwrap(), + PathBuf::from(test_dir) + .join("wallet1_bootstrap") + .canonicalize() + .unwrap() + ); // 3) now set the top level directory to our test wallet dir let req = serde_json::json!({ From fcb1d37876d7bf2b4a4718bc54404af7cda3cd0d Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 29 Sep 2026 23:22:45 +0300 Subject: [PATCH 15/18] Compact transaction table at CLI view (#822) (cherry picked from commit 965e8f1667cf976df61fa846005f9aeddbb14967) --- controller/src/display.rs | 163 ++++++++++++++++++++------------------ libwallet/src/types.rs | 8 +- 2 files changed, 88 insertions(+), 83 deletions(-) diff --git a/controller/src/display.rs b/controller/src/display.rs index 54077fc..8251d35 100644 --- a/controller/src/display.rs +++ b/controller/src/display.rs @@ -17,9 +17,10 @@ use crate::core::core::FeeFields; use crate::core::global; use crate::libwallet::{ address, AcctPathMapping, Error, OutputCommitMapping, OutputStatus, SlatepackAddress, - TxLogEntry, ViewWallet, WalletInfo, + TxLogEntry, TxLogEntryType, ViewWallet, WalletInfo, }; use crate::util::ToHex; + use prettytable; use prettytable::format::{FormatBuilder, LinePosition, LineSeparator}; use std::io::prelude::Write; @@ -150,47 +151,68 @@ pub fn txs( let mut table = table!(); - table.set_titles(row![ - bMG->"Id", - bMG->"Type", - bMG->"State", - bMG->"Shared Transaction Id", - bMG->"Creation Time", - bMG->"TTL Cutoff Height", - bMG->"Confirmed?", - bMG->"Confirmation Time", - bMG->"Num. \nInputs", - bMG->"Num. \nOutputs", - bMG->"Amount \nCredited", - bMG->"Amount \nDebited", - bMG->"Fee", - bMG->"Net \nDifference", - bMG->"Payment \nProof", - bMG->"Kernel", - bMG->"Tx \nData", - ]); + table.set_titles(if dark_background_color_scheme { + row![ + bMG->"Id", + bMG->"Type (State)", + bMG->"Shared Transaction Id \nKernel", + bMG->"Creation Time \nConfirmation Time", + bMG->"Payment Proof \nTTL Cutoff Height", + bMG->"Inputs \nOutputs", + bMG->"Credited \nDebited", + bMG->"Fee \nDifference", + ] + } else { + row![ + bFD ->"Id", + bFD->"Type (State)", + bFD->"Shared Transaction Id \nKernel", + bFD->"Creation Time \nConfirmation Time", + bFD->"Payment Proof \nTTL Cutoff Height", + bFD->"Inputs \nOutputs", + bFD->"Credited \nDebited", + bFD->"Fee \nDifference", + ] + }); - for t in txs { + for (i, t) in txs.iter().enumerate() { let id = format!("{}", t.id); let slate_id = match t.tx_slate_id { Some(m) => format!("{}", m), None => "None".to_owned(), }; - let slate_state = match t.tx_slate_state.as_ref() { - Some(m) => format!("{}", m), - None => "None".to_owned(), + + let entry_type = (match t.tx_type { + TxLogEntryType::ConfirmedCoinbase => "Coinbase", + TxLogEntryType::TxReceived => "Received Tx", + TxLogEntryType::TxSent => "Sent Tx", + TxLogEntryType::TxReceivedCancelled => "Received Tx", + TxLogEntryType::TxSentCancelled => "Sent Tx", + TxLogEntryType::TxReverted => "Received Tx", + }) + .to_string(); + let entry_type_state = match t.tx_slate_state.as_ref() { + None => entry_type.to_string(), + Some(s) => format!("{} ({})", entry_type, s), }; - let entry_type = format!("{}", t.tx_type); + let entry_type_desc = (match t.tx_type { + TxLogEntryType::ConfirmedCoinbase => "Confirmed", + TxLogEntryType::TxReceived => "", + TxLogEntryType::TxSent => "", + TxLogEntryType::TxReceivedCancelled => "Cancelled", + TxLogEntryType::TxSentCancelled => "Cancelled", + TxLogEntryType::TxReverted => "Reverted", + }) + .to_string(); let creation_ts = format!("{}", t.creation_ts.format("%Y-%m-%d %H:%M:%S")); let ttl_cutoff_height = match t.ttl_cutoff_height { - Some(b) => format!("{}", b), + Some(b) => b.to_string(), None => "None".to_owned(), }; let confirmation_ts = match t.confirmation_ts { Some(m) => format!("{}", m.format("%Y-%m-%d %H:%M:%S")), None => "None".to_owned(), }; - let confirmed = format!("{}", t.confirmed); let num_inputs = format!("{}", t.num_inputs); let num_outputs = format!("{}", t.num_outputs); let amount_debited_str = amount_to_hr_string(t.amount_debited, true); @@ -207,10 +229,6 @@ pub fn txs( amount_to_hr_string(t.amount_debited - t.amount_credited, true) ) }; - let tx_data = match t.stored_tx { - Some(_) => "Yes".to_owned(), - None => "None".to_owned(), - }; let kernel_excess = match t.kernel_excess { Some(e) => { let excess: &[u8] = e.0.as_ref(); @@ -225,65 +243,52 @@ pub fn txs( if dark_background_color_scheme { table.add_row(row![ bFC->id, - bFC->entry_type, - bFC->slate_state, + bFC->entry_type_state, bFC->slate_id, bFB->creation_ts, - bFB->ttl_cutoff_height, - bFC->confirmed, - bFB->confirmation_ts, + bFC->payment_proof, bFC->num_inputs, - bFC->num_outputs, bFG->amount_credited_str, - bFR->amount_debited_str, bFR->fee, - bFY->net_diff, - bfG->payment_proof, + ]); + table.add_row(row![ + bFD->"", + bFC->entry_type_desc, bFB->kernel_excess, - bFb->tx_data, + bFB->confirmation_ts, + bFB->ttl_cutoff_height, + bFC->num_outputs, + bFR->amount_debited_str, + bFY->net_diff, ]); } else { - if t.confirmed { - table.add_row(row![ - bFD->id, - bFb->entry_type, - bFD->slate_id, - bFB->creation_ts, - bFg->confirmed, - bFB->confirmation_ts, - bFD->num_inputs, - bFD->num_outputs, - bFG->amount_credited_str, - bFD->amount_debited_str, - bFD->fee, - bFG->net_diff, - bfG->payment_proof, - bFB->kernel_excess, - bFB->tx_data, - ]); - } else { - table.add_row(row![ - bFD->id, - bFb->entry_type, - bFD->slate_id, - bFB->creation_ts, - bFR->confirmed, - bFB->confirmation_ts, - bFD->num_inputs, - bFD->num_outputs, - bFG->amount_credited_str, - bFD->amount_debited_str, - bFD->fee, - bFG->net_diff, - bfG->payment_proof, - bFB->kernel_excess, - bFB->tx_data, - ]); - } + table.add_row(row![ + bFD->id, + bFb->entry_type_state, + bFD->slate_id, + bFB->creation_ts, + bFD->payment_proof, + bFD->num_inputs, + bFG->amount_credited_str, + bFR->fee, + ]); + table.add_row(row![ + bFD->"", + bFb->entry_type_desc, + bFB->kernel_excess, + bFB->confirmation_ts, + bFB->ttl_cutoff_height, + bFD->num_outputs, + bFR->amount_debited_str, + bFG->net_diff, + ]); + } + if i != txs.len() - 1 { + table.add_empty_row(); } } - table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP); + table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE); table.printstd(); println!(); diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 0aec740..b4e2e5e 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -610,12 +610,12 @@ pub enum TxLogEntryType { impl fmt::Display for TxLogEntryType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - TxLogEntryType::ConfirmedCoinbase => write!(f, "Confirmed \nCoinbase"), + TxLogEntryType::ConfirmedCoinbase => write!(f, "Confirmed Coinbase"), TxLogEntryType::TxReceived => write!(f, "Received Tx"), TxLogEntryType::TxSent => write!(f, "Sent Tx"), - TxLogEntryType::TxReceivedCancelled => write!(f, "Received Tx\n- Cancelled"), - TxLogEntryType::TxSentCancelled => write!(f, "Sent Tx\n- Cancelled"), - TxLogEntryType::TxReverted => write!(f, "Received Tx\n- Reverted"), + TxLogEntryType::TxReceivedCancelled => write!(f, "Received Tx - Cancelled"), + TxLogEntryType::TxSentCancelled => write!(f, "Sent Tx - Cancelled"), + TxLogEntryType::TxReverted => write!(f, "Received Tx - Reverted"), } } } From 6c9c5e6b36d736dbaaeedd9cede49595c6673a09 Mon Sep 17 00:00:00 2001 From: wiesche <120273695+wiesche89@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:58:07 +0200 Subject: [PATCH 16/18] fix late lock self send proof (#829) --- controller/tests/self_send.rs | 58 +++++++++++++++++++++++++++++++++++ libwallet/src/internal/tx.rs | 16 +++++----- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/controller/tests/self_send.rs b/controller/tests/self_send.rs index c6ca437..6d1a9a1 100644 --- a/controller/tests/self_send.rs +++ b/controller/tests/self_send.rs @@ -141,3 +141,61 @@ fn wallet_self_send() { } clean_output_dir(test_dir); } + +#[test] +fn late_lock_self_send_proof() -> Result<(), libwallet::Error> { + let test_dir = "test_output/late_lock_self_send_proof"; + setup(test_dir); + let mut wallet_proxy = create_wallet_proxy(test_dir); + let chain = wallet_proxy.chain.clone(); + let stopper = wallet_proxy.running.clone(); + create_wallet_and_add!( + client1, + wallet1, + mask1_i, + test_dir, + "wallet1", + None, + &mut wallet_proxy, + false, + api1 + ); + let mask1 = mask1_i.as_ref(); + let worker = thread::spawn(move || wallet_proxy.run()); + + let result = (|| { + test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 10, false)?; + let mut slate = api1.init_send_tx( + mask1, + InitTxArgs { + amount: core::consensus::REWARD, + minimum_confirmations: 2, + late_lock: Some(true), + payment_proof_recipient_address: Some(api1.get_slatepack_address(mask1, 0)?), + ..Default::default() + }, + )?; + wallet::controller::foreign_single_use( + wallet1.clone(), + PathBuf::from(test_dir), + mask1_i.clone(), + |api| { + slate = api.receive_tx(&slate, None, None)?; + Ok(()) + }, + )?; + let (_, txs) = api1.retrieve_txs(mask1, false, None, Some(slate.id), None)?; + assert_eq!(txs.len(), 1); + assert_eq!(txs[0].tx_type, libwallet::TxLogEntryType::TxReceived); + + api1.finalize_tx(mask1, &slate).map(|_| ()) + })(); + + stopper.store(false, Ordering::Relaxed); + worker.join().unwrap()?; + drop(api1); + drop(wallet1); + drop(chain); + clean_output_dir(test_dir); + result +} diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 6b655e3..379e572 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -500,13 +500,15 @@ where Some(parent_key_id), false, )?; - if tx_vec.is_empty() { - return Err(Error::PaymentProof( - "TxLogEntry with original proof info not found (is account correct?)".to_owned(), - )); - } - - let orig_proof_info = tx_vec[0].clone().payment_proof; + let orig_proof_info = tx_vec + .into_iter() + .find(|tx| tx.tx_type == TxLogEntryType::TxSent) + .ok_or_else(|| { + Error::PaymentProof( + "TxLogEntry with original proof info not found (is account correct?)".to_owned(), + ) + })? + .payment_proof; if orig_proof_info.is_some() && slate.payment_proof.is_none() { return Err(Error::PaymentProof( From 6517e3d4ed1a96d73a768654f6623800377c7832 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Sat, 10 Oct 2026 14:18:40 +0300 Subject: [PATCH 17/18] fix: do not scan outputs twice on payment proof retrieval (#830) --- libwallet/src/api_impl/owner.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 58bfbd2..0f2f46b 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -446,8 +446,8 @@ where "Transaction ID or Slate UUID must be specified".to_owned(), )); } - if refresh_from_node { - update_wallet_state( + let refresh_txs_from_node = if refresh_from_node { + !update_wallet_state( wallet_inst.clone(), keychain_mask, status_send_channel, @@ -460,7 +460,7 @@ where wallet_inst.clone(), keychain_mask, status_send_channel, - refresh_from_node, + refresh_txs_from_node, tx_id, tx_slate_id, None, From f097811ee953fc81be4e70435d6ef845b6b2e8c9 Mon Sep 17 00:00:00 2001 From: wiesche Date: Thu, 8 Oct 2026 22:30:11 +0200 Subject: [PATCH 18/18] bump to version 5.5.1 --- Cargo.lock | 14 +++++++------- Cargo.toml | 12 ++++++------ api/Cargo.toml | 10 +++++----- config/Cargo.toml | 2 +- controller/Cargo.toml | 12 ++++++------ impls/Cargo.toml | 8 ++++---- libwallet/Cargo.toml | 6 +++--- util/Cargo.toml | 2 +- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1948dc..950840e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2433,7 +2433,7 @@ dependencies = [ [[package]] name = "grin_wallet" -version = "5.5.0" +version = "5.5.1" dependencies = [ "built", "clap", @@ -2461,7 +2461,7 @@ dependencies = [ [[package]] name = "grin_wallet_api" -version = "5.5.0" +version = "5.5.1" dependencies = [ "base64 0.12.3", "chrono", @@ -2486,7 +2486,7 @@ dependencies = [ [[package]] name = "grin_wallet_config" -version = "5.5.0" +version = "5.5.1" dependencies = [ "dirs 2.0.2", "grin_core", @@ -2502,7 +2502,7 @@ dependencies = [ [[package]] name = "grin_wallet_controller" -version = "5.5.0" +version = "5.5.1" dependencies = [ "easy-jsonrpc-mw", "ed25519-dalek", @@ -2533,7 +2533,7 @@ dependencies = [ [[package]] name = "grin_wallet_impls" -version = "5.5.0" +version = "5.5.1" dependencies = [ "arti-client", "base64 0.12.3", @@ -2580,7 +2580,7 @@ dependencies = [ [[package]] name = "grin_wallet_libwallet" -version = "5.5.0" +version = "5.5.1" dependencies = [ "age", "base64 0.9.3", @@ -2618,7 +2618,7 @@ dependencies = [ [[package]] name = "grin_wallet_util" -version = "5.5.0" +version = "5.5.1" dependencies = [ "data-encoding", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 901278d..8dbc578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet" -version = "5.5.0" +version = "5.5.1" authors = ["Grin Developers "] description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" @@ -29,11 +29,11 @@ semver = "1.0.28" rustyline = "9" lazy_static = "1" -grin_wallet_api = { path = "./api", version = "5.5.0" } -grin_wallet_impls = { path = "./impls", version = "5.5.0" } -grin_wallet_libwallet = { path = "./libwallet", version = "5.5.0" } -grin_wallet_controller = { path = "./controller", version = "5.5.0" } -grin_wallet_config = { path = "./config", version = "5.5.0" } +grin_wallet_api = { path = "./api", version = "5.5.1" } +grin_wallet_impls = { path = "./impls", version = "5.5.1" } +grin_wallet_libwallet = { path = "./libwallet", version = "5.5.1" } +grin_wallet_controller = { path = "./controller", version = "5.5.1" } +grin_wallet_config = { path = "./config", version = "5.5.1" } ##### Grin Imports diff --git a/api/Cargo.toml b/api/Cargo.toml index c797161..f364849 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_api" -version = "5.5.0" +version = "5.5.1" authors = ["Grin Developers "] description = "Grin Wallet API" license = "Apache-2.0" @@ -22,10 +22,10 @@ ring = "0.17.14" base64 = "0.12" ed25519-dalek = "2" -grin_wallet_libwallet = { path = "../libwallet", version = "5.5.0" } -grin_wallet_config = { path = "../config", version = "5.5.0" } -grin_wallet_impls = { path = "../impls", version = "5.5.0" } -grin_wallet_util = { path = "../util", version = "5.5.0" } +grin_wallet_libwallet = { path = "../libwallet", version = "5.5.1" } +grin_wallet_config = { path = "../config", version = "5.5.1" } +grin_wallet_impls = { path = "../impls", version = "5.5.1" } +grin_wallet_util = { path = "../util", version = "5.5.1" } ##### Grin Imports diff --git a/config/Cargo.toml b/config/Cargo.toml index 5d7e339..6600bc0 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_config" -version = "5.5.0" +version = "5.5.1" authors = ["Grin Developers "] description = "Configuration for grin wallet , a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 5de94f2..4019663 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_controller" -version = "5.5.0" +version = "5.5.1" authors = ["Grin Developers "] description = "Controllers for grin wallet instantiation" license = "Apache-2.0" @@ -25,11 +25,11 @@ lazy_static = "1" thiserror = "1" qr_code = "1.1.0" -grin_wallet_util = { path = "../util", version = "5.5.0" } -grin_wallet_api = { path = "../api", version = "5.5.0" } -grin_wallet_impls = { path = "../impls", version = "5.5.0" } -grin_wallet_libwallet = { path = "../libwallet", version = "5.5.0" } -grin_wallet_config = { path = "../config", version = "5.5.0" } +grin_wallet_util = { path = "../util", version = "5.5.1" } +grin_wallet_api = { path = "../api", version = "5.5.1" } +grin_wallet_impls = { path = "../impls", version = "5.5.1" } +grin_wallet_libwallet = { path = "../libwallet", version = "5.5.1" } +grin_wallet_config = { path = "../config", version = "5.5.1" } ##### Grin Imports diff --git a/impls/Cargo.toml b/impls/Cargo.toml index b93b32f..314510a 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_impls" -version = "5.5.0" +version = "5.5.1" authors = ["Grin Developers "] description = "Concrete types derived from libwallet traits" license = "Apache-2.0" @@ -47,9 +47,9 @@ hyper-util = { version = "0.1.20", features = ["tokio"] } http-body-util = "0.1.3" bytes = "1.11.1" -grin_wallet_util = { path = "../util", version = "5.5.0" } -grin_wallet_config = { path = "../config", version = "5.5.0" } -grin_wallet_libwallet = { path = "../libwallet", version = "5.5.0" } +grin_wallet_util = { path = "../util", version = "5.5.1" } +grin_wallet_config = { path = "../config", version = "5.5.1" } +grin_wallet_libwallet = { path = "../libwallet", version = "5.5.1" } ##### Grin Imports diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index c6bfe82..824af61 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_libwallet" -version = "5.5.0" +version = "5.5.1" authors = ["Grin Developers "] description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." license = "Apache-2.0" @@ -42,8 +42,8 @@ num-bigint = "0.2" chacha20 = "0.8.1" hmac = { version = "0.12.0", features = ["std"]} -grin_wallet_util = { path = "../util", version = "5.5.0" } -grin_wallet_config = { path = "../config", version = "5.5.0" } +grin_wallet_util = { path = "../util", version = "5.5.1" } +grin_wallet_config = { path = "../config", version = "5.5.1" } ##### Grin Imports diff --git a/util/Cargo.toml b/util/Cargo.toml index 898522d..2bf7192 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin_wallet_util" -version = "5.5.0" +version = "5.5.1" authors = ["Grin Developers "] description = "Util, for generic utilities and to re-export grin crates" license = "Apache-2.0"