cake_wallet/lib/utils/exception_handler.dart
Matthew Fosse 62e0c2a592
litecoin mweb support (#1455)
* Fix stub creation

* Generate MWEB addresses

* Fix mweb address derivation

* Use camel-case

* Show utxos in tx list

* A few fixes

* Add spent processing

* Update balance

* Balance fixes

* Update address records

* Get rid of debounce hack

* Get sending up to the confirmation box

* Fee estimation

* Stop the daemon if plugin is unloaded

* Normal fee for non-mweb txns

* Fix fee estimation for send all

* Don't hash mweb addresses

* More fee fixes

* Broadcast mweb

* Remove test files

* One more

* Confirm sent txns

* Couple of fixes

* Resign inputs after mweb create

* Some more fixes

* Update balance after sending

* Correctly update address records

* Update confs

* [skip ci] updates

* [skip ci] add dep overrides

* working

* small fix

* merge fixes [skip ci]

* merge fixes [skip ci]

* [skip ci] minor fixes

* silent payment fixes [skip ci]

* updates [skip ci]

* save [skip ci]

* use mwebutxos box

* [skip ci] lots of fixes, still testing

* add rescan from height feature and test workflow build

* install go

* use sudo

* correct package name

* move building mweb higher for faster testing

* install fixes

* install later version of go

* go fixes

* testing

* testing

* testing

* testing

* testing

* should workgit add .github/workflows/pr_test_build.yml

* ???

* ??? pt.2

* should work, for real this time

* fix tx history not persisting + update build_mwebd script

* updates

* fix some rescan and address gen issues

* save [skip ci]

* fix unconfirmed balance not updating when receiving

* unspent coins / coin control fixes

* coin control fixes

* address balance and txCount fixes, try/catch electrum call

* fix txCount for addresses

* save [skip ci]

* potential fixes

* minor fix

* minor fix - 2

* sync status fixes, potential fix for background state issue

* workflow and script updates

* updates

* expirimental optimization

* [skip ci] minor enhancements

* workflow and script fixes

* workflow minor cleanup [skip ci]

* minor code cleanup & friendlier error message on failed tx's

* balance when sending fix

* experimental

* more experiments

* save

* updates

* coin control edge cases

* remove neutrino.db if no litecoin wallets left after deleting

* update translations

* updates

* minor fix

* [skip ci] update translations + minor fixes

* state fixes

* configure fix

* ui updates

* translation fixes

* [skip ci] addressbook updates

* fix popup

* fix popup2

* fix litecoin address book

* fix ios mwebd build script

* fix for building monero.com

* minor fix

* uncomment fix for state issues

* potential mweb sync fix (ios)

* remove print [skip ci]

* electrum stream potential fix

* fix ios build issues [skip ci]

* connection reliability updates, update kotlin code to match swift code, minor electrum error handling

* dep fixes

* minor fix

* more merge fixes

* bitcoin_flutter removal fixes

* [skip ci] fix always scan setting, swift updates

* updates

* fixes

* small fix

* small fix

* fix

* dart:convert != package:convert

* change address fixes

* update bitcoin_base to fix mweb address program checking

* fix ios xcode project [skip ci]

* updates

* more fixes

* more fixes

* ensure we don't initialize mweb until we really have to

* fix regression

* improve mweb reliability

* [skip ci] wip adress generation

* wip

* wip

* [skip ci] wip

* updates [skip ci]

* ios fixes

* fix workflows + ios fix

* test old mweb version

* update go version and mwebd hash

* review updates pt.1

* Update cw_bitcoin/lib/litecoin_wallet.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* remove non-litecoin address types regex [skip ci]

* more minor fixes

* remove duplicate [skip ci]

* Update lib/store/settings_store.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* script updates, swap params on createLitecoinWalletService

* topup fix

* [skip ci] wip

* [skip ci] testing

* [skip ci] file didn't get saved

* more address generation reliability fixes

* [skip ci] minor

* minor code cleanup

* hopefully prevents send issue

* [skip ci] wip address changes

* [skip ci] save

* save mweb addresses, auto-restart sync process if it gets stuck [skip ci]

* address generation issues mostly resolved

* more performance fixes

* [skip ci]

* this should maybe be refactored, pt.1

* separate mweb balances, pt.2

* [skip ci] save

* add translations [skip ci]

* fix sending with mweb amounts

* works for simple mweb-mweb case, further testing needed

* found an edge case

* [skip ci] make failed broadcast error message less serious

* minor

* capture all grpc errors and much better error handling overall

* [skip ci] minor

* prevent transactions with < 6 confirmations from being used + hide mweb balances if mweb is off

* fix

* merge fixes pt.1 [skip ci]

* fix mweb tags

* fix

* [skip ci] fix tag spacing

* fix transaction history not showing up

* fix mweb crash on non-fully deleted mweb cache, sync status ETA, other connection fixes

* [skip ci] minor code cleanup

* [skip ci] minor code cleanup

* additional cleanup

* silent payments eta fixes and updates

* revert sync eta changes into separate pr

* [skip ci] minor

* [skip ci] minor

* revert sync status title

* review fixes, additional cleanup

* [skip ci] minor

* [skip ci] minor

* [skip ci] minor

* trigger build

* review fixes, pt.2

* check if still processing utxos before updating sync status [skip ci]

* [skip ci] minor

* balance fix

* minor

* minor

* [skip ci] minor

* [skip ci] fix test net btc

* don't use mwebd for non-mweb tx's

* [skip ci] minor cleanup

* don't show all 1000+ mweb addresses on receive page

* minor cleanup + additional logging

---------

Co-authored-by: Hector Chu <hectorchu@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
2024-09-28 05:22:25 +03:00

315 lines
9.5 KiB
Dart

import 'dart:io';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/root_dir.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mailer/flutter_mailer.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ExceptionHandler {
static bool _hasError = false;
static const _coolDownDurationInDays = 7;
static File? _file;
static void _saveException(String? error, StackTrace? stackTrace, {String? library}) async {
final appDocDir = await getAppDir();
if (_file == null) {
_file = File('${appDocDir.path}/error.txt');
}
final exception = {
"${DateTime.now()}": {
"Error": "$error\n\n",
"Library": "$library\n\n",
"StackTrace": stackTrace.toString(),
}
};
const String separator = '''\n\n==========================================================
==========================================================\n\n''';
/// don't save existing errors
if (_file!.existsSync()) {
final String fileContent = await _file!.readAsString();
if (fileContent.contains("${exception.values.first}")) {
return;
}
}
_file!.writeAsStringSync(
"$exception $separator",
mode: FileMode.append,
);
}
static void _sendExceptionFile() async {
try {
if (_file == null) {
final appDocDir = await getAppDir();
_file = File('${appDocDir.path}/error.txt');
}
await _addDeviceInfo(_file!);
final MailOptions mailOptions = MailOptions(
subject: 'Mobile App Issue',
recipients: ['support@cakewallet.com'],
attachments: [_file!.path],
);
final result = await FlutterMailer.send(mailOptions);
// Clear file content if the error was sent or saved.
// On android we can't know if it was sent or saved
if (result.name == MailerResponse.sent.name ||
result.name == MailerResponse.saved.name ||
result.name == MailerResponse.android.name) {
_file!.writeAsString("", mode: FileMode.write);
}
} catch (e, s) {
_saveException(e.toString(), s);
}
}
static void onError(FlutterErrorDetails errorDetails) async {
if (kDebugMode || kProfileMode) {
FlutterError.presentError(errorDetails);
debugPrint(errorDetails.toString());
return;
}
if (_ignoreError(errorDetails.exception.toString())) {
return;
}
_saveException(
errorDetails.exceptionAsString(),
errorDetails.stack,
library: errorDetails.library,
);
if (errorDetails.silent) {
return;
}
final sharedPrefs = await SharedPreferences.getInstance();
final lastPopupDate =
DateTime.tryParse(sharedPrefs.getString(PreferencesKey.lastPopupDate) ?? '') ??
DateTime.now().subtract(Duration(days: _coolDownDurationInDays + 1));
final durationSinceLastReport = DateTime.now().difference(lastPopupDate).inDays;
if (_hasError || durationSinceLastReport < _coolDownDurationInDays) {
return;
}
_hasError = true;
sharedPrefs.setString(PreferencesKey.lastPopupDate, DateTime.now().toString());
WidgetsBinding.instance.addPostFrameCallback(
(timeStamp) async {
if (navigatorKey.currentContext != null) {
await showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (context) {
return AlertWithTwoActions(
isDividerExist: true,
alertTitle: S.of(context).error,
alertContent: S.of(context).error_dialog_content,
rightButtonText: S.of(context).send,
leftButtonText: S.of(context).do_not_send,
actionRightButton: () {
Navigator.of(context).pop();
_sendExceptionFile();
},
actionLeftButton: () {
Navigator.of(context).pop();
},
);
},
);
}
_hasError = false;
},
);
}
/// Ignore User related errors or system errors
static bool _ignoreError(String error) =>
_ignoredErrors.any((element) => error.contains(element));
static const List<String> _ignoredErrors = const [
"Bad file descriptor",
"No space left on device",
"OS Error: Broken pipe",
"Can't assign requested address",
"OS Error: Socket is not connected",
"Operation timed out",
"No route to host",
"Software caused connection abort",
"Connection reset by peer",
"Connection timed out",
"Connection reset by peer",
"Connection closed before full header was received",
"Connection terminated during handshake",
"PERMISSION_NOT_GRANTED",
"Failed host lookup:",
"CERTIFICATE_VERIFY_FAILED",
"Handshake error in client",
"Error while launching http",
"OS Error: Network is unreachable",
"ClientException: Write failed, uri=http",
"Corrupted wallets seeds",
"bad_alloc",
"does not correspond",
"basic_string",
"input_stream",
"input stream error",
"invalid signature",
"invalid password",
];
static Future<void> _addDeviceInfo(File file) async {
final packageInfo = await PackageInfo.fromPlatform();
final currentVersion = packageInfo.version;
final deviceInfoPlugin = DeviceInfoPlugin();
Map<String, dynamic> deviceInfo = {};
if (Platform.isAndroid) {
deviceInfo = _readAndroidBuildData(await deviceInfoPlugin.androidInfo);
deviceInfo["Platform"] = "Android";
} else if (Platform.isIOS) {
deviceInfo = _readIosDeviceInfo(await deviceInfoPlugin.iosInfo);
deviceInfo["Platform"] = "iOS";
} else if (Platform.isLinux) {
deviceInfo = _readLinuxDeviceInfo(await deviceInfoPlugin.linuxInfo);
deviceInfo["Platform"] = "Linux";
} else if (Platform.isMacOS) {
deviceInfo = _readMacOsDeviceInfo(await deviceInfoPlugin.macOsInfo);
deviceInfo["Platform"] = "MacOS";
} else if (Platform.isWindows) {
deviceInfo = _readWindowsDeviceInfo(await deviceInfoPlugin.windowsInfo);
deviceInfo["Platform"] = "Windows";
}
await file.writeAsString(
"App Version: $currentVersion\n\nDevice Info $deviceInfo\n\n",
mode: FileMode.append,
);
}
static Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
return <String, dynamic>{
'brand': build.brand,
'device': build.device,
'manufacturer': build.manufacturer,
'model': build.model,
'product': build.product,
};
}
static Map<String, dynamic> _readIosDeviceInfo(IosDeviceInfo data) {
return <String, dynamic>{
'systemName': data.systemName,
'systemVersion': data.systemVersion,
'model': data.model,
'localizedModel': data.localizedModel,
'isPhysicalDevice': data.isPhysicalDevice,
};
}
static Map<String, dynamic> _readLinuxDeviceInfo(LinuxDeviceInfo data) {
return <String, dynamic>{
'name': data.name,
'version': data.version,
'versionCodename': data.versionCodename,
'versionId': data.versionId,
'prettyName': data.prettyName,
'buildId': data.buildId,
'variant': data.variant,
'variantId': data.variantId,
};
}
static Map<String, dynamic> _readMacOsDeviceInfo(MacOsDeviceInfo data) {
return <String, dynamic>{
'arch': data.arch,
'model': data.model,
'kernelVersion': data.kernelVersion,
'osRelease': data.osRelease,
};
}
static Map<String, dynamic> _readWindowsDeviceInfo(WindowsDeviceInfo data) {
return <String, dynamic>{
'majorVersion': data.majorVersion,
'minorVersion': data.minorVersion,
'buildNumber': data.buildNumber,
'productType': data.productType,
'productName': data.productName,
};
}
static void showError(String error, {int? delayInSeconds}) async {
if (_hasError) {
return;
}
_hasError = true;
if (delayInSeconds != null) {
Future.delayed(Duration(seconds: delayInSeconds), () => _showCopyPopup(error));
return;
}
WidgetsBinding.instance.addPostFrameCallback(
(_) async => _showCopyPopup(error),
);
}
static Future<void> _showCopyPopup(String content) async {
if (navigatorKey.currentContext != null) {
final shouldCopy = await showPopUp<bool?>(
context: navigatorKey.currentContext!,
builder: (context) {
return AlertWithTwoActions(
isDividerExist: true,
alertTitle: S.of(context).error,
alertContent: content,
rightButtonText: S.of(context).copy,
leftButtonText: S.of(context).close,
actionRightButton: () {
Navigator.of(context).pop(true);
},
actionLeftButton: () {
Navigator.of(context).pop();
},
);
},
);
if (shouldCopy == true) {
await Clipboard.setData(ClipboardData(text: content));
await showBar<void>(
navigatorKey.currentContext!,
S.of(navigatorKey.currentContext!).copied_to_clipboard,
);
}
}
_hasError = false;
}
}