mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-17 01:37:40 +00:00
fix mweb crash on non-fully deleted mweb cache, sync status ETA, other connection fixes
This commit is contained in:
parent
8d7bad37f4
commit
e48cd0df41
5 changed files with 126 additions and 22 deletions
|
@ -97,6 +97,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
late final Bip32Slip10Secp256k1 mwebHd;
|
late final Bip32Slip10Secp256k1 mwebHd;
|
||||||
late final Box<MwebUtxo> mwebUtxosBox;
|
late final Box<MwebUtxo> mwebUtxosBox;
|
||||||
Timer? _syncTimer;
|
Timer? _syncTimer;
|
||||||
|
Timer? _stuckSyncTimer;
|
||||||
Timer? _feeRatesTimer;
|
Timer? _feeRatesTimer;
|
||||||
StreamSubscription<Utxo>? _utxoStream;
|
StreamSubscription<Utxo>? _utxoStream;
|
||||||
late RpcClient _stub;
|
late RpcClient _stub;
|
||||||
|
@ -314,26 +315,27 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
await transactionHistory.save();
|
await transactionHistory.save();
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// setup a watch dog to restart the sync process if it gets stuck:
|
// setup a watch dog to restart the sync process if it gets stuck:
|
||||||
List<double> lastFewProgresses = [];
|
List<double> lastFewProgresses = [];
|
||||||
Timer.periodic(const Duration(seconds: 10), (timer) async {
|
_stuckSyncTimer?.cancel();
|
||||||
|
_stuckSyncTimer = Timer.periodic(const Duration(seconds: 10), (timer) async {
|
||||||
if (syncStatus is! SyncingSyncStatus) return;
|
if (syncStatus is! SyncingSyncStatus) return;
|
||||||
if (syncStatus.progress() > 0.98) return;
|
if (syncStatus.progress() > 0.98) return; // don't check if we're close to synced
|
||||||
lastFewProgresses.add(syncStatus.progress());
|
lastFewProgresses.add(syncStatus.progress());
|
||||||
if (lastFewProgresses.length < 4) return;
|
if (lastFewProgresses.length < 10) return;
|
||||||
// limit list size to 4:
|
// limit list size to 10:
|
||||||
while (lastFewProgresses.length > 4) {
|
while (lastFewProgresses.length > 10) {
|
||||||
lastFewProgresses.removeAt(0);
|
lastFewProgresses.removeAt(0);
|
||||||
}
|
}
|
||||||
// if the progress is the same over the last 40 seconds, restart the sync:
|
// if the progress is the same over the last 100 seconds, restart the sync:
|
||||||
if (lastFewProgresses.every((p) => p == lastFewProgresses.first)) {
|
if (lastFewProgresses.every((p) => p == lastFewProgresses.first)) {
|
||||||
print("mweb syncing is stuck, restarting...");
|
print("mweb syncing is stuck, restarting...");
|
||||||
|
syncStatus = LostConnectionSyncStatus();
|
||||||
await stopSync();
|
await stopSync();
|
||||||
startSync();
|
|
||||||
timer.cancel();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -870,10 +872,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
final utxo = unspentCoins
|
final utxo = unspentCoins
|
||||||
.firstWhere((utxo) => utxo.hash == txInput.txId && utxo.vout == txInput.txIndex);
|
.firstWhere((utxo) => utxo.hash == txInput.txId && utxo.vout == txInput.txIndex);
|
||||||
|
|
||||||
if (txInput.sequence.isEmpty) {
|
|
||||||
isHogEx = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: detect actual hog-ex inputs
|
// TODO: detect actual hog-ex inputs
|
||||||
// print(txInput.sequence);
|
// print(txInput.sequence);
|
||||||
// print(txInput.txIndex);
|
// print(txInput.txIndex);
|
||||||
|
@ -949,6 +947,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await super.close();
|
await super.close();
|
||||||
|
await stopSync();
|
||||||
|
_stuckSyncTimer?.cancel();
|
||||||
|
_feeRatesTimer?.cancel();
|
||||||
_syncTimer?.cancel();
|
_syncTimer?.cancel();
|
||||||
_utxoStream?.cancel();
|
_utxoStream?.cancel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@ class LitecoinWalletService extends WalletService<
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
BitcoinRestoreWalletFromSeedCredentials,
|
||||||
BitcoinRestoreWalletFromWIFCredentials,
|
BitcoinRestoreWalletFromWIFCredentials,
|
||||||
BitcoinNewWalletCredentials> {
|
BitcoinNewWalletCredentials> {
|
||||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
|
LitecoinWalletService(
|
||||||
|
this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
|
@ -66,6 +67,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> openWallet(String name, String password) async {
|
Future<LitecoinWallet> openWallet(String name, String password) async {
|
||||||
|
|
||||||
final walletInfo = walletInfoSource.values
|
final walletInfo = walletInfoSource.values
|
||||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||||
|
|
||||||
|
@ -103,13 +105,21 @@ class LitecoinWalletService extends WalletService<
|
||||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||||
await walletInfoSource.delete(walletInfo.key);
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
|
|
||||||
// if there are no more litecoin wallets left, delete the neutrino db:
|
// if there are no more litecoin wallets left, cleanup the neutrino db and other files created by mwebd:
|
||||||
if (walletInfoSource.values.where((info) => info.type == WalletType.litecoin).isEmpty) {
|
if (walletInfoSource.values.where((info) => info.type == WalletType.litecoin).isEmpty) {
|
||||||
final appDir = await getApplicationSupportDirectory();
|
final appDirPath = (await getApplicationSupportDirectory()).path;
|
||||||
File neturinoDb = File('${appDir.path}/neutrino.db');
|
File neturinoDb = File('$appDirPath/neutrino.db');
|
||||||
|
File blockHeaders = File('$appDirPath/block_headers.bin');
|
||||||
|
File regFilterHeaders = File('$appDirPath/reg_filter_headers.bin');
|
||||||
if (neturinoDb.existsSync()) {
|
if (neturinoDb.existsSync()) {
|
||||||
neturinoDb.deleteSync();
|
neturinoDb.deleteSync();
|
||||||
}
|
}
|
||||||
|
if (blockHeaders.existsSync()) {
|
||||||
|
blockHeaders.deleteSync();
|
||||||
|
}
|
||||||
|
if (regFilterHeaders.existsSync()) {
|
||||||
|
regFilterHeaders.deleteSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
abstract class SyncStatus {
|
abstract class SyncStatus {
|
||||||
const SyncStatus();
|
const SyncStatus();
|
||||||
double progress();
|
double progress();
|
||||||
|
|
||||||
|
String formattedProgress() {
|
||||||
|
return "${(progress() * 100).toStringAsFixed(2)}%";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StartingScanSyncStatus extends SyncStatus {
|
class StartingScanSyncStatus extends SyncStatus {
|
||||||
|
@ -12,10 +16,12 @@ class StartingScanSyncStatus extends SyncStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncingSyncStatus extends SyncStatus {
|
class SyncingSyncStatus extends SyncStatus {
|
||||||
SyncingSyncStatus(this.blocksLeft, this.ptc);
|
SyncingSyncStatus(this.blocksLeft, this.ptc) {
|
||||||
|
updateEtaHistory(blocksLeft);
|
||||||
|
}
|
||||||
|
|
||||||
final double ptc;
|
double ptc;
|
||||||
final int blocksLeft;
|
int blocksLeft;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double progress() => ptc;
|
double progress() => ptc;
|
||||||
|
@ -32,6 +38,83 @@ class SyncingSyncStatus extends SyncStatus {
|
||||||
// sum 1 because if at the chain tip, will say "0 blocks left"
|
// sum 1 because if at the chain tip, will say "0 blocks left"
|
||||||
return SyncingSyncStatus(left + 1, ptc);
|
return SyncingSyncStatus(left + 1, ptc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateEtaHistory(int blocksLeft) {
|
||||||
|
blockHistory[DateTime.now()] = blocksLeft;
|
||||||
|
|
||||||
|
// Keep only the last 5 entries to limit memory usage
|
||||||
|
if (blockHistory.length > 5) {
|
||||||
|
var oldestKey = blockHistory.keys.reduce((a, b) => a.isBefore(b) ? a : b);
|
||||||
|
blockHistory.remove(oldestKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<DateTime, int> blockHistory = {};
|
||||||
|
|
||||||
|
DateTime? estimatedCompletionTime;
|
||||||
|
Duration? estimatedCompletionDuration;
|
||||||
|
|
||||||
|
Duration getEtaDuration() {
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime? completionTime = calculateETA();
|
||||||
|
return completionTime.difference(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? getFormattedEta() {
|
||||||
|
// throw out any entries that are more than a minute old:
|
||||||
|
blockHistory.removeWhere(
|
||||||
|
(key, value) => key.isBefore(DateTime.now().subtract(const Duration(minutes: 1))));
|
||||||
|
|
||||||
|
if (blockHistory.length < 2) return null;
|
||||||
|
Duration? duration = getEtaDuration();
|
||||||
|
|
||||||
|
if (duration.inDays > 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||||
|
|
||||||
|
final hours = twoDigits(duration.inHours);
|
||||||
|
final minutes = twoDigits(duration.inMinutes.remainder(60));
|
||||||
|
final seconds = twoDigits(duration.inSeconds.remainder(60));
|
||||||
|
if (hours == '00') {
|
||||||
|
return '${minutes}m${seconds}s';
|
||||||
|
}
|
||||||
|
return '${hours}h${minutes}m${seconds}s';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the rate of block processing (blocks per second)
|
||||||
|
double calculateRate() {
|
||||||
|
List<DateTime> timestamps = blockHistory.keys.toList();
|
||||||
|
List<int> blockCounts = blockHistory.values.toList();
|
||||||
|
|
||||||
|
double totalTimeMinutes = 0;
|
||||||
|
int totalBlocksProcessed = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < blockCounts.length - 1; i++) {
|
||||||
|
int blocksProcessed = blockCounts[i + 1] - blockCounts[i];
|
||||||
|
Duration timeDifference = timestamps[i].difference(timestamps[i + 1]);
|
||||||
|
totalTimeMinutes += timeDifference.inSeconds;
|
||||||
|
totalBlocksProcessed += blocksProcessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalTimeMinutes == 0 || totalBlocksProcessed == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalBlocksProcessed / totalTimeMinutes; // Blocks per second
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the ETA
|
||||||
|
DateTime calculateETA() {
|
||||||
|
double rate = calculateRate();
|
||||||
|
if (rate < 0.01) {
|
||||||
|
return DateTime.now().add(const Duration(days: 1));
|
||||||
|
}
|
||||||
|
int remainingBlocks = this.blocksLeft;
|
||||||
|
double timeRemainingSeconds = remainingBlocks / rate;
|
||||||
|
return DateTime.now().add(Duration(seconds: timeRemainingSeconds.round()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncedSyncStatus extends SyncStatus {
|
class SyncedSyncStatus extends SyncStatus {
|
||||||
|
|
|
@ -3,9 +3,18 @@ import 'package:cw_core/sync_status.dart';
|
||||||
|
|
||||||
String syncStatusTitle(SyncStatus syncStatus) {
|
String syncStatusTitle(SyncStatus syncStatus) {
|
||||||
if (syncStatus is SyncingSyncStatus) {
|
if (syncStatus is SyncingSyncStatus) {
|
||||||
return syncStatus.blocksLeft == 1
|
|
||||||
? S.current.block_remaining
|
if (syncStatus.blocksLeft == 1) {
|
||||||
: S.current.Blocks_remaining('${syncStatus.blocksLeft}');
|
return S.current.block_remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
String eta = syncStatus.getFormattedEta() ?? '';
|
||||||
|
|
||||||
|
if (eta.isEmpty) {
|
||||||
|
return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
|
||||||
|
} else {
|
||||||
|
return "${syncStatus.formattedProgress()} - $eta";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncStatus is SyncedTipSyncStatus) {
|
if (syncStatus is SyncedTipSyncStatus) {
|
||||||
|
|
|
@ -258,6 +258,7 @@ abstract class DashboardViewModelBase with Store {
|
||||||
|
|
||||||
if (hasMweb) {
|
if (hasMweb) {
|
||||||
mwebScanningActive = bitcoin!.getMwebEnabled(wallet);
|
mwebScanningActive = bitcoin!.getMwebEnabled(wallet);
|
||||||
|
settingsStore.mwebEnabled = mwebScanningActive;
|
||||||
reaction((_) => settingsStore.mwebAlwaysScan, (bool alwaysScan) {
|
reaction((_) => settingsStore.mwebAlwaysScan, (bool alwaysScan) {
|
||||||
if (alwaysScan) {
|
if (alwaysScan) {
|
||||||
mwebScanningActive = true;
|
mwebScanningActive = true;
|
||||||
|
|
Loading…
Reference in a new issue