mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
CW-1024 Improve performance of xmr wallet (#2128)
* - enabled development options in CI builds. - Implemented caching for account retrieval. - refactor transaction handling in `dashboard_view_model.dart` to improve efficiency and reduce unnecessary updates in xmr. - `DevMoneroCallProfilerPage`, for profiling performance of xmr,wow,zano wallet calls. * use FeatureFlag.hasDevOptions * prevent crashes in monero_c by using mutexes properly improve performance of _transactionDisposer remove unnecessary checks * remove logging, bring back simplified logic * update _transactionDisposer on length and confirmation of first and last transaction * address comments from review * don't throw unhandled exceptions in unawaited async code * use cached transaction list in getAllSubaddresses, fix usage of txHistoryMutex * [DNM] fix: crashes when opening wallet, performance issue when syncing and update dependencies * Revert "use cached transaction list in getAllSubaddresses, fix usage of txHistoryMutex" This reverts commit 4c4c33ac6a47603e970a6c8d940e90204525b241. * Revert "[DNM] fix: crashes when opening wallet, performance issue when syncing and update dependencies" This reverts commit d7603445ad6ae76d76bf179c34728ce242c8c610. * Revert "use cached transaction list in getAllSubaddresses, fix usage of txHistoryMutex" This reverts commit 4c4c33ac6a47603e970a6c8d940e90204525b241. * update shared_preferences * improve state management performance by not rendering multiple changes in transaction screen on a single frame * fix wallet switching
This commit is contained in:
parent
27eaa1b1cc
commit
cbca4c9c77
25 changed files with 498 additions and 105 deletions
|
@ -1,21 +1,42 @@
|
|||
import 'dart:ffi';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:cw_monero/api/account_list.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
||||
monero.Coins? coins = null;
|
||||
final coinsMutex = Mutex();
|
||||
|
||||
void refreshCoins(int accountIndex) {
|
||||
Future<void> refreshCoins(int accountIndex) async {
|
||||
if (coinsMutex.isLocked) {
|
||||
return;
|
||||
}
|
||||
coins = monero.Wallet_coins(wptr!);
|
||||
monero.Coins_refresh(coins!);
|
||||
final coinsPtr = coins!.address;
|
||||
await coinsMutex.acquire();
|
||||
await Isolate.run(() => monero.Coins_refresh(Pointer.fromAddress(coinsPtr)));
|
||||
coinsMutex.release();
|
||||
}
|
||||
|
||||
int countOfCoins() => monero.Coins_count(coins!);
|
||||
Future<int> countOfCoins() async {
|
||||
await coinsMutex.acquire();
|
||||
final count = monero.Coins_count(coins!);
|
||||
coinsMutex.release();
|
||||
return count;
|
||||
}
|
||||
|
||||
monero.CoinsInfo getCoin(int index) => monero.Coins_coin(coins!, index);
|
||||
Future<monero.CoinsInfo> getCoin(int index) async {
|
||||
await coinsMutex.acquire();
|
||||
final coin = monero.Coins_coin(coins!, index);
|
||||
coinsMutex.release();
|
||||
return coin;
|
||||
}
|
||||
|
||||
int? getCoinByKeyImage(String keyImage) {
|
||||
final count = countOfCoins();
|
||||
Future<int?> getCoinByKeyImage(String keyImage) async {
|
||||
final count = await countOfCoins();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final coin = getCoin(i);
|
||||
final coin = await getCoin(i);
|
||||
final coinAddress = monero.CoinsInfo_keyImage(coin);
|
||||
if (keyImage == coinAddress) {
|
||||
return i;
|
||||
|
@ -24,6 +45,16 @@ int? getCoinByKeyImage(String keyImage) {
|
|||
return null;
|
||||
}
|
||||
|
||||
void freezeCoin(int index) => monero.Coins_setFrozen(coins!, index: index);
|
||||
Future<void> freezeCoin(int index) async {
|
||||
await coinsMutex.acquire();
|
||||
final coinsPtr = coins!.address;
|
||||
await Isolate.run(() => monero.Coins_setFrozen(Pointer.fromAddress(coinsPtr), index: index));
|
||||
coinsMutex.release();
|
||||
}
|
||||
|
||||
void thawCoin(int index) => monero.Coins_thaw(coins!, index: index);
|
||||
Future<void> thawCoin(int index) async {
|
||||
await coinsMutex.acquire();
|
||||
final coinsPtr = coins!.address;
|
||||
await Isolate.run(() => monero.Coins_thaw(Pointer.fromAddress(coinsPtr), index: index));
|
||||
coinsMutex.release();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:ffi';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_monero/api/account_list.dart';
|
||||
import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart';
|
||||
import 'package:cw_monero/api/monero_output.dart';
|
||||
|
@ -13,15 +14,23 @@ import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen;
|
|||
import 'package:mutex/mutex.dart';
|
||||
|
||||
|
||||
Map<int, Map<String, String>> txKeys = {};
|
||||
String getTxKey(String txId) {
|
||||
txKeys[wptr!.address] ??= {};
|
||||
if (txKeys[wptr!.address]![txId] != null) {
|
||||
return txKeys[wptr!.address]![txId]!;
|
||||
}
|
||||
final txKey = monero.Wallet_getTxKey(wptr!, txid: txId);
|
||||
final status = monero.Wallet_status(wptr!);
|
||||
if (status != 0) {
|
||||
final error = monero.Wallet_errorString(wptr!);
|
||||
monero.Wallet_errorString(wptr!);
|
||||
txKeys[wptr!.address]![txId] = "";
|
||||
return "";
|
||||
}
|
||||
txKeys[wptr!.address]![txId] = txKey;
|
||||
return txKey;
|
||||
}
|
||||
|
||||
final txHistoryMutex = Mutex();
|
||||
monero.TransactionHistory? txhistory;
|
||||
bool isRefreshingTx = false;
|
||||
|
@ -34,6 +43,7 @@ Future<void> refreshTransactions() async {
|
|||
await Isolate.run(() {
|
||||
monero.TransactionHistory_refresh(Pointer.fromAddress(ptr));
|
||||
});
|
||||
await Future.delayed(Duration.zero);
|
||||
txHistoryMutex.release();
|
||||
isRefreshingTx = false;
|
||||
}
|
||||
|
@ -45,8 +55,24 @@ Future<List<Transaction>> getAllTransactions() async {
|
|||
|
||||
await txHistoryMutex.acquire();
|
||||
txhistory ??= monero.Wallet_history(wptr!);
|
||||
final startAddress = txhistory!.address * wptr!.address;
|
||||
int size = countOfTransactions();
|
||||
final list = List.generate(size, (index) => Transaction(txInfo: monero.TransactionHistory_transaction(txhistory!, index: index)));
|
||||
final list = <Transaction>[];
|
||||
for (int index = 0; index < size; index++) {
|
||||
if (index % 25 == 0) {
|
||||
// Give main thread a chance to do other things.
|
||||
await Future.delayed(Duration.zero);
|
||||
}
|
||||
if (txhistory!.address * wptr!.address != startAddress) {
|
||||
printV("Loop broken because txhistory!.address * wptr!.address != startAddress");
|
||||
break;
|
||||
}
|
||||
final txInfo = monero.TransactionHistory_transaction(txhistory!, index: index);
|
||||
final txHash = monero.TransactionInfo_hash(txInfo);
|
||||
txCache[wptr!.address] ??= {};
|
||||
txCache[wptr!.address]![txHash] = Transaction(txInfo: txInfo);
|
||||
list.add(txCache[wptr!.address]![txHash]!);
|
||||
}
|
||||
txHistoryMutex.release();
|
||||
final accts = monero.Wallet_numSubaddressAccounts(wptr!);
|
||||
for (var i = 0; i < accts; i++) {
|
||||
|
@ -79,8 +105,18 @@ Future<List<Transaction>> getAllTransactions() async {
|
|||
return list;
|
||||
}
|
||||
|
||||
Transaction getTransaction(String txId) {
|
||||
return Transaction(txInfo: monero.TransactionHistory_transactionById(txhistory!, txid: txId));
|
||||
Map<int, Map<String, Transaction>> txCache = {};
|
||||
Future<Transaction> getTransaction(String txId) async {
|
||||
if (txCache[wptr!.address] != null && txCache[wptr!.address]![txId] != null) {
|
||||
return txCache[wptr!.address]![txId]!;
|
||||
}
|
||||
await txHistoryMutex.acquire();
|
||||
final tx = monero.TransactionHistory_transactionById(txhistory!, txid: txId);
|
||||
final txDart = Transaction(txInfo: tx);
|
||||
txCache[wptr!.address] ??= {};
|
||||
txCache[wptr!.address]![txId] = txDart;
|
||||
txHistoryMutex.release();
|
||||
return txDart;
|
||||
}
|
||||
|
||||
Future<PendingTransactionDescription> createTransactionSync(
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cw_core/root_dir.dart';
|
|||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_monero/api/account_list.dart';
|
||||
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
|
||||
import 'package:cw_monero/api/wallet_manager.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
@ -199,12 +200,15 @@ void startRefreshSync() {
|
|||
}
|
||||
|
||||
|
||||
void setRefreshFromBlockHeight({required int height}) =>
|
||||
monero.Wallet_setRefreshFromBlockHeight(wptr!,
|
||||
refresh_from_block_height: height);
|
||||
void setRefreshFromBlockHeight({required int height}) {
|
||||
monero.Wallet_setRefreshFromBlockHeight(wptr!,
|
||||
refresh_from_block_height: height);
|
||||
}
|
||||
|
||||
void setRecoveringFromSeed({required bool isRecovery}) =>
|
||||
monero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery);
|
||||
void setRecoveringFromSeed({required bool isRecovery}) {
|
||||
monero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery);
|
||||
monero.Wallet_store(wptr!);
|
||||
}
|
||||
|
||||
final storeMutex = Mutex();
|
||||
|
||||
|
@ -394,4 +398,6 @@ String signMessage(String message, {String address = ""}) {
|
|||
|
||||
bool verifyMessage(String message, String address, String signature) {
|
||||
return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<int>> debugCallLength() => monero.debugCallLength;
|
||||
|
|
|
@ -137,6 +137,7 @@ void restoreWalletFromSeedSync(
|
|||
wptr = newWptr;
|
||||
|
||||
setRefreshFromBlockHeight(height: restoreHeight);
|
||||
setupBackgroundSync(password, newWptr);
|
||||
|
||||
monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:cw_core/monero_amount_format.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_monero/api/wallet_manager.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/account.dart';
|
||||
import 'package:cw_monero/api/account_list.dart' as account_list;
|
||||
|
@ -44,7 +45,18 @@ abstract class MoneroAccountListBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
List<Account> getAll() => account_list.getAllAccount().map((accountRow) {
|
||||
Map<int, List<Account>> _cachedAccounts = {};
|
||||
|
||||
List<Account> getAll() {
|
||||
final allAccounts = account_list.getAllAccount();
|
||||
final currentCount = allAccounts.length;
|
||||
_cachedAccounts[account_list.wptr!.address] ??= [];
|
||||
|
||||
if (_cachedAccounts[account_list.wptr!.address]!.length == currentCount) {
|
||||
return _cachedAccounts[account_list.wptr!.address]!;
|
||||
}
|
||||
|
||||
_cachedAccounts[account_list.wptr!.address] = allAccounts.map((accountRow) {
|
||||
final balance = monero.SubaddressAccountRow_getUnlockedBalance(accountRow);
|
||||
|
||||
return Account(
|
||||
|
@ -53,6 +65,9 @@ abstract class MoneroAccountListBase with Store {
|
|||
balance: moneroAmountToString(amount: monero.Wallet_amountFromString(balance)),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return _cachedAccounts[account_list.wptr!.address]!;
|
||||
}
|
||||
|
||||
Future<void> addAccount({required String label}) async {
|
||||
await account_list.addAccount(label: label);
|
||||
|
|
|
@ -7,28 +7,33 @@ class MoneroUnspent extends Unspent {
|
|||
MoneroUnspent(
|
||||
String address, String hash, String keyImage, int value, bool isFrozen, this.isUnlocked)
|
||||
: super(address, hash, value, 0, keyImage) {
|
||||
getCoinByKeyImage(keyImage).then((coinId) {
|
||||
if (coinId == null) return;
|
||||
getCoin(coinId).then((coin) {
|
||||
_frozen = monero.CoinsInfo_frozen(coin);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool _frozen = false;
|
||||
|
||||
@override
|
||||
set isFrozen(bool freeze) {
|
||||
printV("set isFrozen: $freeze ($keyImage): $freeze");
|
||||
final coinId = getCoinByKeyImage(keyImage!);
|
||||
if (coinId == null) throw Exception("Unable to find a coin for address $address");
|
||||
if (freeze) {
|
||||
freezeCoin(coinId);
|
||||
} else {
|
||||
thawCoin(coinId);
|
||||
}
|
||||
getCoinByKeyImage(keyImage!).then((coinId) async {
|
||||
if (coinId == null) return;
|
||||
if (freeze) {
|
||||
await freezeCoin(coinId);
|
||||
_frozen = true;
|
||||
} else {
|
||||
await thawCoin(coinId);
|
||||
_frozen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isFrozen {
|
||||
printV("get isFrozen");
|
||||
final coinId = getCoinByKeyImage(keyImage!);
|
||||
if (coinId == null) throw Exception("Unable to find a coin for address $address");
|
||||
final coin = getCoin(coinId);
|
||||
return monero.CoinsInfo_frozen(coin);
|
||||
}
|
||||
bool get isFrozen => _frozen;
|
||||
|
||||
final bool isUnlocked;
|
||||
}
|
||||
|
|
|
@ -169,6 +169,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
if (monero_wallet.getCurrentHeight() <= 1) {
|
||||
monero_wallet.setRefreshFromBlockHeight(
|
||||
height: walletInfo.restoreHeight);
|
||||
setupBackgroundSync(password, wptr!);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -570,6 +571,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
walletInfo.restoreHeight = height;
|
||||
walletInfo.isRecovery = true;
|
||||
monero_wallet.setRefreshFromBlockHeight(height: height);
|
||||
setupBackgroundSync(password, wptr!);
|
||||
monero_wallet.rescanBlockchainAsync();
|
||||
await startSync();
|
||||
_askForUpdateBalance();
|
||||
|
@ -585,9 +587,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
unspentCoins.clear();
|
||||
|
||||
final coinCount = countOfCoins();
|
||||
final coinCount = await countOfCoins();
|
||||
for (var i = 0; i < coinCount; i++) {
|
||||
final coin = getCoin(i);
|
||||
final coin = await getCoin(i);
|
||||
final coinSpent = monero.CoinsInfo_spent(coin);
|
||||
if (coinSpent == false && monero.CoinsInfo_subaddrAccount(coin) == walletAddresses.account!.id) {
|
||||
final unspent = MoneroUnspent(
|
||||
|
@ -600,7 +602,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
);
|
||||
// TODO: double-check the logic here
|
||||
if (unspent.hash.isNotEmpty) {
|
||||
unspent.isChange = transaction_history.getTransaction(unspent.hash).isSpend == true;
|
||||
final tx = await transaction_history.getTransaction(unspent.hash);
|
||||
unspent.isChange = tx.isSpend == true;
|
||||
}
|
||||
unspentCoins.add(unspent);
|
||||
}
|
||||
|
@ -692,14 +695,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
@override
|
||||
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
||||
transaction_history.refreshTransactions();
|
||||
return (await _getAllTransactionsOfAccount(walletAddresses.account?.id))
|
||||
await transaction_history.refreshTransactions();
|
||||
final resp = (await _getAllTransactionsOfAccount(walletAddresses.account?.id))
|
||||
.fold<Map<String, MoneroTransactionInfo>>(
|
||||
<String, MoneroTransactionInfo>{},
|
||||
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
||||
acc[tx.id] = tx;
|
||||
return acc;
|
||||
});
|
||||
return resp;
|
||||
}
|
||||
|
||||
Future<void> updateTransactions() async {
|
||||
|
@ -710,8 +714,17 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
_isTransactionUpdating = true;
|
||||
final transactions = await fetchTransactions();
|
||||
transactionHistory.clear();
|
||||
transactionHistory.addMany(transactions);
|
||||
|
||||
final currentIds = transactionHistory.transactions.keys.toSet();
|
||||
final newIds = transactions.keys.toSet();
|
||||
|
||||
// Remove transactions that no longer exist
|
||||
currentIds.difference(newIds).forEach((id) =>
|
||||
transactionHistory.transactions.remove(id));
|
||||
|
||||
// Add or update transactions
|
||||
transactions.forEach((key, tx) =>
|
||||
transactionHistory.transactions[key] = tx);
|
||||
await transactionHistory.save();
|
||||
_isTransactionUpdating = false;
|
||||
} catch (e) {
|
||||
|
@ -778,6 +791,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
monero_wallet.setRecoveringFromSeed(isRecovery: true);
|
||||
monero_wallet.setRefreshFromBlockHeight(height: height);
|
||||
setupBackgroundSync(password, wptr!);
|
||||
}
|
||||
|
||||
int _getHeightDistance(DateTime date) {
|
||||
|
|
|
@ -159,19 +159,12 @@ class MoneroWalletService extends WalletService<
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
password: password);
|
||||
final isValid = wallet.walletAddresses.validate();
|
||||
|
||||
if (wallet.isHardwareWallet) {
|
||||
wallet.setLedgerConnection(gLedger!);
|
||||
gLedger = null;
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close(shouldCleanup: false);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue