This commit is contained in:
julian 2024-10-01 16:13:36 -06:00
parent 44fbab715a
commit fec5f5883f
2 changed files with 117 additions and 39 deletions

View file

@ -490,7 +490,15 @@ class ElectrumXClient {
command: 'server.ping', command: 'server.ping',
requestTimeout: const Duration(seconds: 2), requestTimeout: const Duration(seconds: 2),
retries: retryCount, retries: retryCount,
).timeout(const Duration(seconds: 2)) as bool; ).timeout(
const Duration(seconds: 2),
onTimeout: () {
Logging.instance.log(
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",
level: LogLevel.Debug,
);
},
) as bool;
} catch (e) { } catch (e) {
rethrow; rethrow;
} }

View file

@ -487,113 +487,9 @@ abstract class Wallet<T extends CryptoCurrency> {
// Should fire events // Should fire events
Future<void> refresh() async { Future<void> refresh() async {
// Awaiting this lock could be dangerous. final refreshCompleter = Completer<void>();
// Since refresh is periodic (generally) final future = refreshCompleter.future.then(
if (refreshMutex.isLocked) { (_) {
return;
}
final start = DateTime.now();
try {
// this acquire should be almost instant due to above check.
// Slight possibility of race but should be irrelevant
await refreshMutex.acquire();
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.syncing,
walletId,
cryptoCurrency,
),
);
// add some small buffer before making calls.
// this can probably be removed in the future but was added as a
// debugging feature
await Future<void>.delayed(const Duration(milliseconds: 300));
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
final Set<String> codesToCheck = {};
if (this is PaynymInterface) {
// isSegwit does not matter here at all
final myCode =
await (this as PaynymInterface).getPaymentCode(isSegwit: false);
final nym = await PaynymIsApi().nym(myCode.toString());
if (nym.value != null) {
for (final follower in nym.value!.followers) {
codesToCheck.add(follower.code);
}
for (final following in nym.value!.following) {
codesToCheck.add(following.code);
}
}
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
await updateChainHeight();
if (this is BitcoinFrostWallet) {
await (this as BitcoinFrostWallet).lookAhead();
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is MultiAddressInterface) {
if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
await (this as MultiAddressInterface)
.checkReceivingAddressForTransactions();
}
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is MultiAddressInterface) {
await (this as MultiAddressInterface)
.checkChangeAddressForTransactions();
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
if (this is SparkInterface) {
// this should be called before updateTransactions()
await (this as SparkInterface).refreshSparkData();
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
final fetchFuture = updateTransactions();
final utxosRefreshFuture = updateUTXOs();
// if (currentHeight != storedHeight) {
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId));
await utxosRefreshFuture;
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId));
await fetchFuture;
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is PaynymInterface && codesToCheck.isNotEmpty) {
await (this as PaynymInterface)
.checkForNotificationTransactionsTo(codesToCheck);
// check utxos again for notification outputs
await updateUTXOs();
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId));
// await getAllTxsToWatch();
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is LelantusInterface) {
if (info.otherData[WalletInfoKeys.enableLelantusScanning] as bool? ??
false) {
await (this as LelantusInterface).refreshLelantusData();
}
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId));
await updateBalance();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId));
GlobalEventBus.instance.fire( GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent( WalletSyncStatusChangedEvent(
WalletSyncStatus.synced, WalletSyncStatus.synced,
@ -616,7 +512,8 @@ abstract class Wallet<T extends CryptoCurrency> {
// } // }
}); });
} }
} catch (error, strace) { },
onError: (Object error, StackTrace strace) {
GlobalEventBus.instance.fire( GlobalEventBus.instance.fire(
NodeConnectionStatusChangedEvent( NodeConnectionStatusChangedEvent(
NodeConnectionStatus.disconnected, NodeConnectionStatus.disconnected,
@ -635,8 +532,181 @@ abstract class Wallet<T extends CryptoCurrency> {
"Caught exception in refreshWalletData(): $error\n$strace", "Caught exception in refreshWalletData(): $error\n$strace",
level: LogLevel.Error, level: LogLevel.Error,
); );
},
);
unawaited(_refresh(refreshCompleter));
return future;
}
// Should fire events
Future<void> _refresh(Completer<void> completer) async {
// Awaiting this lock could be dangerous.
// Since refresh is periodic (generally)
if (refreshMutex.isLocked) {
return;
}
final start = DateTime.now();
bool tAlive = true;
final t = Timer.periodic(const Duration(seconds: 1), (timer) async {
if (tAlive) {
final pingSuccess = await pingCheck();
if (!pingSuccess) {
tAlive = false;
}
} else {
timer.cancel();
}
});
void _checkAlive() {
if (!tAlive) throw Exception("refresh alive ping failure");
}
try {
// this acquire should be almost instant due to above check.
// Slight possibility of race but should be irrelevant
await refreshMutex.acquire();
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.syncing,
walletId,
cryptoCurrency,
),
);
_checkAlive();
// add some small buffer before making calls.
// this can probably be removed in the future but was added as a
// debugging feature
await Future<void>.delayed(const Duration(milliseconds: 300));
_checkAlive();
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
final Set<String> codesToCheck = {};
_checkAlive();
if (this is PaynymInterface) {
// isSegwit does not matter here at all
final myCode =
await (this as PaynymInterface).getPaymentCode(isSegwit: false);
_checkAlive();
final nym = await PaynymIsApi().nym(myCode.toString());
_checkAlive();
if (nym.value != null) {
for (final follower in nym.value!.followers) {
codesToCheck.add(follower.code);
}
_checkAlive();
for (final following in nym.value!.following) {
codesToCheck.add(following.code);
}
}
_checkAlive();
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
_checkAlive();
await updateChainHeight();
_checkAlive();
if (this is BitcoinFrostWallet) {
await (this as BitcoinFrostWallet).lookAhead();
}
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
_checkAlive();
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is MultiAddressInterface) {
if (info.otherData[WalletInfoKeys.reuseAddress] != true) {
await (this as MultiAddressInterface)
.checkReceivingAddressForTransactions();
}
_checkAlive();
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
_checkAlive();
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is MultiAddressInterface) {
await (this as MultiAddressInterface)
.checkChangeAddressForTransactions();
}
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
if (this is SparkInterface) {
// this should be called before updateTransactions()
await (this as SparkInterface).refreshSparkData();
}
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
_checkAlive();
final fetchFuture = updateTransactions();
_checkAlive();
final utxosRefreshFuture = updateUTXOs();
// if (currentHeight != storedHeight) {
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId));
_checkAlive();
await utxosRefreshFuture;
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId));
_checkAlive();
await fetchFuture;
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is PaynymInterface && codesToCheck.isNotEmpty) {
_checkAlive();
await (this as PaynymInterface)
.checkForNotificationTransactionsTo(codesToCheck);
// check utxos again for notification outputs
_checkAlive();
await updateUTXOs();
}
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId));
// await getAllTxsToWatch();
_checkAlive();
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
if (this is LelantusInterface) {
if (info.otherData[WalletInfoKeys.enableLelantusScanning] as bool? ??
false) {
await (this as LelantusInterface).refreshLelantusData();
_checkAlive();
}
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId));
_checkAlive();
await updateBalance();
_checkAlive();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId));
tAlive = false; // interrupt timer as its not needed anymore
completer.complete();
} catch (error, strace) {
completer.completeError(error, strace);
} finally { } finally {
t.cancel();
refreshMutex.release(); refreshMutex.release();
if (!completer.isCompleted) {
completer.completeError(
"finally block hit before completer completed",
StackTrace.current,
);
}
Logging.instance.log( Logging.instance.log(
"Refresh for " "Refresh for "