Skip to content

fix(generic-fixes): Fixes to a couple of raised issues. #2240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cw_core/lib/utils/print_verbose.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';

String? printVLogFilePath;

void printV(dynamic content) {
CustomTrace programInfo = CustomTrace(StackTrace.current);
print("${programInfo.fileName}#${programInfo.lineNumber}:${programInfo.columnNumber} ${programInfo.callerFunctionName}: $content");
final logMsg = "${programInfo.fileName}#${programInfo.lineNumber}:${programInfo.columnNumber} ${programInfo.callerFunctionName}: $content";
if (printVLogFilePath != null) {
try {
File(printVLogFilePath!).writeAsStringSync("$logMsg\n", mode: FileMode.append);
} catch (e) {
print("Unable to write to log file (printV): $e");
}
}
print(logMsg);
}

// https://stackoverflow.com/a/59386101
Expand Down
44 changes: 41 additions & 3 deletions cw_core/lib/wallet_info.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import 'dart:async';
import 'dart:io';

import 'package:cw_core/address_info.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:cw_core/root_dir.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:path/path.dart' as p;

part 'wallet_info.g.dart';

Expand Down Expand Up @@ -81,7 +86,7 @@ class WalletInfo extends HiveObject {
this.derivationInfo,
this.hardwareWalletType,
this.parentAddress,
this.hashedWalletIdentifier,
this._hashedWalletIdentifier,
this.isNonSeedWallet,
) : _yatLastUsedAddressController = StreamController<String>.broadcast();

Expand Down Expand Up @@ -195,15 +200,48 @@ class WalletInfo extends HiveObject {

@HiveField(22)
String? parentAddress;

@HiveField(23)
List<String>? hiddenAddresses;

@HiveField(24)
List<String>? manualAddresses;

@HiveField(25)
String? hashedWalletIdentifier;
String? _hashedWalletIdentifier;

String? get hashedWalletIdentifier => _hashedWalletIdentifier;

set hashedWalletIdentifier(String? value) {
final oldValue = _hashedWalletIdentifier;
_logHashedIdentifierChange(oldValue, value, StackTrace.current);
_hashedWalletIdentifier = value;
}

Future<void> _logHashedIdentifierChange(
String? oldValue,
String? newValue,
StackTrace trace,
) async {
try {
final customTrace = CustomTrace(trace);
final timestamp = DateTime.now().toIso8601String();
final location =
'${customTrace.fileName}#${customTrace.lineNumber}:${customTrace.columnNumber}';
final caller = customTrace.callerFunctionName;
final logLine =
'[$timestamp] $location ($caller) WalletType: (${type.name}) Name: ($name) ParentAddress: ($parentAddress) '
'isNonSeedWallet: ($isNonSeedWallet) isRecovery: ($isRecovery) Address: ($address) \n'
'hashedWalletIdentifier: "$oldValue" → "$newValue"\n\n ${trace.toString()}\n\n\n';

final dir = await getAppDir();
final file = File(p.join(dir.path, 'hashed_identifier_changes.log'));

await file.writeAsString(logLine, mode: FileMode.append, flush: true);
} catch (e) {
if (kDebugMode) print('Failed to log hash change: $e');
}
}

@HiveField(26, defaultValue: false)
bool isNonSeedWallet;
Expand Down
1 change: 1 addition & 0 deletions lib/core/wallet_loading_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class WalletLoadingService {

// try opening another wallet that is not corrupted to give user access to the app
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
printV("WalletInfoSource length (wallet loading service): ${walletInfoSource.length}");
WalletBase? wallet;
for (var walletInfo in walletInfoSource.values) {
try {
Expand Down
2 changes: 1 addition & 1 deletion lib/entities/hash_wallet_identifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:hashlib/hashlib.dart';

String createHashedWalletIdentifier(WalletBase wallet) {
if (wallet.seed == null) return '';
if (wallet.seed == null || wallet.seed!.isEmpty) return '';

final salt = secrets.walletGroupSalt;
final combined = '$salt.${wallet.seed}';
Expand Down
13 changes: 3 additions & 10 deletions lib/entities/load_current_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,15 @@ import 'package:cake_wallet/core/wallet_loading_service.dart';

Future<void> loadCurrentWallet({String? password}) async {
final appStore = getIt.get<AppStore>();
final name = getIt
.get<SharedPreferences>()
.getString(PreferencesKey.currentWalletName);
final typeRaw =
getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType) ??
0;
final name = getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName);
final typeRaw = getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType) ?? 0;

if (name == null) {
throw Exception('Incorrect current wallet name: $name');
}

final type = deserializeFromInt(typeRaw);
final walletLoadingService = getIt.get<WalletLoadingService>();
final wallet = await walletLoadingService.load(
type,
name,
password: password);
final wallet = await walletLoadingService.load(type, name, password: password);
await appStore.changeCurrentWallet(wallet);
}
67 changes: 23 additions & 44 deletions lib/entities/wallet_manager.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:math';

import 'package:cake_wallet/entities/hash_wallet_identifier.dart';
import 'package:cake_wallet/entities/wallet_group.dart';
import 'package:cw_core/wallet_base.dart';
Expand Down Expand Up @@ -34,12 +32,8 @@ class WalletManager {
return walletInfo.hashedWalletIdentifier!;
}

// Fallback to old logic
final address = walletInfo.parentAddress ?? walletInfo.address;
if (address.isEmpty) {
return Random().nextInt(100000).toString();
}
return address;
return address.isNotEmpty ? address : walletInfo.id;
}

WalletGroup _getOrCreateGroup(String groupKey) {
Expand Down Expand Up @@ -100,56 +94,41 @@ class WalletManager {
_saveCustomGroupName(groupKey, name);
}

// ---------------------------------------------------------------------------
// This performs a Group-Based Lazy Migration:
// If the user opens a wallet in an old group,
// we migrate ALL wallets that share its old group key to a new hash.
// ---------------------------------------------------------------------------

/// When a user opens a wallet, check if it has a real hash.
/// When user opens wallet, check if it has a real hash.
///
/// If not, migrate the ENTIRE old group so they keep the same group name
/// and end up with the same new hash (preserving grouping).
Future<void> ensureGroupHasHashedIdentifier(WalletBase openedWallet) async {
WalletInfo walletInfo = openedWallet.walletInfo;
final info = openedWallet.walletInfo;

// If the openedWallet already has an hash, then there is nothing to do
if (walletInfo.hashedWalletIdentifier != null &&
walletInfo.hashedWalletIdentifier!.isNotEmpty) {
updateWalletGroups(); // Still skeptical of calling this here. Looking for a better spot.
if (info.hashedWalletIdentifier?.isNotEmpty ?? false) {
updateWalletGroups();
return;
}

// Identify the old group key for this wallet
final oldGroupKey = _resolveGroupKey(walletInfo); // parentAddress fallback
final oldGroupKey = info.parentAddress?.isNotEmpty == true ? info.parentAddress! : null;
final walletsToMigrate = oldGroupKey != null
? _walletInfoSource.values.where((w) => (w.parentAddress ?? w.address) == oldGroupKey).toList()
: [info];

// Find all wallets that share this old group key (i.e the old group)
final oldGroupWallets = _walletInfoSource.values.where((w) {
final key = w.hashedWalletIdentifier != null && w.hashedWalletIdentifier!.isNotEmpty
? w.hashedWalletIdentifier
: (w.parentAddress ?? w.address);
return key == oldGroupKey;
}).toList();
if (oldGroupKey != null && walletsToMigrate.isEmpty) return;

if (oldGroupWallets.isEmpty) {
// This shouldn't happen, but just in case it does, we return.
return;
}
final newHash = createHashedWalletIdentifier(openedWallet);

// Next, we determine the new group hash for these wallets
// Since they share the same seed, we can assign that group hash
// to all the wallets to preserve grouping.
final newGroupHash = createHashedWalletIdentifier(openedWallet);

// Migrate the old group name from oldGroupKey(i.e parentAddress) to newGroupHash
await _migrateGroupName(oldGroupKey, newGroupHash);
if (oldGroupKey != null) {
await _migrateGroupName(oldGroupKey, newHash);
}

// Then we assign this new hash to each wallet in that old group and save them
for (final wallet in oldGroupWallets) {
wallet.hashedWalletIdentifier = newGroupHash;
await wallet.save();
// This throttle is here so we don't overwhelm the app when we have a lot of wallets we want to migrate.
const maxConcurrent = 3;
for (var i = 0; i < walletsToMigrate.length; i += maxConcurrent) {
final batch = walletsToMigrate.skip(i).take(maxConcurrent);
await Future.wait(batch.map((w) {
w.hashedWalletIdentifier = newHash;
return w.save();
}));
}

// Finally, we rebuild the groups so that these wallets are now in the new group
updateWalletGroups();
}

Expand Down
14 changes: 14 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/address_info.dart';
Expand Down Expand Up @@ -72,6 +73,12 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {

return true;
};
final date = DateTime.now().toIso8601String().replaceAll(':', '-');
final dir = '${(await getAppDir()).path}/print_v';
if (!Directory(dir).existsSync()) {
Directory(dir).createSync(recursive: true);
}
printVLogFilePath = FeatureFlag.hasDevOptions ? '$dir/$date.log' : null;
await FlutterDaemon().unmarkBackgroundSync();
await initializeAppAtRoot();

Expand Down Expand Up @@ -193,6 +200,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
final trades = await CakeHive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final orders = await CakeHive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
printV("WalletInfoSource length (initializeAppConfigs): ${walletInfoSource.length}");
final templates = await CakeHive.openBox<Template>(Template.boxName);
final exchangeTemplates = await CakeHive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
final anonpayInvoiceInfo = await CakeHive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
Expand Down Expand Up @@ -407,6 +415,12 @@ Future<void> backgroundSync() async {
WidgetsFlutterBinding.ensureInitialized();
printV("- DartPluginRegistrant.ensureInitialized()");
DartPluginRegistrant.ensureInitialized();
final date = DateTime.now().toIso8601String().replaceAll(':', '-');
final dir = '${(await getAppDir()).path}/print_v';
if (!Directory(dir).existsSync()) {
Directory(dir).createSync(recursive: true);
}
printVLogFilePath = FeatureFlag.hasDevOptions ? '$dir/$date.log' : null;
printV("- FlutterDaemon.markBackgroundSync()");
final val = await FlutterDaemon().markBackgroundSync();
if (val) {
Expand Down
8 changes: 8 additions & 0 deletions lib/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
import 'package:cake_wallet/src/screens/dev/hash_change_logs_page.dart';
import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
import 'package:cake_wallet/src/screens/dev/print_verbose_logs_page.dart';
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
Expand Down Expand Up @@ -858,6 +860,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
return MaterialPageRoute<void>(
builder: (_) => getIt.get<DevSecurePreferencesPage>(),
);

case Routes.devHashChangeLogs:
return MaterialPageRoute<void>(builder: (_) => HashChangeLogsPage());

case Routes.devPrintVerbose:
return MaterialPageRoute<void>(builder: (_) => PrintVerboseLogsPage());

default:
return MaterialPageRoute<void>(
Expand Down
4 changes: 3 additions & 1 deletion lib/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ class Routes {
static const devSharedPreferences = '/dev/shared_preferences';
static const devSecurePreferences = '/dev/secure_preferences';
static const devBackgroundSyncLogs = '/dev/background_sync_logs';

static const devHashChangeLogs = '/dev/hash_change_logs';
static const devPrintVerbose = '/dev/print_verbose';

static const signPage = '/sign_page';
static const connectDevices = '/device/connect';
static const urqrAnimatedPage = '/urqr/animated_page';
Expand Down
65 changes: 65 additions & 0 deletions lib/src/screens/dev/hash_change_logs_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'dart:io';

import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;

class HashChangeLogsPage extends BasePage {
@override
String? get title => "[dev] hash change logs";

@override
Widget? trailing(BuildContext context) {
return IconButton(
icon: Icon(Icons.download, size: 20),
onPressed: () => _shareLog(context),
);
}

Future<void> _shareLog(BuildContext context) async {
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/hashed_identifier_changes.log');
if (await file.exists()) {
await ShareUtil.shareFile(
filePath: file.path,
fileName: 'Hash-change log',
context: context,
);
}
}

Future<String?> _loadHashLog() async {
final dir = await getApplicationDocumentsDirectory();
final file = File(p.join(dir.path, 'hashed_identifier_changes.log'));
if (!await file.exists()) return null;
return await file.readAsString();
}

@override
Widget body(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hash Change Log')),
body: FutureBuilder<String?>(
future: _loadHashLog(),
builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) {
return Center(child: CircularProgressIndicator());
}
final text = snap.data;
if (text == null || text.isEmpty) {
return Center(child: Text('No log records found.'));
}
return SingleChildScrollView(
padding: EdgeInsets.all(16),
child: SelectableText(
text,
style: TextStyle(fontFamily: 'monospace', fontSize: 14),
),
);
},
),
);
}
}
Loading
Loading