diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 5b43e0e91f..f5ad0a99a1 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 5b43e0e91f3d04bddfe88bba1d2f6178a18aadf9 +Subproject commit f5ad0a99a1781f600742095fee0e47057eafd9c0 diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index ab2b8bcde1..8b56d4e0b2 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -170,12 +170,11 @@ class DbVersionMigrator with WalletDB { final count = await MainDB.instance.isar.addresses.count(); // add change/receiving tags to address labels for (var i = 0; i < count; i += 50) { - final addresses = - await MainDB.instance.isar.addresses - .where() - .offset(i) - .limit(50) - .findAll(); + final addresses = await MainDB.instance.isar.addresses + .where() + .offset(i) + .limit(50) + .findAll(); final List labels = []; for (final address in addresses) { @@ -203,14 +202,13 @@ class DbVersionMigrator with WalletDB { // update/create label if tags is not empty if (tags != null) { - isar_models.AddressLabel? label = - await MainDB.instance.isar.addressLabels - .where() - .addressStringWalletIdEqualTo( - address.value, - address.walletId, - ) - .findFirst(); + isar_models.AddressLabel? label = await MainDB + .instance + .isar + .addressLabels + .where() + .addressStringWalletIdEqualTo(address.value, address.walletId) + .findFirst(); if (label == null) { label = isar_models.AddressLabel( walletId: address.walletId, @@ -268,13 +266,12 @@ class DbVersionMigrator with WalletDB { Bitcoincash(CryptoCurrencyNetwork.main).identifier || info.coinIdentifier == Bitcoincash(CryptoCurrencyNetwork.test).identifier) { - final ids = - await MainDB.instance - .getAddresses(walletId) - .filter() - .typeEqualTo(isar_models.AddressType.p2sh) - .idProperty() - .findAll(); + final ids = await MainDB.instance + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2sh) + .idProperty() + .findAll(); await MainDB.instance.isar.writeTxn(() async { await MainDB.instance.isar.addresses.deleteAll(ids); @@ -376,6 +373,20 @@ class DbVersionMigrator with WalletDB { // try to continue migrating return await migrate(15, secureStore: secureStore); + case 15: + // No-op: nodeApiSecret field added to NodeModel (Hive field 15). + // Existing nodes read null; updateDefaults() backfills from defaultNode. + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, + key: "hive_data_version", + value: 16, + ); + + // try to continue migrating + return await migrate(16, secureStore: secureStore); + default: // finally return return; @@ -421,17 +432,15 @@ class DbVersionMigrator with WalletDB { walletId: walletId, txid: tx.txid, timestamp: tx.timestamp, - type: - isIncoming - ? isar_models.TransactionType.incoming - : isar_models.TransactionType.outgoing, + type: isIncoming + ? isar_models.TransactionType.incoming + : isar_models.TransactionType.outgoing, subType: isar_models.TransactionSubType.none, amount: tx.amount, - amountString: - Amount( - rawValue: BigInt.from(tx.amount), - fractionDigits: epic.fractionDigits, - ).toJsonString(), + amountString: Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: epic.fractionDigits, + ).toJsonString(), fee: tx.fees, height: tx.height, isCancelled: tx.isCancelled, @@ -453,14 +462,12 @@ class DbVersionMigrator with WalletDB { publicKey: [], derivationIndex: isIncoming ? rcvIndex : -1, derivationPath: null, - type: - isIncoming - ? isar_models.AddressType.mimbleWimble - : isar_models.AddressType.unknown, - subType: - isIncoming - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.unknown, + type: isIncoming + ? isar_models.AddressType.mimbleWimble + : isar_models.AddressType.unknown, + subType: isIncoming + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.unknown, ); transactionsData.add(Tuple2(iTx, address)); } @@ -518,28 +525,25 @@ class DbVersionMigrator with WalletDB { final crypto = AppConfig.getCryptoCurrencyFor(info.coinIdentifier)!; for (var i = 0; i < count; i += 50) { - final txns = - await MainDB.instance - .getTransactions(walletId) - .offset(i) - .limit(50) - .findAll(); + final txns = await MainDB.instance + .getTransactions(walletId) + .offset(i) + .limit(50) + .findAll(); // migrate amount to serialized amount string - final txnsData = - txns - .map( - (tx) => Tuple2( - tx - ..amountString = - Amount( - rawValue: BigInt.from(tx.amount), - fractionDigits: crypto.fractionDigits, - ).toJsonString(), - tx.address.value, - ), - ) - .toList(); + final txnsData = txns + .map( + (tx) => Tuple2( + tx + ..amountString = Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: crypto.fractionDigits, + ).toJsonString(), + tx.address.value, + ), + ) + .toList(); // update db records await MainDB.instance.addNewTransactionData(txnsData, walletId); diff --git a/lib/models/node_model.dart b/lib/models/node_model.dart index 5d43d84dba..5386cae0f9 100644 --- a/lib/models/node_model.dart +++ b/lib/models/node_model.dart @@ -47,6 +47,8 @@ class NodeModel { final bool forceNoTor; // @HiveField(14) final bool isPrimary; + // @HiveField(15) + final String? nodeApiSecret; NodeModel({ required this.host, @@ -64,6 +66,7 @@ class NodeModel { this.forceNoTor = false, this.loginName, this.trusted, + this.nodeApiSecret, }); NodeModel copyWith({ @@ -81,6 +84,7 @@ class NodeModel { bool? forceNoTor, bool? clearnetEnabled, bool? isPrimary, + String? nodeApiSecret, }) { return NodeModel( host: host ?? this.host, @@ -98,6 +102,7 @@ class NodeModel { clearnetEnabled: clearnetEnabled ?? this.clearnetEnabled, forceNoTor: forceNoTor ?? this.forceNoTor, isPrimary: isPrimary ?? this.isPrimary, + nodeApiSecret: nodeApiSecret ?? this.nodeApiSecret, ); } @@ -123,6 +128,7 @@ class NodeModel { map['clearEnabled'] = clearnetEnabled; map['forceNoTor'] = forceNoTor; map['isPrimary'] = isPrimary; + map['nodeApiSecret'] = nodeApiSecret; return map; } diff --git a/lib/models/type_adaptors/node_model.g.dart b/lib/models/type_adaptors/node_model.g.dart index 218731a69c..1ab826d553 100644 --- a/lib/models/type_adaptors/node_model.g.dart +++ b/lib/models/type_adaptors/node_model.g.dart @@ -32,13 +32,14 @@ class NodeModelAdapter extends TypeAdapter { clearnetEnabled: fields[12] as bool? ?? true, forceNoTor: fields[13] as bool? ?? false, isPrimary: fields[14] as bool? ?? false, + nodeApiSecret: fields[15] as String?, ); } @override void write(BinaryWriter writer, NodeModel obj) { writer - ..writeByte(15) + ..writeByte(16) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -68,7 +69,9 @@ class NodeModelAdapter extends TypeAdapter { ..writeByte(13) ..write(obj.forceNoTor) ..writeByte(14) - ..write(obj.isPrimary); + ..write(obj.isPrimary) + ..writeByte(15) + ..write(obj.nodeApiSecret); } @override diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 75411e9cbc..b05815e0fa 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -751,7 +751,7 @@ class _AddEditNodeViewState extends ConsumerState { } class NodeFormData { - String? name, host, login, password; + String? name, host, login, password, apiSecret; int? port; bool? useSSL, isFailover, trusted, forceNoTor, isPrimary; TorPlainNetworkOption? netOption; diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 69205f1f0c..f7b495ee06 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -40,7 +40,7 @@ abstract class Constants { // Enable Logger.print statements static const bool disableLogger = false; - static const int currentDataVersion = 15; + static const int currentDataVersion = 16; static const int rescanV1 = 1; diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart index c199afcbf3..48860df3c1 100644 --- a/lib/utilities/test_mwcmqs_connection.dart +++ b/lib/utilities/test_mwcmqs_connection.dart @@ -17,15 +17,13 @@ import '../services/tor_service.dart'; import 'logger.dart'; import 'prefs.dart'; -Future _testMwcMqsNodeConnection(Uri uri) async { +Future _testMwcMqsNodeConnection(Uri uri, {String? apiSecret}) async { final HTTP client = HTTP(); try { final headers = {'Content-Type': 'application/json'}; - if (uri.toString() == 'https://mwc713.mwc.mw/v1/version') { - const username = 'mwcmain'; - const password = '11ne3EAUtOXVKwhxm84U'; - final credentials = base64Encode(utf8.encode('$username:$password')); + if (apiSecret != null) { + final credentials = base64Encode(utf8.encode('mwcmain:$apiSecret')); headers['Authorization'] = 'Basic $credentials'; } final response = await client @@ -80,7 +78,7 @@ Future testMwcNodeConnection(NodeFormData data) async { uri = uri.replace(port: data.port); try { - if (await _testMwcMqsNodeConnection(uri)) { + if (await _testMwcMqsNodeConnection(uri, apiSecret: data.apiSecret)) { return data; } else { return null; diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index c9d57878a0..2ed274b35c 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -101,6 +101,7 @@ class Mimblewimblecoin extends Bip39Currency { torEnabled: true, clearnetEnabled: true, isPrimary: true, + nodeApiSecret: '11ne3EAUtOXVKwhxm84U', ); default: diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 3850cb7500..9a7dbc53f1 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -41,6 +41,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { : super(Mimblewimblecoin(network)); final syncMutex = Mutex(); + final _walletOpenMutex = Mutex(); NodeModel? _mimblewimblecoinNode; Timer? timer; @@ -95,24 +96,31 @@ class MimblewimblecoinWallet extends Bip39Wallet { } Future _ensureWalletOpen() async { - final existing = await secureStorageInterface.read( - key: '${walletId}_wallet', - ); - if (existing != null && existing.isNotEmpty) return existing; + return await _walletOpenMutex.protect(() async { + final existing = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); + if (existing != null && existing.isNotEmpty) return existing; - final config = await _getRealConfig(); - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - if (password == null) { - throw Exception('Wallet password not found'); - } - final opened = await libMwc.openWallet(config: config, password: password); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: opened, - ); - return opened; + final config = await _getRealConfig(); + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); + if (password == null) { + throw Exception('Wallet password not found'); + } + final opened = await libMwc + .openWallet(config: config, password: password) + .timeout( + const Duration(seconds: 60), + onTimeout: () => throw TimeoutException('openWallet timed out'), + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: opened, + ); + return opened; + }); } /// Returns an empty String on success, error message on failure. @@ -563,6 +571,17 @@ class MimblewimblecoinWallet extends Bip39Wallet { // ================= Private ================================================= + Future _ensureApiSecret(String walletDir) async { + final file = File('$walletDir/.api_secret'); + final secret = _mimblewimblecoinNode?.nodeApiSecret; + if (secret != null) { + await Directory(walletDir).create(recursive: true); + await file.writeAsString(secret); + } else if (await file.exists()) { + await file.delete(); + } + } + Future _getConfig() async { if (_mimblewimblecoinNode == null) { await updateNode(); @@ -576,6 +595,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String nodeApiAddress = uri.toString(); final walletDir = await _currentWalletDirPath(); + await _ensureApiSecret(walletDir); + final Map config = {}; config["wallet_dir"] = walletDir; config["check_node_api_http_addr"] = nodeApiAddress; @@ -894,14 +915,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open wallet - encodedWallet = await libMwc.openWallet( - config: stringConfig, - password: password, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: encodedWallet, - ); + encodedWallet = await _ensureWalletOpen(); //Store MwcMqs address info await _generateAndStoreReceivingAddressForIndex(0); @@ -926,23 +940,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); } else { try { - final config = await _getRealConfig(); - //if (!_logsInitialized) { - // await libMwc.initLogs(config: config); - // _logsInitialized = true; // Set flag to true after initializing - //} - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - - final walletOpen = await libMwc.openWallet( - config: config, - password: password!, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: walletOpen, - ); + await _ensureWalletOpen(); await updateNode(); } catch (e, s) { @@ -1144,14 +1142,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open Wallet - final walletOpen = await libMwc.openWallet( - config: stringConfig, - password: password, - ); - await secureStorageInterface.write( - key: '${walletId}_wallet', - value: walletOpen, - ); + await _ensureWalletOpen(); await _generateAndStoreReceivingAddressForIndex( mimblewimblecoinData.receivingIndex, @@ -1505,7 +1496,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { NodeFormData() ..host = node!.host ..useSSL = node.useSSL - ..port = node.port, + ..port = node.port + ..apiSecret = node.nodeApiSecret, ) != null; } catch (e, s) {