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]

Omar Hatem committed Aug 30, 2023 at 18:11 UTC 1cc2c645fa1e6fc338c62b96eab830c13012d186
5 files changed +279 -32
.github/workflows/cache_dependencies.yml
+1 -1
@@ -45,7 +45,7 @@ jobs:
45 /opt/android/cake_wallet/cw_monero/android/.cxx
46 /opt/android/cake_wallet/cw_monero/ios/External
47 /opt/android/cake_wallet/cw_shared_external/ios/External
48 - key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh') }}
48 + key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }}
49
50 - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
51 name: Generate Externals
cw_monero/lib/monero_wallet_service.dart
+14 -7
@@ -57,7 +57,7 @@ class MoneroWalletService extends WalletService<
57
58 final Box<WalletInfo> walletInfoSource;
59 final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
60 -
60 +
61 static bool walletFilesExist(String path) =>
62 !File(path).existsSync() && !File('$path.keys').existsSync();
63
@@ -124,13 +124,20 @@ class MoneroWalletService extends WalletService<
124 } catch (e) {
125 // TODO: Implement Exception for wallet list service.
126
127 - if ((e.toString().contains('bad_alloc') ||
128 - (e is WalletOpeningException &&
129 - (e.message == 'std::bad_alloc' ||
130 - e.message.contains('bad_alloc')))) ||
131 - (e.toString().contains('does not correspond') ||
127 + final bool isBadAlloc = e.toString().contains('bad_alloc') ||
128 (e is WalletOpeningException &&
133 - e.message.contains('does not correspond')))) {
129 + (e.message == 'std::bad_alloc' || e.message.contains('bad_alloc')));
130 +
131 + final bool doesNotCorrespond = e.toString().contains('does not correspond') ||
132 + (e is WalletOpeningException && e.message.contains('does not correspond'));
133 +
134 + final bool isMissingCacheFilesIOS = e.toString().contains('basic_string') ||
135 + (e is WalletOpeningException && e.message.contains('basic_string'));
136 +
137 + final bool isMissingCacheFilesAndroid = e.toString().contains('input_stream') ||
138 + (e is WalletOpeningException && e.message.contains('input_stream'));
139 +
140 + if (isBadAlloc || doesNotCorrespond || isMissingCacheFilesIOS || isMissingCacheFilesAndroid) {
141 await restoreOrResetWalletFiles(name);
142 return openWallet(name, password);
143 }
cw_monero/macos/Classes/monero_api.cpp
+191 -12
@@ -3,8 +3,10 @@
3 #include <chrono>
4 #include <functional>
5 #include <iostream>
6 +#include <fstream>
7 #include <unistd.h>
8 #include <mutex>
9 +#include <list>
10 #include "thread"
11 #include "CwWalletListener.h"
12 #if __APPLE__
@@ -137,7 +139,7 @@ extern "C"
139 int8_t direction;
140 int8_t isPending;
141 uint32_t subaddrIndex;
140 -
142 +
143 char *hash;
144 char *paymentId;
145
@@ -152,7 +154,7 @@ extern "C"
154 std::set<uint32_t>::iterator it = transaction->subaddrIndex().begin();
155 subaddrIndex = *it;
156 confirmations = transaction->confirmations();
155 - datetime = static_cast<int64_t>(transaction->timestamp());
157 + datetime = static_cast<int64_t>(transaction->timestamp());
158 direction = transaction->direction();
159 isPending = static_cast<int8_t>(transaction->isPending());
160 std::string *hash_str = new std::string(transaction->hash());
@@ -181,6 +183,62 @@ extern "C"
183 }
184 };
185
186 + struct CoinsInfoRow
187 + {
188 + uint64_t blockHeight;
189 + char *hash;
190 + uint64_t internalOutputIndex;
191 + uint64_t globalOutputIndex;
192 + bool spent;
193 + bool frozen;
194 + uint64_t spentHeight;
195 + uint64_t amount;
196 + bool rct;
197 + bool keyImageKnown;
198 + uint64_t pkIndex;
199 + uint32_t subaddrIndex;
200 + uint32_t subaddrAccount;
201 + char *address;
202 + char *addressLabel;
203 + char *keyImage;
204 + uint64_t unlockTime;
205 + bool unlocked;
206 + char *pubKey;
207 + bool coinbase;
208 + char *description;
209 +
210 + CoinsInfoRow(Monero::CoinsInfo *coinsInfo)
211 + {
212 + blockHeight = coinsInfo->blockHeight();
213 + std::string *hash_str = new std::string(coinsInfo->hash());
214 + hash = strdup(hash_str->c_str());
215 + internalOutputIndex = coinsInfo->internalOutputIndex();
216 + globalOutputIndex = coinsInfo->globalOutputIndex();
217 + spent = coinsInfo->spent();
218 + frozen = coinsInfo->frozen();
219 + spentHeight = coinsInfo->spentHeight();
220 + amount = coinsInfo->amount();
221 + rct = coinsInfo->rct();
222 + keyImageKnown = coinsInfo->keyImageKnown();
223 + pkIndex = coinsInfo->pkIndex();
224 + subaddrIndex = coinsInfo->subaddrIndex();
225 + subaddrAccount = coinsInfo->subaddrAccount();
226 + address = strdup(coinsInfo->address().c_str()) ;
227 + addressLabel = strdup(coinsInfo->addressLabel().c_str());
228 + keyImage = strdup(coinsInfo->keyImage().c_str());
229 + unlockTime = coinsInfo->unlockTime();
230 + unlocked = coinsInfo->unlocked();
231 + pubKey = strdup(coinsInfo->pubKey().c_str());
232 + coinbase = coinsInfo->coinbase();
233 + description = strdup(coinsInfo->description().c_str());
234 + }
235 +
236 + void setUnlocked(bool unlocked);
237 +
238 + };
239 +
240 + Monero::Coins *m_coins;
241 +
242 Monero::Wallet *m_wallet;
243 Monero::TransactionHistory *m_transaction_history;
244 MoneroWalletListener *m_listener;
@@ -188,6 +246,7 @@ extern "C"
246 Monero::SubaddressAccount *m_account;
247 uint64_t m_last_known_wallet_height;
248 uint64_t m_cached_syncing_blockchain_height = 0;
249 + std::list<Monero::CoinsInfo*> m_coins_info;
250 std::mutex store_lock;
251 bool is_storing = false;
252
@@ -195,7 +254,7 @@ extern "C"
254 {
255 m_wallet = wallet;
256 m_listener = nullptr;
198 -
257 +
258
259 if (wallet != nullptr)
260 {
@@ -223,6 +282,17 @@ extern "C"
282 {
283 m_subaddress = nullptr;
284 }
285 +
286 + m_coins_info = std::list<Monero::CoinsInfo*>();
287 +
288 + if (wallet != nullptr)
289 + {
290 + m_coins = wallet->coins();
291 + }
292 + else
293 + {
294 + m_coins = nullptr;
295 + }
296 }
297
298 Monero::Wallet *get_current_wallet()
@@ -405,13 +475,14 @@ extern "C"
475 return is_connected;
476 }
477
408 - bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error)
478 + bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *socksProxyAddress, char *error)
479 {
480 nice(19);
481 Monero::Wallet *wallet = get_current_wallet();
412 -
482 +
483 std::string _login = "";
484 std::string _password = "";
485 + std::string _socksProxyAddress = "";
486
487 if (login != nullptr)
488 {
@@ -423,7 +494,12 @@ extern "C"
494 _password = std::string(password);
495 }
496
426 - bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet);
497 + if (socksProxyAddress != nullptr)
498 + {
499 + _socksProxyAddress = std::string(socksProxyAddress);
500 + }
501 +
502 + bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet, _socksProxyAddress);
503
504 if (!inited)
505 {
@@ -480,10 +556,19 @@ extern "C"
556 }
557
558 bool transaction_create(char *address, char *payment_id, char *amount,
483 - uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction)
559 + uint8_t priority_raw, uint32_t subaddr_account,
560 + char **preferred_inputs, uint32_t preferred_inputs_size,
561 + Utf8Box &error, PendingTransactionRaw &pendingTransaction)
562 {
563 nice(19);
486 -
564 +
565 + std::set<std::string> _preferred_inputs;
566 +
567 + for (int i = 0; i < preferred_inputs_size; i++) {
568 + _preferred_inputs.insert(std::string(*preferred_inputs));
569 + preferred_inputs++;
570 + }
571 +
572 auto priority = static_cast<Monero::PendingTransaction::Priority>(priority_raw);
573 std::string _payment_id;
574 Monero::PendingTransaction *transaction;
@@ -496,13 +581,13 @@ extern "C"
581 if (amount != nullptr)
582 {
583 uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount));
499 - transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account);
584 + transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs);
585 }
586 else
587 {
503 - transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional<uint64_t>(), m_wallet->defaultMixin(), priority, subaddr_account);
588 + transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional<uint64_t>(), m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs);
589 }
505 -
590 +
591 int status = transaction->status();
592
593 if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical)
@@ -520,7 +605,9 @@ extern "C"
605 }
606
607 bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size,
523 - uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction)
608 + uint8_t priority_raw, uint32_t subaddr_account,
609 + char **preferred_inputs, uint32_t preferred_inputs_size,
610 + Utf8Box &error, PendingTransactionRaw &pendingTransaction)
611 {
612 nice(19);
613
@@ -534,6 +621,13 @@ extern "C"
621 amounts++;
622 }
623
624 + std::set<std::string> _preferred_inputs;
625 +
626 + for (int i = 0; i < preferred_inputs_size; i++) {
627 + _preferred_inputs.insert(std::string(*preferred_inputs));
628 + preferred_inputs++;
629 + }
630 +
631 auto priority = static_cast<Monero::PendingTransaction::Priority>(priority_raw);
632 std::string _payment_id;
633 Monero::PendingTransaction *transaction;
@@ -793,6 +887,91 @@ extern "C"
887 return m_wallet->trustedDaemon();
888 }
889
890 + CoinsInfoRow* coin(int index)
891 + {
892 + if (index >= 0 && index < m_coins_info.size()) {
893 + std::list<Monero::CoinsInfo*>::iterator it = m_coins_info.begin();
894 + std::advance(it, index);
895 + Monero::CoinsInfo* element = *it;
896 + std::cout << "Element at index " << index << ": " << element << std::endl;
897 + return new CoinsInfoRow(element);
898 + } else {
899 + std::cout << "Invalid index." << std::endl;
900 + return nullptr; // Return a default value (nullptr) for invalid index
901 + }
902 + }
903 +
904 + void refresh_coins(uint32_t accountIndex)
905 + {
906 + m_coins_info.clear();
907 +
908 + m_coins->refresh();
909 + for (const auto i : m_coins->getAll()) {
910 + if (i->subaddrAccount() == accountIndex && !(i->spent())) {
911 + m_coins_info.push_back(i);
912 + }
913 + }
914 + }
915 +
916 + uint64_t coins_count()
917 + {
918 + return m_coins_info.size();
919 + }
920 +
921 + CoinsInfoRow** coins_from_account(uint32_t accountIndex)
922 + {
923 + std::vector<CoinsInfoRow*> matchingCoins;
924 +
925 + for (int i = 0; i < coins_count(); i++) {
926 + CoinsInfoRow* coinInfo = coin(i);
927 + if (coinInfo->subaddrAccount == accountIndex) {
928 + matchingCoins.push_back(coinInfo);
929 + }
930 + }
931 +
932 + CoinsInfoRow** result = new CoinsInfoRow*[matchingCoins.size()];
933 + std::copy(matchingCoins.begin(), matchingCoins.end(), result);
934 + return result;
935 + }
936 +
937 + CoinsInfoRow** coins_from_txid(const char* txid, size_t* count)
938 + {
939 + std::vector<CoinsInfoRow*> matchingCoins;
940 +
941 + for (int i = 0; i < coins_count(); i++) {
942 + CoinsInfoRow* coinInfo = coin(i);
943 + if (std::string(coinInfo->hash) == txid) {
944 + matchingCoins.push_back(coinInfo);
945 + }
946 + }
947 +
948 + *count = matchingCoins.size();
949 + CoinsInfoRow** result = new CoinsInfoRow*[*count];
950 + std::copy(matchingCoins.begin(), matchingCoins.end(), result);
951 + return result;
952 + }
953 +
954 + CoinsInfoRow** coins_from_key_image(const char** keyimages, size_t keyimageCount, size_t* count)
955 + {
956 + std::vector<CoinsInfoRow*> matchingCoins;
957 +
958 + for (int i = 0; i < coins_count(); i++) {
959 + CoinsInfoRow* coinsInfoRow = coin(i);
960 + for (size_t j = 0; j < keyimageCount; j++) {
961 + if (coinsInfoRow->keyImageKnown && std::string(coinsInfoRow->keyImage) == keyimages[j]) {
962 + matchingCoins.push_back(coinsInfoRow);
963 + break;
964 + }
965 + }
966 + }
967 +
968 + *count = matchingCoins.size();
969 + CoinsInfoRow** result = new CoinsInfoRow*[*count];
970 + std::copy(matchingCoins.begin(), matchingCoins.end(), result);
971 + return result;
972 + }
973 +
974 +
975 #ifdef __cplusplus
976 }
977 #endif
lib/entities/default_settings_migration.dart
+69 -8
@@ -1,12 +1,11 @@
1 -import 'dart:io' show File, Platform;
1 +import 'dart:io' show Directory, File, Platform;
2 import 'package:cake_wallet/bitcoin/bitcoin.dart';
3 import 'package:cake_wallet/entities/exchange_api_mode.dart';
4 import 'package:cw_core/pathForWallet.dart';
5 import 'package:cake_wallet/entities/secret_store_key.dart';
6 -import 'package:flutter/foundation.dart';
6 import 'package:flutter_secure_storage/flutter_secure_storage.dart';
7 import 'package:hive/hive.dart';
9 -import 'package:share_plus/share_plus.dart';
8 +import 'package:path_provider/path_provider.dart';
9 import 'package:shared_preferences/shared_preferences.dart';
10 import 'package:cake_wallet/entities/preferences_key.dart';
11 import 'package:cw_core/wallet_type.dart';
@@ -28,7 +27,7 @@ const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
27 const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
28 const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
29
31 -Future defaultSettingsMigration(
30 +Future<void> defaultSettingsMigration(
31 {required int version,
32 required SharedPreferences sharedPreferences,
33 required FlutterSecureStorage secureStorage,
@@ -43,6 +42,8 @@ Future defaultSettingsMigration(
42 // check current nodes for nullability regardless of the version
43 await checkCurrentNodes(nodes, sharedPreferences);
44
45 + await _validateWalletInfoBoxData(walletInfoSource);
46 +
47 final isNewInstall = sharedPreferences
48 .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
49
@@ -179,6 +180,66 @@ Future defaultSettingsMigration(
180 PreferencesKey.currentDefaultSettingsMigrationVersion, version);
181 }
182
183 +Future<void> _validateWalletInfoBoxData(Box<WalletInfo> walletInfoSource) async {
184 + final root = await getApplicationDocumentsDirectory();
185 +
186 + for (var type in WalletType.values) {
187 + if (type == WalletType.none) {
188 + continue;
189 + }
190 +
191 + String prefix = walletTypeToString(type).toLowerCase();
192 + Directory walletsDir = Directory('${root.path}/wallets/$prefix/');
193 +
194 + if (!walletsDir.existsSync()) {
195 + continue;
196 + }
197 +
198 + List<String> walletNames = walletsDir.listSync().map((e) => e.path.split("/").last).toList();
199 +
200 + for (var name in walletNames) {
201 + final dir = Directory(await pathForWalletDir(name: name, type: type));
202 +
203 + final walletFiles = dir.listSync();
204 + final hasCacheFile = walletFiles.any((element) => element.path.contains("$name/$name"));
205 +
206 + if (!hasCacheFile) {
207 + continue;
208 + }
209 +
210 + if (type == WalletType.monero || type == WalletType.haven) {
211 + final hasKeysFile = walletFiles.any((element) => element.path.contains(".keys"));
212 +
213 + if (!hasKeysFile) {
214 + continue;
215 + }
216 + }
217 +
218 + final id = prefix + '_' + name;
219 + final exist = walletInfoSource.values.any((el) => el.id == id);
220 +
221 + if (exist) {
222 + continue;
223 + }
224 +
225 + final walletInfo = WalletInfo.external(
226 + id: id,
227 + type: type,
228 + name: name,
229 + isRecovery: true,
230 + restoreHeight: 0,
231 + date: DateTime.now(),
232 + dirPath: dir.path,
233 + path: '${dir.path}/$name',
234 + address: '',
235 + showIntroCakePayCard: false,
236 + );
237 +
238 + walletInfoSource.add(walletInfo);
239 + }
240 + }
241 +}
242 +
243 Future<void> validateBitcoinSavedTransactionPriority(SharedPreferences sharedPreferences) async {
244 if (bitcoin == null) {
245 return;
@@ -226,7 +287,7 @@ Future<void> changeMoneroCurrentNodeToDefault(
287 {required SharedPreferences sharedPreferences,
288 required Box<Node> nodes}) async {
289 final node = getMoneroDefaultNode(nodes: nodes);
229 - final nodeId = node?.key as int ?? 0; // 0 - England
290 + final nodeId = node.key as int? ?? 0; // 0 - England
291
292 await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, nodeId);
293 }
@@ -279,7 +340,7 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
340 {required SharedPreferences sharedPreferences,
341 required Box<Node> nodes}) async {
342 final server = getBitcoinDefaultElectrumServer(nodes: nodes);
282 - final serverId = server?.key as int ?? 0;
343 + final serverId = server?.key as int? ?? 0;
344
345 await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, serverId);
346 }
@@ -288,7 +349,7 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
349 {required SharedPreferences sharedPreferences,
350 required Box<Node> nodes}) async {
351 final server = getLitecoinDefaultElectrumServer(nodes: nodes);
291 - final serverId = server?.key as int ?? 0;
352 + final serverId = server?.key as int? ?? 0;
353
354 await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId);
355 }
@@ -297,7 +358,7 @@ Future<void> changeHavenCurrentNodeToDefault(
358 {required SharedPreferences sharedPreferences,
359 required Box<Node> nodes}) async {
360 final node = getHavenDefaultNode(nodes: nodes);
300 - final nodeId = node?.key as int ?? 0;
361 + final nodeId = node?.key as int? ?? 0;
362
363 await sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, nodeId);
364 }
macos/Podfile.lock
+4 -4
@@ -57,11 +57,11 @@ DEPENDENCIES:
57 - FlutterMacOS (from `Flutter/ephemeral`)
58 - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`)
59 - package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`)
60 - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`)
60 + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
61 - platform_device_id (from `Flutter/ephemeral/.symlinks/plugins/platform_device_id/macos`)
62 - platform_device_id_macos (from `Flutter/ephemeral/.symlinks/plugins/platform_device_id_macos/macos`)
63 - share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
64 - - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`)
64 + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
65 - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
66 - wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
67
@@ -87,7 +87,7 @@ EXTERNAL SOURCES:
87 package_info:
88 :path: Flutter/ephemeral/.symlinks/plugins/package_info/macos
89 path_provider_foundation:
90 - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos
90 + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
91 platform_device_id:
92 :path: Flutter/ephemeral/.symlinks/plugins/platform_device_id/macos
93 platform_device_id_macos:
@@ -95,7 +95,7 @@ EXTERNAL SOURCES:
95 share_plus_macos:
96 :path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
97 shared_preferences_foundation:
98 - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos
98 + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
99 url_launcher_macos:
100 :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
101 wakelock_macos: