From 4b09b59ce0cbc7e5c270c4c06a671c2fcff18bfc Mon Sep 17 00:00:00 2001 From: Andrejs Gubarevs <1062334+agubarev@users.noreply.github.com> Date: Fri, 24 Mar 2023 14:59:54 +0200 Subject: [PATCH] feat: added FFI function `wallet_get_network_and_version` #5252 (#5263) Description --- - Network and client APP_VERSION_NUMBER is now stored when wallet is initialized. - Added FFI function `wallet_get_network_and_version` that returns the `network` and `version` of the client that has last accessed the wallet database. Motivation and Context --- For debugging purposes on the client (e.g. Mobile) How Has This Been Tested? --- manually --------- Co-authored-by: SW van Heerden Co-authored-by: stringhandler --- base_layer/wallet/Cargo.toml | 3 + base_layer/wallet/build.rs | 10 ++ base_layer/wallet/src/lib.rs | 5 + base_layer/wallet/src/storage/database.rs | 17 ++ .../wallet/src/storage/sqlite_db/wallet.rs | 13 +- .../src/storage/sqlite_utilities/mod.rs | 31 +++- base_layer/wallet/src/wallet.rs | 8 + base_layer/wallet_ffi/src/lib.rs | 166 +++++++++++++++++- base_layer/wallet_ffi/wallet.h | 32 ++++ common/src/logging.rs | 2 +- 10 files changed, 282 insertions(+), 5 deletions(-) create mode 100644 base_layer/wallet/build.rs diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index d0f5d8ae63..0ac3c58f7c 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -56,6 +56,9 @@ itertools = "0.10.3" chacha20poly1305 = "0.10.1" zeroize = "1" +[build-dependencies] +tari_common = { path = "../../common", features = ["build", "static-application-info"] } + [dev-dependencies] tari_p2p = { path = "../p2p", features = ["test-mocks"] } tari_comms_dht = { path = "../../comms/dht", features = ["test-mocks"] } diff --git a/base_layer/wallet/build.rs b/base_layer/wallet/build.rs new file mode 100644 index 0000000000..399221badd --- /dev/null +++ b/base_layer/wallet/build.rs @@ -0,0 +1,10 @@ +// Copyright 2022. The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_common::build::StaticApplicationInfo; + +fn main() { + // generate version info + let gen = StaticApplicationInfo::initialize().unwrap(); + gen.write_consts_to_outdir("consts.rs").unwrap(); +} diff --git a/base_layer/wallet/src/lib.rs b/base_layer/wallet/src/lib.rs index 120e437ae4..c43b399958 100644 --- a/base_layer/wallet/src/lib.rs +++ b/base_layer/wallet/src/lib.rs @@ -46,6 +46,11 @@ use crate::{ transaction_service::storage::sqlite_db::TransactionServiceSqliteDatabase, }; +mod consts { + // Import the auto-generated const values from the Manifest and Git + include!(concat!(env!("OUT_DIR"), "/consts.rs")); +} + pub type WalletSqlite = Wallet< WalletSqliteDatabase, TransactionServiceSqliteDatabase, diff --git a/base_layer/wallet/src/storage/database.rs b/base_layer/wallet/src/storage/database.rs index 24555b1809..4ab418ba76 100644 --- a/base_layer/wallet/src/storage/database.rs +++ b/base_layer/wallet/src/storage/database.rs @@ -77,6 +77,8 @@ pub enum DbKey { SecondaryKeyVersion, // the parameter version for the secondary derivation key SecondaryKeyHash, // a hash commitment to the secondary derivation key WalletBirthday, + LastAccessedNetwork, + LastAccessedVersion, } impl DbKey { @@ -94,6 +96,8 @@ impl DbKey { DbKey::SecondaryKeyHash => "SecondaryKeyHash".to_string(), DbKey::WalletBirthday => "WalletBirthday".to_string(), DbKey::CommsIdentitySignature => "CommsIdentitySignature".to_string(), + DbKey::LastAccessedNetwork => "LastAccessedNetwork".to_string(), + DbKey::LastAccessedVersion => "LastAccessedVersion".to_string(), } } } @@ -112,6 +116,8 @@ pub enum DbValue { SecondaryKeyVersion(String), SecondaryKeyHash(String), WalletBirthday(String), + LastAccessedNetwork(String), + LastAccessedVersion(String), } #[derive(Clone)] @@ -123,6 +129,7 @@ pub enum DbKeyValuePair { CommsAddress(Multiaddr), CommsFeatures(PeerFeatures), CommsIdentitySignature(Box), + NetworkAndVersion((String, String)), } pub enum WriteOperation { @@ -255,6 +262,14 @@ where T: WalletBackend + 'static Ok(()) } + pub fn set_last_network_and_version(&self, network: String, version: String) -> Result<(), WalletStorageError> { + self.db + .write(WriteOperation::Insert(DbKeyValuePair::NetworkAndVersion(( + network, version, + ))))?; + Ok(()) + } + pub fn get_client_key_value(&self, key: String) -> Result, WalletStorageError> { let c = match self.db.fetch(&DbKey::ClientKey(key.clone())) { Ok(None) => Ok(None), @@ -354,6 +369,8 @@ impl Display for DbValue { DbValue::SecondaryKeyHash(h) => f.write_str(&format!("SecondaryKeyHash: {}", h)), DbValue::WalletBirthday(b) => f.write_str(&format!("WalletBirthday: {}", b)), DbValue::CommsIdentitySignature(_) => f.write_str("CommsIdentitySignature"), + DbValue::LastAccessedNetwork(network) => f.write_str(&format!("LastAccessedNetwork: {}", network)), + DbValue::LastAccessedVersion(version) => f.write_str(&format!("LastAccessedVersion: {}", version)), } } } diff --git a/base_layer/wallet/src/storage/sqlite_db/wallet.rs b/base_layer/wallet/src/storage/sqlite_db/wallet.rs index 4431dbc437..44deffabe8 100644 --- a/base_layer/wallet/src/storage/sqlite_db/wallet.rs +++ b/base_layer/wallet/src/storage/sqlite_db/wallet.rs @@ -414,7 +414,14 @@ impl WalletSqliteDatabase { WalletSettingSql::new(DbKey::CommsIdentitySignature, identity_sig.to_bytes().to_hex()) .set(&mut conn)?; }, + DbKeyValuePair::NetworkAndVersion((network, version)) => { + kvp_text = "NetworkAndVersion"; + + WalletSettingSql::new(DbKey::LastAccessedNetwork, network).set(&mut conn)?; + WalletSettingSql::new(DbKey::LastAccessedVersion, version).set(&mut conn)?; + }, } + if start.elapsed().as_millis() > 0 { trace!( target: LOG_TARGET, @@ -452,7 +459,9 @@ impl WalletSqliteDatabase { DbKey::SecondaryKeySalt | DbKey::SecondaryKeyHash | DbKey::WalletBirthday | - DbKey::CommsIdentitySignature => { + DbKey::CommsIdentitySignature | + DbKey::LastAccessedNetwork | + DbKey::LastAccessedVersion => { return Err(WalletStorageError::OperationNotSupported); }, }; @@ -499,6 +508,8 @@ impl WalletBackend for WalletSqliteDatabase { DbKey::SecondaryKeySalt => WalletSettingSql::get(key, &mut conn)?.map(DbValue::SecondaryKeySalt), DbKey::SecondaryKeyHash => WalletSettingSql::get(key, &mut conn)?.map(DbValue::SecondaryKeyHash), DbKey::WalletBirthday => WalletSettingSql::get(key, &mut conn)?.map(DbValue::WalletBirthday), + DbKey::LastAccessedNetwork => WalletSettingSql::get(key, &mut conn)?.map(DbValue::LastAccessedNetwork), + DbKey::LastAccessedVersion => WalletSettingSql::get(key, &mut conn)?.map(DbValue::LastAccessedVersion), DbKey::CommsIdentitySignature => WalletSettingSql::get(key, &mut conn)? .and_then(|s| from_hex(&s).ok()) .and_then(|bytes| IdentitySignature::from_bytes(&bytes).ok()) diff --git a/base_layer/wallet/src/storage/sqlite_utilities/mod.rs b/base_layer/wallet/src/storage/sqlite_utilities/mod.rs index c6b63d30d2..b08d36b46b 100644 --- a/base_layer/wallet/src/storage/sqlite_utilities/mod.rs +++ b/base_layer/wallet/src/storage/sqlite_utilities/mod.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{fs::File, path::Path, time::Duration}; +use std::{fs::File, ops::DerefMut, path::Path, time::Duration}; use diesel_migrations::{EmbeddedMigrations, MigrationHarness}; use fs2::FileExt; @@ -34,7 +34,10 @@ use crate::{ error::WalletStorageError, key_manager_service::storage::sqlite_db::KeyManagerSqliteDatabase, output_manager_service::storage::sqlite_db::OutputManagerSqliteDatabase, - storage::sqlite_db::wallet::WalletSqliteDatabase, + storage::{ + database::DbKey, + sqlite_db::wallet::{WalletSettingSql, WalletSqliteDatabase}, + }, transaction_service::storage::sqlite_db::TransactionServiceSqliteDatabase, }; @@ -142,3 +145,27 @@ pub fn initialize_sqlite_database_backends>( key_manager_backend, )) } + +pub fn get_last_version>(db_path: P) -> Result, WalletStorageError> { + let path_str = db_path + .as_ref() + .to_str() + .ok_or(WalletStorageError::InvalidUnicodePath)?; + + let mut pool = SqliteConnectionPool::new(String::from(path_str), 1, true, true, Duration::from_secs(60)); + pool.create_pool()?; + + WalletSettingSql::get(&DbKey::LastAccessedVersion, pool.get_pooled_connection()?.deref_mut()) +} + +pub fn get_last_network>(db_path: P) -> Result, WalletStorageError> { + let path_str = db_path + .as_ref() + .to_str() + .ok_or(WalletStorageError::InvalidUnicodePath)?; + + let mut pool = SqliteConnectionPool::new(String::from(path_str), 1, true, true, Duration::from_secs(60)); + pool.create_pool()?; + + WalletSettingSql::get(&DbKey::LastAccessedNetwork, pool.get_pooled_connection()?.deref_mut()) +} diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 86290181b5..60ae3ed301 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -82,6 +82,7 @@ use crate::{ base_node_service::{handle::BaseNodeServiceHandle, BaseNodeServiceInitializer}, config::{WalletConfig, KEY_MANAGER_COMMS_SECRET_KEY_BRANCH_KEY}, connectivity_service::{WalletConnectivityHandle, WalletConnectivityInitializer, WalletConnectivityInterface}, + consts, error::{WalletError, WalletStorageError}, key_manager_service::{storage::database::KeyManagerBackend, KeyManagerHandle, KeyManagerInitializer}, output_manager_service::{ @@ -279,6 +280,13 @@ where wallet_database.set_comms_identity_signature(identity_sig)?; } + // storing current network and version + if let Err(e) = wallet_database + .set_last_network_and_version(config.network.to_string(), consts::APP_VERSION_NUMBER.to_string()) + { + warn!("failed to store network and version: {:#?}", e); + } + Ok(Self { network: config.network.into(), comms, diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index d28ba21886..c0024565cb 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -149,7 +149,7 @@ use tari_wallet::{ storage::{ database::WalletDatabase, sqlite_db::wallet::WalletSqliteDatabase, - sqlite_utilities::initialize_sqlite_database_backends, + sqlite_utilities::{get_last_network, get_last_version, initialize_sqlite_database_backends}, }, transaction_service::{ config::TransactionServiceConfig, @@ -5447,6 +5447,84 @@ pub unsafe extern "C" fn wallet_create( } } +/// Retrieves the version of an app that last accessed the wallet database +/// +/// ## Arguments +/// `config` - The TariCommsConfig pointer +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// ## Returns +/// `*mut c_char` - Returns the pointer to the hexadecimal representation of the signature and +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string coming from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn wallet_get_last_version(config: *mut TariCommsConfig, error_out: *mut c_int) -> *mut c_char { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if config.is_null() { + error = LibWalletError::from(InterfaceError::NullError("config".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + + let sql_database_path = (*config) + .datastore_path + .join((*config).peer_database_name.clone()) + .with_extension("sqlite3"); + match get_last_version(sql_database_path) { + Ok(None) => ptr::null_mut(), + Ok(Some(version)) => { + let version = CString::new(version).expect("failed to initialize CString"); + version.into_raw() + }, + Err(e) => { + error = LibWalletError::from(WalletError::WalletStorageError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + +/// Retrieves the network of an app that last accessed the wallet database +/// +/// ## Arguments +/// `config` - The TariCommsConfig pointer +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// ## Returns +/// `*mut c_char` - Returns the pointer to the hexadecimal representation of the signature and +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string coming from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn wallet_get_last_network(config: *mut TariCommsConfig, error_out: *mut c_int) -> *mut c_char { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if config.is_null() { + error = LibWalletError::from(InterfaceError::NullError("config".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + + let sql_database_path = (*config) + .datastore_path + .join((*config).peer_database_name.clone()) + .with_extension("sqlite3"); + match get_last_network(sql_database_path) { + Ok(None) => ptr::null_mut(), + Ok(Some(network)) => { + let network = CString::new(network).expect("failed to initialize CString"); + network.into_raw() + }, + Err(e) => { + error = LibWalletError::from(WalletError::WalletStorageError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + /// Retrieves the balance from a wallet /// /// ## Arguments @@ -10356,6 +10434,92 @@ mod test { } } + #[test] + #[allow(clippy::too_many_lines, clippy::needless_collect)] + fn test_wallet_get_network_and_version() { + unsafe { + let mut error = 0; + let error_ptr = &mut error as *mut c_int; + let mut recovery_in_progress = true; + let recovery_in_progress_ptr = &mut recovery_in_progress as *mut bool; + + let secret_key_alice = private_key_generate(); + let db_name_alice = CString::new(random::string(8).as_str()).unwrap(); + let db_name_alice_str: *const c_char = CString::into_raw(db_name_alice) as *const c_char; + let alice_temp_dir = tempdir().unwrap(); + let db_path_alice = CString::new(alice_temp_dir.path().to_str().unwrap()).unwrap(); + let db_path_alice_str: *const c_char = CString::into_raw(db_path_alice) as *const c_char; + let transport_config_alice = transport_memory_create(); + let address_alice = transport_memory_get_address(transport_config_alice, error_ptr); + let address_alice_str = CStr::from_ptr(address_alice).to_str().unwrap().to_owned(); + let address_alice_str: *const c_char = CString::new(address_alice_str).unwrap().into_raw() as *const c_char; + let network = CString::new(NETWORK_STRING).unwrap(); + let network_str: *const c_char = CString::into_raw(network) as *const c_char; + + let alice_config = comms_config_create( + address_alice_str, + transport_config_alice, + db_name_alice_str, + db_path_alice_str, + 20, + 10800, + error_ptr, + ); + + let passphrase: *const c_char = CString::into_raw(CString::new("niao").unwrap()) as *const c_char; + + let alice_wallet = wallet_create( + alice_config, + ptr::null(), + 0, + 0, + passphrase, + ptr::null(), + network_str, + received_tx_callback, + received_tx_reply_callback, + received_tx_finalized_callback, + broadcast_callback, + mined_callback, + mined_unconfirmed_callback, + scanned_callback, + scanned_unconfirmed_callback, + transaction_send_result_callback, + tx_cancellation_callback, + txo_validation_complete_callback, + contacts_liveness_data_updated_callback, + balance_updated_callback, + transaction_validation_complete_callback, + saf_messages_received_callback, + connectivity_status_callback, + recovery_in_progress_ptr, + error_ptr, + ); + assert_eq!(error, 0); + (1..=5).for_each(|i| { + (*alice_wallet) + .runtime + .block_on((*alice_wallet).wallet.output_manager_service.add_output( + create_test_input((15000 * i).into(), 0, &ExtendedPedersenCommitmentFactory::default()).1, + None, + )) + .unwrap(); + }); + + // obtaining network and version + let _ = wallet_get_last_version(alice_config, &mut error as *mut c_int); + let _ = wallet_get_last_network(alice_config, &mut error as *mut c_int); + + string_destroy(db_name_alice_str as *mut c_char); + string_destroy(db_path_alice_str as *mut c_char); + string_destroy(address_alice_str as *mut c_char); + private_key_destroy(secret_key_alice); + transport_config_destroy(transport_config_alice); + comms_config_destroy(alice_config); + wallet_destroy(alice_wallet); + } + } + #[test] fn test_tari_vector() { let mut error = 0; diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index e72975c37f..24084cabe9 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -2701,6 +2701,38 @@ struct TariWallet *wallet_create(TariCommsConfig *config, bool *recovery_in_progress, int *error_out); +/** + * Retrieves the version of an app that last accessed the wallet database + * + * ## Arguments + * `config` - The TariCommsConfig pointer + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * ## Returns + * `*mut c_char` - Returns the pointer to the hexadecimal representation of the signature and + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string coming from rust to prevent a memory leak + */ +char *wallet_get_last_version(TariCommsConfig *config, + int *error_out); + +/** + * Retrieves the network of an app that last accessed the wallet database + * + * ## Arguments + * `config` - The TariCommsConfig pointer + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * ## Returns + * `*mut c_char` - Returns the pointer to the hexadecimal representation of the signature and + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string coming from rust to prevent a memory leak + */ +char *wallet_get_last_network(TariCommsConfig *config, + int *error_out); + /** * Retrieves the balance from a wallet * diff --git a/common/src/logging.rs b/common/src/logging.rs index 0626db50b0..afde7a7b90 100644 --- a/common/src/logging.rs +++ b/common/src/logging.rs @@ -61,7 +61,7 @@ pub fn initialize_logging(config_file: &Path, base_path: &Path, default: &str) - .to_str() .expect("Could not replace {{log_dir}} variable from the log4rs config") // log4rs requires the path to be in a unix format regardless of the system it's running on - .replace("\\", "/"); + .replace('\\', "/"); let contents = contents.replace("{{log_dir}}", &replace_str);