Fix refresh loading forever

This commit is contained in:
likho 2023-01-08 17:19:58 +02:00
parent b60122fd85
commit 045cf857fd
2 changed files with 298 additions and 41 deletions

View file

@ -179,6 +179,7 @@ abstract class CoinServiceAPI {
walletName: walletName,
coin: coin,
secureStore: secureStorageInterface,
tracker: tracker,
);
case Coin.monero:

View file

@ -10,6 +10,7 @@ import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/models/paymint/utxo_model.dart';
import 'package:stackwallet/services/price.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
@ -32,6 +33,11 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
const int MINIMUM_CONFIRMATIONS = 1;
const int DUST_LIMIT = 294;
@ -82,23 +88,28 @@ class EthereumWallet extends CoinServiceAPI {
Coin get coin => _coin;
late SecureStorageInterface _secureStore;
late final TransactionNotificationTracker txTracker;
late PriceAPI _priceAPI;
final _prefs = Prefs.instance;
bool longMutex = false;
final _client = Web3Client(
"https://goerli.infura.io/v3/22677300bf774e49a458b73313ee56ba", Client());
final _blockExplorer = "https://eth-goerli.blockscout.com/api?";
late EthPrivateKey _credentials;
int _chainId = 5; //5 for testnet and 1 for mainnet
final int _chainId = 5; //5 for testnet and 1 for mainnet
EthereumWallet(
{required String walletId,
required String walletName,
required Coin coin,
PriceAPI? priceAPI,
required SecureStorageInterface secureStore}) {
EthereumWallet({
required String walletId,
required String walletName,
required Coin coin,
PriceAPI? priceAPI,
required SecureStorageInterface secureStore,
required TransactionNotificationTracker tracker,
}) {
txTracker = tracker;
_walletId = walletId;
_walletName = walletName;
_coin = coin;
@ -107,17 +118,35 @@ class EthereumWallet extends CoinServiceAPI {
_secureStore = secureStore;
}
bool _shouldAutoSync = false;
@override
bool shouldAutoSync = false;
bool get shouldAutoSync => _shouldAutoSync;
@override
set shouldAutoSync(bool shouldAutoSync) {
if (_shouldAutoSync != shouldAutoSync) {
_shouldAutoSync = shouldAutoSync;
if (!shouldAutoSync) {
timer?.cancel();
timer = null;
stopNetworkAlivePinging();
} else {
startNetworkAlivePinging();
refresh();
}
}
}
@override
String get walletName => _walletName;
late String _walletName;
late Coin _coin;
Timer? timer;
Timer? _networkAliveTimer;
@override
// TODO: implement allOwnAddresses
Future<List<String>> get allOwnAddresses =>
_allOwnAddresses ??= _fetchAllOwnAddresses();
Future<List<String>>? _allOwnAddresses;
@ -143,6 +172,11 @@ class EthereumWallet extends CoinServiceAPI {
@override
Future<String> confirmSend({required Map<String, dynamic> txData}) async {
final gasPrice = await _client.getGasPrice();
//Get Gas Limit for current block
final blockInfo = await _client.getBlockInformation(blockNumber: 'latest');
String gasLimit = blockInfo.gasLimit;
final amount = txData['recipientAmt'];
final decimalAmount =
Format.satoshisToAmount(amount as int, coin: Coin.ethereum);
@ -150,7 +184,7 @@ class EthereumWallet extends CoinServiceAPI {
final tx = Transaction.Transaction(
to: EthereumAddress.fromHex(txData['addresss'] as String),
gasPrice: gasPrice,
maxGas: 21000,
maxGas: int.parse(gasLimit),
value: EtherAmount.inWei(bigIntAmount));
final transaction =
await _client.sendTransaction(_credentials, tx, chainId: _chainId);
@ -357,32 +391,168 @@ class EthereumWallet extends CoinServiceAPI {
required int maxUnusedAddressGap,
required int maxNumberOfIndexesToCheck,
required int height}) async {
await _prefs.init();
print("Mnemonic is $mnemonic");
_credentials = EthPrivateKey.fromHex(StringToHex.toHexString(mnemonic));
longMutex = true;
final start = DateTime.now();
await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic);
try {
if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) {
print("DUPLICATE MNEMONIC");
longMutex = false;
throw Exception("Attempted to overwrite mnemonic on restore!");
}
await DB.instance
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
await DB.instance.put<dynamic>(
boxName: walletId, key: 'receivingAddresses', value: ["0"]);
await DB.instance
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
await DB.instance
.put<dynamic>(boxName: walletId, key: "changeIndex", value: 0);
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'blocked_tx_hashes',
value: ["0xdefault"],
); // A list of transaction hashes to represent frozen utxos in wallet
// initialize address book entries
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'addressBookEntries',
value: <String, String>{});
await DB.instance
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
await _secureStore.write(
key: '${_walletId}_mnemonic', value: mnemonic.trim());
_credentials = EthPrivateKey.fromHex(StringToHex.toHexString(mnemonic));
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from recoverFromMnemonic(): $e\n$s",
level: LogLevel.Error);
longMutex = false;
rethrow;
}
longMutex = false;
final end = DateTime.now();
Logging.instance.log(
"$walletName recovery time: ${end.difference(start).inMilliseconds} millis",
level: LogLevel.Info);
}
Future<bool> refreshIfThereIsNewData() async {
if (longMutex) return false;
if (_hasCalledExit) return false;
Logging.instance.log("refreshIfThereIsNewData", level: LogLevel.Info);
try {
bool needsRefresh = false;
Set<String> txnsToCheck = {};
for (final String txid in txTracker.pendings) {
if (!txTracker.wasNotifiedConfirmed(txid)) {
txnsToCheck.add(txid);
}
}
// for (String txid in txnsToCheck) {
// final txn = await _client.getTransactionByHash(txid);
// int confirmations = txn["confirmations"] as int? ?? 0;
// bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS;
// if (!isUnconfirmed) {
// // unconfirmedTxs = {};
// needsRefresh = true;
// break;
// }
// }
// if (!needsRefresh) {
// var allOwnAddresses = await _fetchAllOwnAddresses();
// List<Map<String, dynamic>> allTxs =
// await _fetchHistory(allOwnAddresses);
// final txData = await transactionData;
// for (Map<String, dynamic> transaction in allTxs) {
// if (txData.findTransaction(transaction['tx_hash'] as String) ==
// null) {
// Logging.instance.log(
// " txid not found in address history already ${transaction['tx_hash']}",
// level: LogLevel.Info);
// needsRefresh = true;
// break;
// }
// }
// }
return needsRefresh;
} catch (e, s) {
Logging.instance.log(
"Exception caught in refreshIfThereIsNewData: $e\n$s",
level: LogLevel.Error);
rethrow;
}
}
Future<void> getAllTxsToWatch(
TransactionData txData,
) async {
if (_hasCalledExit) return;
List<models.Transaction> unconfirmedTxnsToNotifyPending = [];
List<models.Transaction> unconfirmedTxnsToNotifyConfirmed = [];
for (final chunk in txData.txChunks) {
for (final tx in chunk.transactions) {
if (tx.confirmedStatus) {
// get all transactions that were notified as pending but not as confirmed
if (txTracker.wasNotifiedPending(tx.txid) &&
!txTracker.wasNotifiedConfirmed(tx.txid)) {
unconfirmedTxnsToNotifyConfirmed.add(tx);
}
} else {
// get all transactions that were not notified as pending yet
if (!txTracker.wasNotifiedPending(tx.txid)) {
unconfirmedTxnsToNotifyPending.add(tx);
}
}
}
}
// notify on unconfirmed transactions
for (final tx in unconfirmedTxnsToNotifyPending) {
if (tx.txType == "Received") {
unawaited(NotificationApi.showNotification(
title: "Incoming transaction",
body: walletName,
walletId: walletId,
iconAssetName: Assets.svg.iconFor(coin: coin),
date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS,
coinName: coin.name,
txid: tx.txid,
confirmations: tx.confirmations,
requiredConfirmations: MINIMUM_CONFIRMATIONS,
));
await txTracker.addNotifiedPending(tx.txid);
} else if (tx.txType == "Sent") {
unawaited(NotificationApi.showNotification(
title: "Sending transaction",
body: walletName,
walletId: walletId,
iconAssetName: Assets.svg.iconFor(coin: coin),
date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS,
coinName: coin.name,
txid: tx.txid,
confirmations: tx.confirmations,
requiredConfirmations: MINIMUM_CONFIRMATIONS,
));
await txTracker.addNotifiedPending(tx.txid);
}
}
// notify on confirmed
for (final tx in unconfirmedTxnsToNotifyConfirmed) {
if (tx.txType == "Received") {
unawaited(NotificationApi.showNotification(
title: "Incoming transaction confirmed",
body: walletName,
walletId: walletId,
iconAssetName: Assets.svg.iconFor(coin: coin),
date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
shouldWatchForUpdates: false,
coinName: coin.name,
));
await txTracker.addNotifiedConfirmed(tx.txid);
} else if (tx.txType == "Sent") {
unawaited(NotificationApi.showNotification(
title: "Outgoing transaction confirmed",
body: walletName,
walletId: walletId,
iconAssetName: Assets.svg.iconFor(coin: coin),
date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
shouldWatchForUpdates: false,
coinName: coin.name,
));
await txTracker.addNotifiedConfirmed(tx.txid);
}
}
}
@override
@ -395,7 +565,7 @@ class EthereumWallet extends CoinServiceAPI {
refreshMutex = true;
}
final blockNumber = await _client.getBlockNumber();
// final blockNumber = await _client.getBlockNumber();
try {
GlobalEventBus.instance.fire(
@ -423,9 +593,60 @@ class EthereumWallet extends CoinServiceAPI {
unawaited(updateStoredChainHeight(newHeight: currentHeight));
}
final newTxData = await _fetchTransactionData();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
final newTxData = _fetchTransactionData();
GlobalEventBus.instance
.fire(RefreshPercentChangedEvent(0.50, walletId));
final feeObj = _getFees();
GlobalEventBus.instance
.fire(RefreshPercentChangedEvent(0.60, walletId));
_transactionData = Future(() => newTxData);
GlobalEventBus.instance
.fire(RefreshPercentChangedEvent(0.70, walletId));
_feeObject = Future(() => feeObj);
GlobalEventBus.instance
.fire(RefreshPercentChangedEvent(0.80, walletId));
final allTxsToWatch = getAllTxsToWatch(await newTxData);
await Future.wait([
newTxData,
feeObj,
/// TODO - GET fee object
allTxsToWatch,
]);
GlobalEventBus.instance
.fire(RefreshPercentChangedEvent(0.90, walletId));
}
refreshMutex = false;
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId));
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.synced,
walletId,
coin,
),
);
if (shouldAutoSync) {
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
Logging.instance.log(
"Periodic refresh check for $walletId $walletName in object instance: $hashCode",
level: LogLevel.Info);
// chain height check currently broken
// if ((await chainHeight) != (await storedChainHeight)) {
if (await refreshIfThereIsNewData()) {
await refresh();
GlobalEventBus.instance.fire(UpdatedInBackgroundEvent(
"New data found in $walletId $walletName in background!",
walletId));
}
// }
});
}
} catch (error, strace) {
refreshMutex = false;
@ -459,9 +680,27 @@ class EthereumWallet extends CoinServiceAPI {
}
@override
Future<bool> testNetworkConnection() {
// TODO: implement testNetworkConnection
throw UnimplementedError();
Future<bool> testNetworkConnection() async {
//TODO - LOOK for correct implementation of ping
try {
// final result = await _electrumXClient.ping();
// return result;
return true;
} catch (_) {
return false;
}
}
void _periodicPingCheck() async {
bool hasNetwork = await testNetworkConnection();
_isConnected = hasNetwork;
if (_isConnected != hasNetwork) {
NodeConnectionStatus status = hasNetwork
? NodeConnectionStatus.connected
: NodeConnectionStatus.disconnected;
GlobalEventBus.instance
.fire(NodeConnectionStatusChangedEvent(status, walletId, coin));
}
}
@override
@ -678,7 +917,24 @@ class EthereumWallet extends CoinServiceAPI {
String get walletId => _walletId;
late String _walletId;
@override
@override
set walletName(String newName) => _walletName = newName;
void stopNetworkAlivePinging() {
_networkAliveTimer?.cancel();
_networkAliveTimer = null;
}
void startNetworkAlivePinging() {
// call once on start right away
_periodicPingCheck();
// then periodically check
_networkAliveTimer = Timer.periodic(
Constants.networkAliveTimerDuration,
(_) async {
_periodicPingCheck();
},
);
}
}