From 1cc2c645fa1e6fc338c62b96eab830c13012d186 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 30 Aug 2023 18:11:56 +0300 Subject: [PATCH] Fix Wallet Loading issues (basic_string & input_stream) (#1059) * Recover from wallet loading exceptions (basic_string & input_stream) Recover from removed cached wallets * Fix restoring as Monero wallets Fix restoring wallets with invalid files * Add coin control missing changes for macos monero files * Add same key for cached dependencies [skip ci] --- .github/workflows/cache_dependencies.yml | 2 +- cw_monero/lib/monero_wallet_service.dart | 21 +- cw_monero/macos/Classes/monero_api.cpp | 203 +++++++++++++++++-- lib/entities/default_settings_migration.dart | 77 ++++++- macos/Podfile.lock | 8 +- 5 files changed, 279 insertions(+), 32 deletions(-) diff --git a/.github/workflows/cache_dependencies.yml b/.github/workflows/cache_dependencies.yml index d57281447..ef4175641 100644 --- a/.github/workflows/cache_dependencies.yml +++ b/.github/workflows/cache_dependencies.yml @@ -45,7 +45,7 @@ jobs: /opt/android/cake_wallet/cw_monero/android/.cxx /opt/android/cake_wallet/cw_monero/ios/External /opt/android/cake_wallet/cw_shared_external/ios/External - key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh') }} + key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }} - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} name: Generate Externals diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 90c63fb72..9d1a0b3e4 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -57,7 +57,7 @@ class MoneroWalletService extends WalletService< final Box walletInfoSource; final Box unspentCoinsInfoSource; - + static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync(); @@ -124,13 +124,20 @@ class MoneroWalletService extends WalletService< } catch (e) { // TODO: Implement Exception for wallet list service. - if ((e.toString().contains('bad_alloc') || + final bool isBadAlloc = e.toString().contains('bad_alloc') || (e is WalletOpeningException && - (e.message == 'std::bad_alloc' || - e.message.contains('bad_alloc')))) || - (e.toString().contains('does not correspond') || - (e is WalletOpeningException && - e.message.contains('does not correspond')))) { + (e.message == 'std::bad_alloc' || e.message.contains('bad_alloc'))); + + final bool doesNotCorrespond = e.toString().contains('does not correspond') || + (e is WalletOpeningException && e.message.contains('does not correspond')); + + final bool isMissingCacheFilesIOS = e.toString().contains('basic_string') || + (e is WalletOpeningException && e.message.contains('basic_string')); + + final bool isMissingCacheFilesAndroid = e.toString().contains('input_stream') || + (e is WalletOpeningException && e.message.contains('input_stream')); + + if (isBadAlloc || doesNotCorrespond || isMissingCacheFilesIOS || isMissingCacheFilesAndroid) { await restoreOrResetWalletFiles(name); return openWallet(name, password); } diff --git a/cw_monero/macos/Classes/monero_api.cpp b/cw_monero/macos/Classes/monero_api.cpp index 56548e79e..ac8a64861 100644 --- a/cw_monero/macos/Classes/monero_api.cpp +++ b/cw_monero/macos/Classes/monero_api.cpp @@ -3,8 +3,10 @@ #include #include #include +#include #include #include +#include #include "thread" #include "CwWalletListener.h" #if __APPLE__ @@ -137,7 +139,7 @@ extern "C" int8_t direction; int8_t isPending; uint32_t subaddrIndex; - + char *hash; char *paymentId; @@ -152,7 +154,7 @@ extern "C" std::set::iterator it = transaction->subaddrIndex().begin(); subaddrIndex = *it; confirmations = transaction->confirmations(); - datetime = static_cast(transaction->timestamp()); + datetime = static_cast(transaction->timestamp()); direction = transaction->direction(); isPending = static_cast(transaction->isPending()); std::string *hash_str = new std::string(transaction->hash()); @@ -181,6 +183,62 @@ extern "C" } }; + struct CoinsInfoRow + { + uint64_t blockHeight; + char *hash; + uint64_t internalOutputIndex; + uint64_t globalOutputIndex; + bool spent; + bool frozen; + uint64_t spentHeight; + uint64_t amount; + bool rct; + bool keyImageKnown; + uint64_t pkIndex; + uint32_t subaddrIndex; + uint32_t subaddrAccount; + char *address; + char *addressLabel; + char *keyImage; + uint64_t unlockTime; + bool unlocked; + char *pubKey; + bool coinbase; + char *description; + + CoinsInfoRow(Monero::CoinsInfo *coinsInfo) + { + blockHeight = coinsInfo->blockHeight(); + std::string *hash_str = new std::string(coinsInfo->hash()); + hash = strdup(hash_str->c_str()); + internalOutputIndex = coinsInfo->internalOutputIndex(); + globalOutputIndex = coinsInfo->globalOutputIndex(); + spent = coinsInfo->spent(); + frozen = coinsInfo->frozen(); + spentHeight = coinsInfo->spentHeight(); + amount = coinsInfo->amount(); + rct = coinsInfo->rct(); + keyImageKnown = coinsInfo->keyImageKnown(); + pkIndex = coinsInfo->pkIndex(); + subaddrIndex = coinsInfo->subaddrIndex(); + subaddrAccount = coinsInfo->subaddrAccount(); + address = strdup(coinsInfo->address().c_str()) ; + addressLabel = strdup(coinsInfo->addressLabel().c_str()); + keyImage = strdup(coinsInfo->keyImage().c_str()); + unlockTime = coinsInfo->unlockTime(); + unlocked = coinsInfo->unlocked(); + pubKey = strdup(coinsInfo->pubKey().c_str()); + coinbase = coinsInfo->coinbase(); + description = strdup(coinsInfo->description().c_str()); + } + + void setUnlocked(bool unlocked); + + }; + + Monero::Coins *m_coins; + Monero::Wallet *m_wallet; Monero::TransactionHistory *m_transaction_history; MoneroWalletListener *m_listener; @@ -188,6 +246,7 @@ extern "C" Monero::SubaddressAccount *m_account; uint64_t m_last_known_wallet_height; uint64_t m_cached_syncing_blockchain_height = 0; + std::list m_coins_info; std::mutex store_lock; bool is_storing = false; @@ -195,7 +254,7 @@ extern "C" { m_wallet = wallet; m_listener = nullptr; - + if (wallet != nullptr) { @@ -223,6 +282,17 @@ extern "C" { m_subaddress = nullptr; } + + m_coins_info = std::list(); + + if (wallet != nullptr) + { + m_coins = wallet->coins(); + } + else + { + m_coins = nullptr; + } } Monero::Wallet *get_current_wallet() @@ -405,13 +475,14 @@ extern "C" return is_connected; } - bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error) + bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *socksProxyAddress, char *error) { nice(19); Monero::Wallet *wallet = get_current_wallet(); - + std::string _login = ""; std::string _password = ""; + std::string _socksProxyAddress = ""; if (login != nullptr) { @@ -423,7 +494,12 @@ extern "C" _password = std::string(password); } - bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet); + if (socksProxyAddress != nullptr) + { + _socksProxyAddress = std::string(socksProxyAddress); + } + + bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet, _socksProxyAddress); if (!inited) { @@ -480,10 +556,19 @@ extern "C" } bool transaction_create(char *address, char *payment_id, char *amount, - uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + uint8_t priority_raw, uint32_t subaddr_account, + char **preferred_inputs, uint32_t preferred_inputs_size, + Utf8Box &error, PendingTransactionRaw &pendingTransaction) { nice(19); - + + std::set _preferred_inputs; + + for (int i = 0; i < preferred_inputs_size; i++) { + _preferred_inputs.insert(std::string(*preferred_inputs)); + preferred_inputs++; + } + auto priority = static_cast(priority_raw); std::string _payment_id; Monero::PendingTransaction *transaction; @@ -496,13 +581,13 @@ extern "C" if (amount != nullptr) { uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount)); - transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account); + transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs); } else { - transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account); + transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs); } - + int status = transaction->status(); if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) @@ -520,7 +605,9 @@ extern "C" } bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size, - uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + uint8_t priority_raw, uint32_t subaddr_account, + char **preferred_inputs, uint32_t preferred_inputs_size, + Utf8Box &error, PendingTransactionRaw &pendingTransaction) { nice(19); @@ -534,6 +621,13 @@ extern "C" amounts++; } + std::set _preferred_inputs; + + for (int i = 0; i < preferred_inputs_size; i++) { + _preferred_inputs.insert(std::string(*preferred_inputs)); + preferred_inputs++; + } + auto priority = static_cast(priority_raw); std::string _payment_id; Monero::PendingTransaction *transaction; @@ -793,6 +887,91 @@ extern "C" return m_wallet->trustedDaemon(); } + CoinsInfoRow* coin(int index) + { + if (index >= 0 && index < m_coins_info.size()) { + std::list::iterator it = m_coins_info.begin(); + std::advance(it, index); + Monero::CoinsInfo* element = *it; + std::cout << "Element at index " << index << ": " << element << std::endl; + return new CoinsInfoRow(element); + } else { + std::cout << "Invalid index." << std::endl; + return nullptr; // Return a default value (nullptr) for invalid index + } + } + + void refresh_coins(uint32_t accountIndex) + { + m_coins_info.clear(); + + m_coins->refresh(); + for (const auto i : m_coins->getAll()) { + if (i->subaddrAccount() == accountIndex && !(i->spent())) { + m_coins_info.push_back(i); + } + } + } + + uint64_t coins_count() + { + return m_coins_info.size(); + } + + CoinsInfoRow** coins_from_account(uint32_t accountIndex) + { + std::vector matchingCoins; + + for (int i = 0; i < coins_count(); i++) { + CoinsInfoRow* coinInfo = coin(i); + if (coinInfo->subaddrAccount == accountIndex) { + matchingCoins.push_back(coinInfo); + } + } + + CoinsInfoRow** result = new CoinsInfoRow*[matchingCoins.size()]; + std::copy(matchingCoins.begin(), matchingCoins.end(), result); + return result; + } + + CoinsInfoRow** coins_from_txid(const char* txid, size_t* count) + { + std::vector matchingCoins; + + for (int i = 0; i < coins_count(); i++) { + CoinsInfoRow* coinInfo = coin(i); + if (std::string(coinInfo->hash) == txid) { + matchingCoins.push_back(coinInfo); + } + } + + *count = matchingCoins.size(); + CoinsInfoRow** result = new CoinsInfoRow*[*count]; + std::copy(matchingCoins.begin(), matchingCoins.end(), result); + return result; + } + + CoinsInfoRow** coins_from_key_image(const char** keyimages, size_t keyimageCount, size_t* count) + { + std::vector matchingCoins; + + for (int i = 0; i < coins_count(); i++) { + CoinsInfoRow* coinsInfoRow = coin(i); + for (size_t j = 0; j < keyimageCount; j++) { + if (coinsInfoRow->keyImageKnown && std::string(coinsInfoRow->keyImage) == keyimages[j]) { + matchingCoins.push_back(coinsInfoRow); + break; + } + } + } + + *count = matchingCoins.size(); + CoinsInfoRow** result = new CoinsInfoRow*[*count]; + std::copy(matchingCoins.begin(), matchingCoins.end(), result); + return result; + } + + #ifdef __cplusplus } #endif diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index b4cb23131..a783eec43 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -1,12 +1,11 @@ -import 'dart:io' show File, Platform; +import 'dart:io' show Directory, File, Platform; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; -import 'package:share_plus/share_plus.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cw_core/wallet_type.dart'; @@ -28,7 +27,7 @@ const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; const ethereumDefaultNodeUri = 'ethereum.publicnode.com'; -Future defaultSettingsMigration( +Future defaultSettingsMigration( {required int version, required SharedPreferences sharedPreferences, required FlutterSecureStorage secureStorage, @@ -43,6 +42,8 @@ Future defaultSettingsMigration( // check current nodes for nullability regardless of the version await checkCurrentNodes(nodes, sharedPreferences); + await _validateWalletInfoBoxData(walletInfoSource); + final isNewInstall = sharedPreferences .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null; @@ -179,6 +180,66 @@ Future defaultSettingsMigration( PreferencesKey.currentDefaultSettingsMigrationVersion, version); } +Future _validateWalletInfoBoxData(Box walletInfoSource) async { + final root = await getApplicationDocumentsDirectory(); + + for (var type in WalletType.values) { + if (type == WalletType.none) { + continue; + } + + String prefix = walletTypeToString(type).toLowerCase(); + Directory walletsDir = Directory('${root.path}/wallets/$prefix/'); + + if (!walletsDir.existsSync()) { + continue; + } + + List walletNames = walletsDir.listSync().map((e) => e.path.split("/").last).toList(); + + for (var name in walletNames) { + final dir = Directory(await pathForWalletDir(name: name, type: type)); + + final walletFiles = dir.listSync(); + final hasCacheFile = walletFiles.any((element) => element.path.contains("$name/$name")); + + if (!hasCacheFile) { + continue; + } + + if (type == WalletType.monero || type == WalletType.haven) { + final hasKeysFile = walletFiles.any((element) => element.path.contains(".keys")); + + if (!hasKeysFile) { + continue; + } + } + + final id = prefix + '_' + name; + final exist = walletInfoSource.values.any((el) => el.id == id); + + if (exist) { + continue; + } + + final walletInfo = WalletInfo.external( + id: id, + type: type, + name: name, + isRecovery: true, + restoreHeight: 0, + date: DateTime.now(), + dirPath: dir.path, + path: '${dir.path}/$name', + address: '', + showIntroCakePayCard: false, + ); + + walletInfoSource.add(walletInfo); + } + } +} + Future validateBitcoinSavedTransactionPriority(SharedPreferences sharedPreferences) async { if (bitcoin == null) { return; @@ -226,7 +287,7 @@ Future changeMoneroCurrentNodeToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final node = getMoneroDefaultNode(nodes: nodes); - final nodeId = node?.key as int ?? 0; // 0 - England + final nodeId = node.key as int? ?? 0; // 0 - England await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, nodeId); } @@ -279,7 +340,7 @@ Future changeBitcoinCurrentElectrumServerToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final server = getBitcoinDefaultElectrumServer(nodes: nodes); - final serverId = server?.key as int ?? 0; + final serverId = server?.key as int? ?? 0; await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, serverId); } @@ -288,7 +349,7 @@ Future changeLitecoinCurrentElectrumServerToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final server = getLitecoinDefaultElectrumServer(nodes: nodes); - final serverId = server?.key as int ?? 0; + final serverId = server?.key as int? ?? 0; await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId); } @@ -297,7 +358,7 @@ Future changeHavenCurrentNodeToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final node = getHavenDefaultNode(nodes: nodes); - final nodeId = node?.key as int ?? 0; + final nodeId = node?.key as int? ?? 0; await sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, nodeId); } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 6657ec4dc..664a5231b 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -57,11 +57,11 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) - package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - platform_device_id (from `Flutter/ephemeral/.symlinks/plugins/platform_device_id/macos`) - platform_device_id_macos (from `Flutter/ephemeral/.symlinks/plugins/platform_device_id_macos/macos`) - share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`) - - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`) @@ -87,7 +87,7 @@ EXTERNAL SOURCES: package_info: :path: Flutter/ephemeral/.symlinks/plugins/package_info/macos path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin platform_device_id: :path: Flutter/ephemeral/.symlinks/plugins/platform_device_id/macos platform_device_id_macos: @@ -95,7 +95,7 @@ EXTERNAL SOURCES: share_plus_macos: :path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos shared_preferences_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos wakelock_macos: