Skip to content
Snippets Groups Projects
Commit 2d8a9e7c authored by vjrj's avatar vjrj
Browse files

Detect gva nodes giving a negative balance and retry with healthy nodes

parent 4508e3da
No related branches found
No related tags found
No related merge requests found
import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:tuple/tuple.dart';
import '../../../g1/api.dart';
import '../../../g1/transaction_parser.dart';
......@@ -8,7 +9,9 @@ import '../../ui/contacts_cache.dart';
import '../../ui/logger.dart';
import '../../ui/notification_controller.dart';
import 'contact.dart';
import 'node.dart';
import 'node_list_cubit.dart';
import 'node_type.dart';
import 'transaction.dart';
import 'transaction_balance_state.dart';
import 'transaction_type.dart';
......@@ -38,46 +41,67 @@ class TransactionsCubit extends HydratedCubit<TransactionsAndBalanceState> {
emit(state.copyWith(transactions: newTransactions, balance: newBalance));
}
Future<void> fetchTransactions(NodeListCubit cubit) async {
logger('Loading transactions --------------------');
final Map<String, dynamic>? txData =
await gvaHistoryAndBalance(SharedPreferencesHelper().getPubKey());
if (txData == null) {
logger('Failed to get transactions');
return;
}
final TransactionsAndBalanceState newState =
transactionsGvaParser(txData, state);
// Notify
//logger(
// 'Last received: ${lastReceived.toIso8601String()}, last received notification: ${lastReceivedNotification.toIso8601String()}, compared ${lastReceived.compareTo(lastReceivedNotification)}');
logger(
'Last received notification: ${newState.latestReceivedNotification.toIso8601String()})}');
logger(
'Last sent notification: ${newState.latestSentNotification.toIso8601String()})}');
emit(newState);
for (final Transaction tx in newState.transactions.reversed) {
if (tx.type == TransactionType.received &&
newState.latestReceivedNotification.isBefore(tx.time)) {
// Future
final Contact from = await ContactsCache().getContact(tx.from);
NotificationController.createNewNotification(
tx.time.millisecondsSinceEpoch.toString(),
amount: tx.amount / 100,
from: from.title);
emit(newState.copyWith(latestReceivedNotification: tx.time));
Future<void> fetchTransactions(NodeListCubit cubit, {int retries = 5}) async {
Tuple2<Map<String, dynamic>?, Node> txDataResult;
bool success = false;
for (int attempt = 0; attempt < retries; attempt++) {
txDataResult =
await gvaHistoryAndBalance(SharedPreferencesHelper().getPubKey());
final Node node = txDataResult.item2;
logger('Loading transactions using $node --------------------');
if (txDataResult.item1 == null) {
logger(
'Failed to get transactions, attempt ${attempt + 1} of $retries');
await Future<void>.delayed(const Duration(seconds: 1));
increaseNodeErrors(NodeType.gva, node);
continue;
}
final Map<String, dynamic> txData = txDataResult.item1!;
final TransactionsAndBalanceState newState =
transactionsGvaParser(txData, state);
if (newState.balance < 0) {
logger('Warning: Negative balance in node ${txDataResult.item2}');
increaseNodeErrors(NodeType.gva, node);
continue;
}
if (tx.type == TransactionType.sent &&
newState.latestSentNotification.isBefore(tx.time)) {
// Future
final Contact to = await ContactsCache().getContact(tx.from);
NotificationController.createNewNotification(
tx.time.millisecondsSinceEpoch.toString(),
amount: -tx.amount / 100,
to: to.title);
emit(newState.copyWith(latestSentNotification: tx.time));
success = true;
logger(
'Last received notification: ${newState.latestReceivedNotification.toIso8601String()})}');
logger(
'Last sent notification: ${newState.latestSentNotification.toIso8601String()})}');
emit(newState);
for (final Transaction tx in newState.transactions.reversed) {
if (tx.type == TransactionType.received &&
newState.latestReceivedNotification.isBefore(tx.time)) {
// Future
final Contact from = await ContactsCache().getContact(tx.from);
NotificationController.createNewNotification(
tx.time.millisecondsSinceEpoch.toString(),
amount: tx.amount / 100,
from: from.title);
emit(newState.copyWith(latestReceivedNotification: tx.time));
}
if (tx.type == TransactionType.sent &&
newState.latestSentNotification.isBefore(tx.time)) {
// Future
final Contact to = await ContactsCache().getContact(tx.from);
NotificationController.createNewNotification(
tx.time.millisecondsSinceEpoch.toString(),
amount: -tx.amount / 100,
to: to.title);
emit(newState.copyWith(latestSentNotification: tx.time));
}
}
}
if (!success) {
logger('Failed to get transactions after $retries attempts');
return;
}
}
@override
......
......@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:tuple/tuple.dart';
import 'package:universal_html/html.dart' show window;
import '../data/models/contact.dart';
......@@ -496,8 +497,7 @@ Future<http.Response> _requestWithRetry(
} catch (e) {
logger('Error trying ${node.url} $e');
if (!dontRecord) {
logger('Increasing node errors of ${node.url} (${node.errors})');
NodeManager().updateNode(type, node.copyWith(errors: node.errors + 1));
increaseNodeErrors(type, node);
}
continue;
}
......@@ -574,37 +574,36 @@ String proxyfyNode(String nodeUrl) {
return url;
}
Future<Map<String, dynamic>?> gvaHistoryAndBalance(String pubKey) async {
Future<Tuple2<Map<String, dynamic>?, Node>> gvaHistoryAndBalance(
String pubKey) async {
return gvaFunctionWrapper<Map<String, dynamic>>(
pubKey, (Gva gva) => gva.history(pubKey));
}
Future<double?> gvaBalance(String pubKey) async {
Future<Tuple2<double?, Node>> gvaBalance(String pubKey) async {
return gvaFunctionWrapper<double>(pubKey, (Gva gva) => gva.balance(pubKey));
}
Future<String?> gvaNick(String pubKey) async {
Future<Tuple2<String?, Node>> gvaNick(String pubKey) async {
return gvaFunctionWrapper<String>(
pubKey, (Gva gva) => gva.getUsername(pubKey));
}
Future<T?> gvaFunctionWrapper<T>(
Future<Tuple2<T?, Node>> gvaFunctionWrapper<T>(
String pubKey, Future<T?> Function(Gva) specificFunction) async {
final List<Node> nodes = _getBestGvaNodes();
for (int i = 0; i < nodes.length; i++) {
final Node node = nodes[i];
try {
final Gva gva = Gva(node: proxyfyNode(node.url));
logger('Trying use gva ${node.url}');
logger('Trying to use gva ${node.url}');
final T? result = await specificFunction(gva);
return result;
return Tuple2<T?, Node>(result, node);
} catch (e) {
// await Sentry.captureMessage(
// 'Error trying to use gva node ${node.url} $e');
logger('Error trying ${node.url} $e');
logger('Increasing node errors of ${node.url} (${node.errors})');
NodeManager()
.updateNode(NodeType.gva, node.copyWith(errors: node.errors + 1));
increaseNodeErrors(NodeType.gva, node);
continue;
}
}
......@@ -638,3 +637,8 @@ class NodeCheck {
final Duration latency;
final int currentBlock;
}
void increaseNodeErrors(NodeType type, Node node) {
logger('Increasing node errors of ${node.url} (${node.errors})');
NodeManager().updateNode(type, node.copyWith(errors: node.errors + 1));
}
......@@ -57,13 +57,9 @@ TransactionsAndBalanceState transactionsGvaParser(
Map<String, dynamic> txData, TransactionsAndBalanceState state) {
// Balance
final dynamic rawBalance = txData['balance'];
final double? amount;
if (rawBalance != null) {
final Map<String, dynamic> balance = rawBalance as Map<String, dynamic>;
amount = (balance['amount'] as int) / 1.0;
} else {
amount = 0;
}
final double amount = rawBalance != null
? ((rawBalance as Map<String, dynamic>)['amount'] as int) / 1.0
: 0;
// Transactions
final Map<String, dynamic> txsHistoryBc =
txData['txsHistoryBc'] as Map<String, dynamic>;
......
......@@ -34,7 +34,7 @@ class _TransactionsAndBalanceWidgetState
void initState() {
super.initState();
_transScrollController.addListener(_scrollListener);
// Remove in the future
// Remove in the future
transCubit = context.read<TransactionsCubit>();
nodeListCubit = context.read<NodeListCubit>();
transCubit.fetchTransactions(nodeListCubit);
......
......@@ -1337,6 +1337,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
tuple:
dependency: "direct main"
description:
name: tuple
sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
typed_data:
dependency: transitive
description:
......
......@@ -75,6 +75,7 @@ dependencies:
barcode_scan2: ^4.2.4
jsqr: ^0.1.4
web_browser_detect: ^2.0.3
tuple: ^2.0.1
dev_dependencies:
flutter_test:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment