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

More work with node management

parent 7df8401c
No related branches found
No related tags found
No related merge requests found
......@@ -11,4 +11,4 @@ CARD_COLOR_TEXT=Ğ1 Wallet Dev
# The duniter nodes are only used at boot time, later it tries to calculate periodically the nodes
# that are available with the less latency
DUNITER_NODES=https://g1.duniter.fr https://g1.le-sou.org https://g1.cgeek.fr https://g1.monnaielibreoccitanie.org https://g1.duniter.fr https://g1.le-sou.org https://g1.cgeek.fr
CESIUM_PLUS_NODES=https://g1.data.le-sou.org https://g1.data.e-is.pro https://g1.data.presler.fr https://g1.data.mithril.re g1.data.adn.life
CESIUM_PLUS_NODES=https://g1.data.le-sou.org https://g1.data.e-is.pro https://g1.data.presler.fr https://g1.data.mithril.re https://g1.data.adn.life
......@@ -10,5 +10,5 @@ CARD_COLOR_TEXT=Ğ1 Wallet Cop
# The duniter nodes are only used at boot time, later it tries to calculate periodically the nodes
# that are available with the less latency
DUNITER_NODES=https://g1.duniter.fr https://g1.le-sou.org https://g1.cgeek.fr https://g1.monnaielibreoccitanie.org https://g1.duniter.fr https://g1.le-sou.org https://g1.cgeek.fr
CESIUM_PLUS_NODES=https://g1.data.le-sou.org https://g1.data.e-is.pro https://g1.data.presler.fr https://g1.data.mithril.re g1.data.adn.life
CESIUM_PLUS_NODES=https://g1.data.le-sou.org https://g1.data.e-is.pro https://g1.data.presler.fr https://g1.data.mithril.re https://g1.data.adn.life
......@@ -40,7 +40,6 @@
"card_validity": "Validity",
"card_validity_tooltip": "Please note that this wallet is only accessible while using this specific browser and device. If you delete or reset the browser, you will lose access to this wallet and the funds stored in it.",
"demo_desc": "Please refrain from using this with real transactions for now.",
"connected_to": "We are connected to node:",
"export_key": "Export your wallet",
"import_key": "Import your wallet",
"copy_your_key": "Copy your public key",
......@@ -57,9 +56,12 @@
"data_load_error": "Error loading data",
"add_contact": "Add contact",
"contact_added": "Contact added",
"current_nodes_length": "(of {nodes})",
"no_transactions": "You don't have any transaction yet",
"qr-scanner-title": "Scan the QR of someone",
"copy_contact_key": "Copy",
"nothing_found": "Nothing found"
"nothing_found": "Nothing found",
"using_nodes": "Using {nodes} nodes of {type}",
"using_nodes_first": "Faster: {node}",
"long_press_to_refresh": "Long press to refresh",
"technical_info_title": "Technical info"
}
......@@ -40,7 +40,6 @@
"card_validity": "Validez",
"card_validity_tooltip": "Tenga en cuenta que este monedero solo es accesible mientras utiliza este navegador y este dispositivo específico. Si borra o restablece el navegador, perderá el acceso a este monedero y los fondos almacenados en el.",
"demo_desc": "Por favor, no utilice esto aún para transacciones reales.",
"connected_to": "Estamos conectados al nodo:",
"export_key": "Exporta tu monedero",
"import_key": "Importa tu monedero",
"copy_your_key": "Copia tu clave pública",
......@@ -60,5 +59,9 @@
"no_transactions": "No tiene todavía ninguna transacción",
"qr-scanner-title": "Escanea el QR de alguien",
"copy_contact_key": "Copiar",
"nothing_found": "No se ha encontrado nada"
"nothing_found": "No se ha encontrado nada",
"using_nodes": "Usando {nodes} nodos de {type}",
"using_nodes_first": "El más rápido: {node}",
"long_press_to_refresh": "Mantén presionado para refrescar",
"technical_info_title": "Info técnica"
}
......@@ -8,7 +8,7 @@
"bottom_nav_trd": "Contacts",
"bottom_nav_frd": "Solde",
"bottom_nav_fifth": "Info",
"send_g1: "Envoyer des Ğ1",
"send_g1: "Envoyer" des Ğ1",
"g1_amount": "Montant à envoyer",
"g1_amount_hint": "Montant à envoyer en Ğ1",
"g1_form_pay_send": "Envoyer",
......@@ -40,7 +40,6 @@
"card_validity": "Validité",
"card_validity_tooltip": "Veuillez noter que ce portefeuille n'est accessible que lors de l'utilisation de ce navigateur et de cet appareil spécifiques. Si vous supprimez ou réinitialisez le navigateur, vous perdrez l'accès à ce portefeuille et aux fonds qu'il contient.",
"demo_desc": "Veuillez vous abstenir d'utiliser ceci avec de vraies transactions pour le moment.",
"connected_to": "Nous sommes connectés au nœud:",
"export_key": "Exporter votre portefeuille",
"import_key": "Importer votre portefeuille",
"copy_your_key": "Copier votre clé publique",
......@@ -53,5 +52,5 @@
"delete_contact": "Supprimer le contact",
"search_contacts": "Rechercher des contacts",
"no_contacts": "Vous n'avez pas encore de contacts",
"data_load_error": "Erreur de chargement des données"
"data_load_error": "Erreur de chargement des données"
}
......@@ -159,6 +159,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers() async {
final List<Node> lNodes = <Node>[];
// To compare with something...
String fastestNode = 'https://g1.duniter.org';
const NodeType type = NodeType.duniter;
late Duration fastestLatency = const Duration(minutes: 1);
try {
final Response response = await getPeers();
......@@ -186,7 +187,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers() async {
final String? endpoint = parseHost(endpointUnParsed);
if (endpoint != null) {
try {
final Duration latency = await _pingNode(endpoint);
final Duration latency = await _pingNode(endpoint, type);
logger(
'Evaluating node: $endpoint, latency ${latency.inMicroseconds}');
final Node node =
......@@ -197,11 +198,11 @@ Future<List<Node>> _fetchDuniterNodesFromPeers() async {
if (!kReleaseMode) {
logger('Node bloc: Current faster node $fastestNode');
}
NodeManager().insertNode(NodeType.duniter, node);
NodeManager().insertNode(type, node);
lNodes.insert(0, node);
} else {
// Not the faster
NodeManager().addNode(NodeType.duniter, node);
NodeManager().addNode(type, node);
lNodes.add(node);
}
} catch (e) {
......@@ -211,7 +212,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers() async {
}
}
if (lNodes.length >= NodeManager.maxNodes) {
logger('We have enought nodes for now');
logger('We have enough nodes for now');
break;
}
}
......@@ -219,8 +220,9 @@ Future<List<Node>> _fetchDuniterNodesFromPeers() async {
}
logger(
'Fetched ${lNodes.length} duniter nodes ordered by latency (first: ${lNodes.first.url})');
} catch (e) {
} catch (e, stacktrace) {
logger('General error in fetch duniter nodes: $e');
logger(stacktrace);
// rethrow;
}
lNodes.sort((Node a, Node b) => a.latency.compareTo(b.latency));
......@@ -234,13 +236,13 @@ Future<List<Node>> _fetchCesiumPlusNodes() async {
late Duration fastestLatency = const Duration(minutes: 1);
try {
const NodeType type = NodeType.cesiumPlus;
final List<Node> currentNodes = NodeManager().nodeList(type);
final List<Node> currentNodes = <Node>[...NodeManager().nodeList(type)];
currentNodes.shuffle();
for (final Node node in currentNodes) {
final String endpoint = node.url;
try {
final Duration latency = await _pingNode(endpoint);
final Duration latency = await _pingNode(endpoint, type);
logger('Evaluating node: $endpoint, latency ${latency.inMicroseconds}');
final Node node = Node(url: endpoint, latency: latency.inMicroseconds);
if (fastestNode == null || latency < fastestLatency) {
......@@ -263,20 +265,22 @@ Future<List<Node>> _fetchCesiumPlusNodes() async {
logger(
'Fetched ${lNodes.length} cesium plus nodes ordered by latency (first: ${lNodes.first.url})');
} catch (e) {
} catch (e, stacktrace) {
logger('General error in fetch cplus nodes: $e');
// rethrow;
logger(stacktrace);
}
lNodes.sort((Node a, Node b) => a.latency.compareTo(b.latency));
logger('First node in list ${lNodes.first.url}');
return lNodes;
}
Future<Duration> _pingNode(String node) async {
Future<Duration> _pingNode(String node, NodeType type) async {
try {
final Stopwatch stopwatch = Stopwatch()..start();
await http
.get(Uri.parse('$node/network/peers/self/ping'))
.get(Uri.parse(type == NodeType.duniter
? '$node/network/peers/self/ping'
: '$node/node/summary'))
// Decrease http timeout during ping
.timeout(const Duration(seconds: 10));
stopwatch.stop();
......
......@@ -205,28 +205,30 @@ class GinkgoApp extends StatefulWidget {
}
class _GinkgoAppState extends State<GinkgoApp> {
Future<void> _loadNodes(NodeListCubit cubit) async {
// Load nodes from /network/peers
NodeManager().loadFromCubit(cubit);
Future<void> _loadNodes() async {
_printNodeStatus();
await fetchDuniterNodes();
await fetchCesiumPlusNodes();
_printNodeStatus(prefix: 'Continuing');
}
void _printNodeStatus({String prefix = 'Starting'}) {
final int nDuniterNodes = NodeManager().nodeList(NodeType.duniter).length;
final int nCesiumPlusNodes =
NodeManager().nodeList(NodeType.cesiumPlus).length;
logger(
'Starting with $nDuniterNodes duniter nodes and $nCesiumPlusNodes c+ nodes');
await fetchDuniterNodes();
await fetchCesiumPlusNodes();
logger(
'Continue with $nDuniterNodes duniter nodes and $nCesiumPlusNodes c+ nodes');
'$prefix with $nDuniterNodes duniter nodes and $nCesiumPlusNodes c+ nodes');
}
@override
Widget build(BuildContext context) {
return BlocBuilder<NodeListCubit, NodeListState>(
builder: (BuildContext nodeContext, NodeListState state) {
NodeManager().loadFromCubit(nodeContext.read<NodeListCubit>());
Once.runHourly('load_nodes',
callback: () => _loadNodes(nodeContext.read<NodeListCubit>()),
callback: () => _loadNodes(),
fallback: () {
logger('Finished once load nodes');
_printNodeStatus(prefix: 'After once hourly having');
});
Once.runCustom('clear_errors', callback: () {
......
......@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../data/models/node.dart';
import '../../data/models/node_list_cubit.dart';
import '../../data/models/node_list_state.dart';
import '../../data/models/node_manager.dart';
import '../../g1/api.dart';
import '../../g1/export_import.dart';
import '../../main.dart';
......@@ -23,7 +24,6 @@ class FifthScreen extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<NodeListCubit, NodeListState>(
builder: (BuildContext nodeContext, NodeListState state) {
final List<Node> duniterNodes = state.duniterNodes;
return Material(
color: Theme.of(context).colorScheme.background,
child: ListView(
......@@ -31,24 +31,6 @@ class FifthScreen extends StatelessWidget {
physics: const BouncingScrollPhysics(),
children: <Widget>[
const Header(text: 'bottom_nav_fifth'),
GestureDetector(
onLongPress: () {
logger('On long press');
fetchDuniterNodes(force: true);
fetchCesiumPlusNodes(force: true);
},
child: InfoCard(
title: 'connected_to',
subtitle: duniterNodes.first.url.replaceFirst(':443', ''),
trailing: tr('current_nodes_length',
namedArgs: <String, String>{
'nodes': duniterNodes.length.toString()
}),
icon: Icons.hub)),
LinkCard(
title: 'code_card_title',
icon: Icons.code_rounded,
url: Uri.parse('https://git.duniter.org/vjrj/ginkgo')),
const TextDivider(text: 'key_tools_title'),
GridView.count(
physics: const NeverScrollableScrollPhysics(),
......@@ -82,9 +64,42 @@ class FifthScreen extends StatelessWidget {
onTap: () => copyPublicKeyToClipboard(context),
)
]),
const TextDivider(text: 'technical_info_title'),
_buildNodeInfo(NodeType.duniter, state.duniterNodes, context),
_buildNodeInfo(
NodeType.cesiumPlus, state.cesiumPlusNodes, context),
LinkCard(
title: 'code_card_title',
icon: Icons.code_rounded,
url: Uri.parse('https://git.duniter.org/vjrj/ginkgo')),
const BottomWidget()
]),
);
});
}
GestureDetector _buildNodeInfo(
NodeType type, List<Node> nodes, BuildContext context) {
return GestureDetector(
onTap: () => showTooltip(context, '', tr('long_press_to_refresh')),
onLongPress: () {
logger('On long press');
if (type == NodeType.duniter) {
fetchDuniterNodes(force: true);
} else {
fetchCesiumPlusNodes(force: true);
}
},
child: InfoCard(
title: tr('using_nodes', namedArgs: <String, String>{
'type': type.name,
'nodes': nodes.length.toString()
}),
translate: false,
subtitle: nodes.isNotEmpty
? tr('using_nodes_first',
namedArgs: <String, String>{'node': nodes.first.url})
: '',
icon: Icons.hub));
}
}
......@@ -44,9 +44,8 @@ class _SearchDialogState extends State<SearchDialog> {
final Response response = await searchUser(_searchTerm);
if (response.statusCode == 404) {
_results = <Contact>[];
_isLoading = false;
if (validateKey(_searchTerm)) {
// looks like a plain key
// looks like a plain pub key
final Contact contact = Contact(pubkey: _searchTerm);
_results.add(contact);
}
......@@ -62,6 +61,7 @@ class _SearchDialogState extends State<SearchDialog> {
logger('Contact retrieved in search $c');
return c;
}).toList();
logger('Found: ${_results.length}');
setState(() {
_isLoading = false;
});
......
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