Cw 688 avoid wallet file corruption (#1582)

* CW-688 Store Seed and keys in .keys file

* CW-688 Open wallet from keys in .keys file and migrate wallets using the old file

* CW-688 Open wallet from keys in .keys file and migrate wallets using the old file

* CW-688 Restore .keys file from .keys.backup

* CW-688 Restore .keys file from .keys.backup

* CW-688 Move saving .keys files into the save function instead of the service

* CW-688 Handle corrupt wallets

* CW-688 Handle corrupt wallets

* CW-688 Remove code duplication

* CW-688 Reduce cache dependency

* wrap any file reading/writing function with try/catch [skip ci]

---------

Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
This commit is contained in:
Omar Hatem 2024-08-09 23:15:30 +03:00 committed by GitHub
parent 8e4082d680
commit fb33a6f23d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 433 additions and 144 deletions

View file

@ -1,8 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/n2_node.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
@ -10,23 +14,20 @@ import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_keys_file.dart';
import 'package:cw_nano/file.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/n2_node.dart';
import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_client.dart';
import 'package:cw_nano/nano_transaction_credentials.dart';
import 'package:cw_nano/nano_transaction_history.dart';
import 'package:cw_nano/nano_transaction_info.dart';
import 'package:cw_nano/nano_wallet_addresses.dart';
import 'package:cw_nano/nano_wallet_keys.dart';
import 'package:cw_nano/pending_nano_transaction.dart';
import 'package:mobx/mobx.dart';
import 'dart:async';
import 'package:cw_nano/nano_wallet_addresses.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:nanodart/nanodart.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:nanoutil/nanoutil.dart';
part 'nano_wallet.g.dart';
@ -34,7 +35,8 @@ part 'nano_wallet.g.dart';
class NanoWallet = NanoWalletBase with _$NanoWallet;
abstract class NanoWalletBase
extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> with Store {
extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo>
with Store, WalletKeysFile {
NanoWalletBase({
required WalletInfo walletInfo,
required String mnemonic,
@ -70,6 +72,7 @@ abstract class NanoWalletBase
String? _representativeAddress;
int repScore = 100;
bool get isRepOk => repScore >= 90;
late final NanoClient _client;
@ -128,14 +131,10 @@ abstract class NanoWalletBase
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
return 0; // always 0 :)
}
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0; // always 0 :)
@override
Future<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
@override
void close() {
@ -170,9 +169,7 @@ abstract class NanoWalletBase
}
@override
Future<void> connectToPowNode({required Node node}) async {
_client.connectPow(node);
}
Future<void> connectToPowNode({required Node node}) async => _client.connectPow(node);
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
@ -296,9 +293,7 @@ abstract class NanoWalletBase
}
@override
NanoWalletKeys get keys {
return NanoWalletKeys(seedKey: _hexSeed!);
}
NanoWalletKeys get keys => NanoWalletKeys(seedKey: _hexSeed!);
@override
String? get privateKey => _privateKey!;
@ -312,6 +307,11 @@ abstract class NanoWalletBase
@override
Future<void> save() async {
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
await saveKeysFile(_password);
saveKeysFile(_password, true);
}
await walletAddresses.updateAddressesInBox();
final path = await makePath();
await write(path: path, password: _password, data: toJSON());
@ -323,6 +323,9 @@ abstract class NanoWalletBase
String get hexSeed => _hexSeed!;
@override
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, altMnemonic: hexSeed);
String get representative => _representativeAddress ?? "";
@action
@ -358,8 +361,6 @@ abstract class NanoWalletBase
}
}
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'seedKey': _hexSeed,
'mnemonic': _mnemonic,
@ -373,31 +374,47 @@ abstract class NanoWalletBase
required String password,
required WalletInfo walletInfo,
}) async {
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
final path = await pathForWallet(name: name, type: walletInfo.type);
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
Map<String, dynamic>? data = null;
try {
final jsonSource = await read(path: path, password: password);
data = json.decode(jsonSource) as Map<String, dynamic>;
} catch (e) {
if (!hasKeysFile) rethrow;
}
final balance = NanoBalance.fromRawString(
currentBalance: data['currentBalance'] as String? ?? "0",
receivableBalance: data['receivableBalance'] as String? ?? "0",
currentBalance: data?['currentBalance'] as String? ?? "0",
receivableBalance: data?['receivableBalance'] as String? ?? "0",
);
final WalletKeysData keysData;
// Migrate wallet from the old scheme to then new .keys file scheme
if (!hasKeysFile) {
final mnemonic = data!['mnemonic'] as String;
final isHexSeed = !mnemonic.contains(' ');
keysData = WalletKeysData(
mnemonic: isHexSeed ? null : mnemonic, altMnemonic: isHexSeed ? mnemonic : null);
} else {
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
}
DerivationType derivationType = DerivationType.nano;
if (data['derivationType'] == "DerivationType.bip39") {
if (data?['derivationType'] == "DerivationType.bip39") {
derivationType = DerivationType.bip39;
}
walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType);
if (walletInfo.derivationInfo!.derivationType == null) {
walletInfo.derivationInfo!.derivationType = derivationType;
}
walletInfo.derivationInfo!.derivationType ??= derivationType;
return NanoWallet(
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
mnemonic: keysData.mnemonic!,
initialBalance: balance,
);
// init() should always be run after this!
@ -435,7 +452,7 @@ abstract class NanoWalletBase
_representativeAddress = await _client.getRepFromPrefs();
throw Exception("Failed to get representative address $e");
}
repScore = await _client.getRepScore(_representativeAddress!);
}

View file

@ -39,7 +39,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
mnemonic: mnemonic,
password: credentials.password!,
);
wallet.init();
await wallet.init();
return wallet;
}