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: