Skip to content
Snippets Groups Projects
Commit b43a5fcd authored by poka's avatar poka
Browse files

feat: check indexer and node sync

parent 0e38099f
No related branches found
No related tags found
No related merge requests found
Pipeline #38189 waiting for manual action
const String getNameByAddressQ = r'''
const getNameByAddressQ = r'''
query ($address: String!) {
identityConnection(
where: { accountId: { _eq: $address } }
......@@ -14,7 +14,7 @@ query ($address: String!) {
}
''';
const String searchAddressByNameQ = r'''
const searchAddressByNameQ = r'''
query ($name: String!) {
identityConnection(
where: { name: { _ilike: $name } }
......@@ -30,7 +30,7 @@ query ($name: String!) {
}
''';
const String getHistoryByAddressRelayQ = r'''
const getHistoryByAddressRelayQ = r'''
query ($address: String!, $first: Int!, $after: String) {
transferConnection(
after: $after
......@@ -64,7 +64,7 @@ query ($address: String!, $first: Int!, $after: String) {
}
''';
const String getCertsReceived = r'''
const getCertsReceived = r'''
query ($address: String!) {
certConnection(
where: {receiver: {accountId: {_eq: $address}}}
......@@ -88,7 +88,7 @@ query ($address: String!) {
}
''';
const String getCertsSent = r'''
const getCertsSent = r'''
query ($address: String!) {
certConnection(
where: {issuer: {accountId: {_eq: $address}}}
......@@ -112,7 +112,7 @@ query ($address: String!) {
}
''';
const String isIdtyExistQ = r'''
const isIdtyExistQ = r'''
query ($name: String!) {
identityConnection(where: {name: {_eq: ""}}) {
edges {
......@@ -124,7 +124,7 @@ query ($name: String!) {
}
''';
const String getBlockchainStartQ = r'''
const getBlockchainStartQ = r'''
query {
blockConnection(first: 1) {
edges {
......@@ -137,7 +137,7 @@ query {
}
''';
const String subscribeHistoryIssuedQ = r'''
const subscribeHistoryIssuedQ = r'''
subscription ($address: String!) {
accountConnection(
where: {id: {_eq: $address}}
......@@ -155,3 +155,11 @@ subscription ($address: String!) {
}
}
''';
const getBlockByHash = r'''
query ($hash: bytea!) {
block(where: {hash: {_eq: $hash}}) {
height
}
}
''';
......@@ -8,6 +8,7 @@ import 'package:gecko/globals.dart';
import 'package:gecko/models/queries_indexer.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart';
class DuniterIndexer with ChangeNotifier {
Map<String, String?> walletNameIndexer = {};
......@@ -29,8 +30,7 @@ class DuniterIndexer with ChangeNotifier {
final client = HttpClient();
client.connectionTimeout = const Duration(milliseconds: 4000);
try {
final request =
await client.postUrl(Uri.parse('https://$endpoint/v1beta1/relay'));
final request = await client.postUrl(Uri.parse('https://$endpoint/v1beta1/relay'));
final response = await request.close();
if (response.statusCode != 200) {
log.w('Indexer $endpoint is offline');
......@@ -39,6 +39,12 @@ class DuniterIndexer with ChangeNotifier {
notifyListeners();
return false;
} else {
final isSynced = await isIndexerSynced('https://$endpoint/v1/graphql');
if (!isSynced) {
log.e('This endpoint is not synced, next');
return false;
}
indexerEndpoint = endpoint;
await configBox.put('indexerEndpoint', endpoint);
// await configBox.put('customEndpoint', endpoint);
......@@ -60,9 +66,7 @@ class DuniterIndexer with ChangeNotifier {
Future<String> getValidIndexerEndpoint() async {
// await configBox.delete('indexerEndpoint');
listIndexerEndpoints = await rootBundle
.loadString('config/indexer_endpoints.json')
.then((jsonStr) => jsonDecode(jsonStr));
listIndexerEndpoints = await rootBundle.loadString('config/indexer_endpoints.json').then((jsonStr) => jsonDecode(jsonStr));
// _listEndpoints.shuffle();
listIndexerEndpoints.add('Personnalisé');
......@@ -71,8 +75,7 @@ class DuniterIndexer with ChangeNotifier {
return configBox.get('customIndexer');
}
if (configBox.containsKey('indexerEndpoint') &&
listIndexerEndpoints.contains(configBox.get('indexerEndpoint'))) {
if (configBox.containsKey('indexerEndpoint') && listIndexerEndpoints.contains(configBox.get('indexerEndpoint'))) {
if (await checkIndexerEndpoint(configBox.get('indexerEndpoint'))) {
return configBox.get('indexerEndpoint');
}
......@@ -104,6 +107,15 @@ class DuniterIndexer with ChangeNotifier {
final request = await client.postUrl(Uri.parse(endpointPath));
final response = await request.close();
final isSynced = await isIndexerSynced('https://${listIndexerEndpoints[i]}/v1/graphql');
if (!isSynced) {
log.e('This endpoint is not synced, next');
statusCode = 40;
i++;
continue;
}
indexerEndpoint = listIndexerEndpoints[i];
await configBox.put('indexerEndpoint', listIndexerEndpoints[i]);
......@@ -131,6 +143,37 @@ class DuniterIndexer with ChangeNotifier {
return indexerEndpoint;
}
Future<bool> isIndexerSynced(String endpoint) async {
try {
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
var duniterFinilizedHash = await sub.getLastFinilizedHash();
final duniterFinilizedNumber = await sub.getBlockNumberByHash(duniterFinilizedHash);
duniterFinilizedHash = "\\x${duniterFinilizedHash.substring(2)}";
final indexerLink = HttpLink(endpoint);
final iClient = GraphQLClient(
cache: GraphQLCache(),
link: indexerLink,
);
final result = await iClient.query(QueryOptions(document: gql(getBlockByHash), variables: <String, dynamic>{'hash': duniterFinilizedHash}));
if (result.hasException || result.data == null || result.data!['block'].isEmpty) {
log.e('Indexer is not synced: ${result.exception} -- ${result.data}');
return false;
}
final indexerFinilizedNumber = result.data!['block'][0]['height'] as int;
if (duniterFinilizedNumber != indexerFinilizedNumber) {
log.e('Indexer is not synced');
return false;
}
return true;
} catch (e) {
log.e('An error occured while checking indexer sync: $e');
return false;
}
}
List parseHistory(List blockchainTX, String address) {
List transBC = [];
int i = 0;
......@@ -159,10 +202,8 @@ class DuniterIndexer with ChangeNotifier {
return transBC;
}
FetchMoreOptions? mergeQueryResult(QueryResult result, FetchMoreOptions? opts,
String address, int nRepositories) {
final List<dynamic> blockchainTX =
(result.data!['transferConnection']['edges'] as List<dynamic>);
FetchMoreOptions? mergeQueryResult(QueryResult result, FetchMoreOptions? opts, String address, int nRepositories) {
final List<dynamic> blockchainTX = (result.data!['transferConnection']['edges'] as List<dynamic>);
pageInfo = result.data!['transferConnection']['pageInfo'];
fetchMoreCursor = pageInfo!['endCursor'];
......@@ -174,10 +215,8 @@ class DuniterIndexer with ChangeNotifier {
variables: {'after': fetchMoreCursor, 'first': nRepositories},
updateQuery: (previousResultData, fetchMoreResultData) {
final List<dynamic> repos = [
...previousResultData!['transferConnection']['edges']
as List<dynamic>,
...fetchMoreResultData!['transferConnection']['edges']
as List<dynamic>
...previousResultData!['transferConnection']['edges'] as List<dynamic>,
...fetchMoreResultData!['transferConnection']['edges'] as List<dynamic>
];
fetchMoreResultData['transferConnection']['edges'] = repos;
......@@ -213,16 +252,14 @@ class DuniterIndexer with ChangeNotifier {
Future<DateTime> getBlockStart() async {
final result = await _execQuery(getBlockchainStartQ, {});
if (!result.hasException) {
startBlockchainTime = DateTime.parse(
result.data!['blockConnection']['edges'][0]['node']['timestamp']);
startBlockchainTime = DateTime.parse(result.data!['blockConnection']['edges'][0]['node']['timestamp']);
startBlockchainInitialized = true;
return startBlockchainTime;
}
return DateTime(0, 0, 0, 0, 0);
}
Future<QueryResult> _execQuery(
String query, Map<String, dynamic> variables) async {
Future<QueryResult> _execQuery(String query, Map<String, dynamic> variables) async {
final options = QueryOptions(document: gql(query), variables: variables);
// 5GMyvKsTNk9wDBy9jwKaX6mhSzmFFtpdK9KNnmrLoSTSuJHv
......@@ -249,12 +286,7 @@ class DuniterIndexer with ChangeNotifier {
late String finalAmount;
final DateTime date = repository[0];
final dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, {
1,
2,
7,
9
}.contains(date.month) ? 4 : 3)}";
final dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, {1, 2, 7, 9}.contains(date.month) ? 4 : 3)}";
DateTime normalizeDate(DateTime inputDate) {
return DateTime(inputDate.year, inputDate.month, inputDate.day);
......@@ -264,12 +296,9 @@ class DuniterIndexer with ChangeNotifier {
DateTime now = DateTime.now();
final transactionDate = normalizeDate(date.toLocal());
final todayDate = normalizeDate(now);
final yesterdayDate =
normalizeDate(now.subtract(const Duration(days: 1)));
final isSameWeek = weekNumber(transactionDate) == weekNumber(now) &&
transactionDate.year == now.year;
final isTodayOrYesterday =
transactionDate == todayDate || transactionDate == yesterdayDate;
final yesterdayDate = normalizeDate(now.subtract(const Duration(days: 1)));
final isSameWeek = weekNumber(transactionDate) == weekNumber(now) && transactionDate.year == now.year;
final isTodayOrYesterday = transactionDate == todayDate || transactionDate == yesterdayDate;
if (transactionDate == todayDate) {
return "today".tr();
......@@ -299,8 +328,7 @@ class DuniterIndexer with ChangeNotifier {
finalAmount = '$amount $currencyName';
}
bool isMigrationTime =
startBlockchainInitialized && date.compareTo(startBlockchainTime) < 0;
bool isMigrationTime = startBlockchainInitialized && date.compareTo(startBlockchainTime) < 0;
return {
'finalAmount': finalAmount,
......
......@@ -6,6 +6,8 @@ class SearchProvider with ChangeNotifier {
final searchController = TextEditingController();
List searchResult = [];
int resultLenght = 0;
bool canPasteAddress = false;
String pastedAddress = '';
void reload() {
notifyListeners();
......
This diff is collapsed.
......@@ -37,10 +37,8 @@ class _HomeScreenState extends State<HomeScreen> {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final homeProvider = Provider.of<HomeProvider>(context, listen: false);
final sub = Provider.of<SubstrateSdk>(context, listen: false);
final duniterIndexer =
Provider.of<DuniterIndexer>(context, listen: false);
final myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
final myWalletProvider = Provider.of<MyWalletsProvider>(context, listen: false);
final datapod = Provider.of<V2sDatapodProvider>(context, listen: false);
final bool isWalletsExists = myWalletProvider.isWalletsExists();
......@@ -77,35 +75,12 @@ class _HomeScreenState extends State<HomeScreen> {
homeProvider.isWalletBoxInit = true;
myWalletProvider.reload();
duniterIndexer.getValidIndexerEndpoint().then((validIndexerEndpoint) {
final wsLinkIndexer = WebSocketLink(
'wss://$validIndexerEndpoint/v1beta1/relay',
);
// const headerWebsocket =
// datapodEndpoint == '10.0.2.2:8080' ? 'ws' : 'wss';
// final wsLinkDatapod = WebSocketLink(
// '$headerWebsocket://$datapodEndpoint/v1/graphql',
// );
duniterIndexer.indexerClient = GraphQLClient(
cache: GraphQLCache(),
link: wsLinkIndexer,
);
// datapod.datapodClient = GraphQLClient(
// cache: GraphQLCache(),
// link: wsLinkDatapod,
// );
});
await homeProvider.getValidEndpoints();
if (configBox.get('isCacheChecked') == null) {
configBox.put('isCacheChecked', false);
}
Future<void> updateConnectionStatus(
List<ConnectivityResult> result) async {
Future<void> updateConnectionStatus(List<ConnectivityResult> result) async {
log.i('Network changed: $result');
if (result.contains(ConnectivityResult.none)) {
sub.nodeConnected = false;
......@@ -117,6 +92,22 @@ class _HomeScreenState extends State<HomeScreen> {
var connectivityResult = await (Connectivity().checkConnectivity());
if (!connectivityResult.contains(ConnectivityResult.none)) {
await sub.connectNode();
//Connect to Indexer
final validIndexerEndpoint = await duniterIndexer.getValidIndexerEndpoint();
final wsLinkIndexer = WebSocketLink(
'wss://$validIndexerEndpoint/v1beta1/relay',
);
duniterIndexer.indexerClient = GraphQLClient(
cache: GraphQLCache(),
link: wsLinkIndexer,
);
// Indexer Blockchain start
duniterIndexer.getBlockStart();
homeProvider.changeMessage("Node and indexer synced !".tr(), 5);
}
}
}
......@@ -135,9 +126,7 @@ class _HomeScreenState extends State<HomeScreen> {
Provider.of<ChestProvider>(context);
final isWalletsExists = myWalletProvider.isWalletsExists();
isTall = (MediaQuery.of(context).size.height /
MediaQuery.of(context).size.width) >
1.75;
isTall = (MediaQuery.of(context).size.height / MediaQuery.of(context).size.width) > 1.75;
return Scaffold(
resizeToAvoidBottomInset: false,
......@@ -158,8 +147,7 @@ Widget geckHome(context) {
fit: BoxFit.cover,
),
),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
Stack(children: <Widget>[
Positioned(
top: statusBarHeight + scaleSize(10),
......@@ -177,15 +165,12 @@ Widget geckHome(context) {
),
),
Align(
child: Image(
image: const AssetImage('assets/home/header.png'),
height: scaleSize(165)),
child: Image(image: const AssetImage('assets/home/header.png'), height: scaleSize(165)),
),
]),
Padding(
padding: const EdgeInsets.only(top: 15),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
DefaultTextStyle(
textAlign: TextAlign.center,
style: scaledTextStyle(
......@@ -246,8 +231,7 @@ Widget welcomeHome(context) {
fit: BoxFit.cover,
),
),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
Stack(children: <Widget>[
Positioned(
top: statusBarHeight + scaleSize(10),
......@@ -265,15 +249,12 @@ Widget welcomeHome(context) {
),
),
Align(
child: Image(
image: const AssetImage('assets/home/header.png'),
height: scaleSize(165)),
child: Image(image: const AssetImage('assets/home/header.png'), height: scaleSize(165)),
),
]),
Padding(
padding: const EdgeInsets.only(top: 1),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Text(
"fastAppDescription".tr(args: [currencyName]),
textAlign: TextAlign.center,
......@@ -321,8 +302,7 @@ Widget welcomeHome(context) {
Padding(
padding: EdgeInsets.only(top: scaleSize(55)),
child: Image(
image: const AssetImage(
'assets/home/gecko-bienvenue.png'),
image: const AssetImage('assets/home/gecko-bienvenue.png'),
height: scaleSize(180),
),
),
......@@ -356,10 +336,7 @@ Widget welcomeHome(context) {
},
child: Text(
'createWallet'.tr(),
style: scaledTextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white),
style: scaledTextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Colors.white),
),
),
),
......@@ -369,8 +346,7 @@ Widget welcomeHome(context) {
height: 60,
child: OutlinedButton(
key: keyRestoreChest,
style: OutlinedButton.styleFrom(
side: BorderSide(width: scaleSize(4), color: orangeC)),
style: OutlinedButton.styleFrom(side: BorderSide(width: scaleSize(4), color: orangeC)),
onPressed: () {
Navigator.push(
context,
......@@ -383,10 +359,7 @@ Widget welcomeHome(context) {
},
child: Text(
"restoreWallet".tr(),
style: scaledTextStyle(
fontSize: 20,
color: orangeC,
fontWeight: FontWeight.w600),
style: scaledTextStyle(fontSize: 20, color: orangeC, fontWeight: FontWeight.w600),
),
),
),
......
......@@ -2,16 +2,15 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:flutter/material.dart';
import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/search.dart';
import 'package:gecko/providers/wallets_profiles.dart';
import 'package:gecko/screens/my_contacts.dart';
import 'package:gecko/screens/search_result.dart';
import 'package:gecko/screens/wallet_view.dart';
import 'package:gecko/widgets/clipboard_monitor.dart';
import 'package:gecko/widgets/commons/offline_info.dart';
import 'package:gecko/widgets/commons/top_appbar.dart';
import 'package:provider/provider.dart';
......@@ -26,47 +25,13 @@ class SearchScreen extends StatefulWidget {
class _SearchScreenState extends State<SearchScreen> {
Timer? debounce;
final int debouneTime = 50;
bool canPasteAddress = false;
String pastedAddress = '';
Timer? clipboardPollingTimer;
void getClipBoard() {
final searchProvider = Provider.of<SearchProvider>(context, listen: false);
// Function to check clipboard and update if necessary
Future<void> checkAndUpdateClipboard() async {
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
if (clipboardData?.text?.isEmpty ?? true) return;
if (clipboardData!.text != pastedAddress) {
canPasteAddress = await isAddress(clipboardData.text!);
if (!canPasteAddress) return;
pastedAddress = clipboardData.text!;
searchProvider.reload();
}
}
// Check clipboard immediately
checkAndUpdateClipboard();
// Set up the periodic clipboard checking
clipboardPollingTimer =
Timer.periodic(const Duration(milliseconds: 500), (_) async {
await checkAndUpdateClipboard();
});
}
@override
void initState() {
getClipBoard();
ClipboardMonitor().startMonitoring();
super.initState();
}
@override
void dispose() {
clipboardPollingTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final searchProvider = Provider.of<SearchProvider>(context);
......@@ -139,13 +104,9 @@ class _SearchScreenState extends State<SearchScreen> {
height: scaleSize(10),
),
),
border: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.grey[500]!, width: 2),
borderRadius: BorderRadius.circular(8)),
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[500]!, width: 2), borderRadius: BorderRadius.circular(8)),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.grey[500]!, width: 2.5),
borderSide: BorderSide(color: Colors.grey[500]!, width: 2.5),
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.all(13),
......@@ -177,13 +138,12 @@ class _SearchScreenState extends State<SearchScreen> {
}),
);
}
: canPasteAddress
: searchProvider.canPasteAddress
? () async {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return WalletViewScreen(
address: pastedAddress, username: null);
return WalletViewScreen(address: searchProvider.pastedAddress, username: null);
}),
);
}
......@@ -191,14 +151,11 @@ class _SearchScreenState extends State<SearchScreen> {
child: Text(
canValidate
? 'search'.tr()
: canPasteAddress
: searchProvider.canPasteAddress
? 'pasteAddress'.tr()
: 'search'.tr(),
textAlign: TextAlign.center,
style: scaledTextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white),
style: scaledTextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.white),
),
),
),
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/providers/search.dart';
import 'package:gecko/providers/wallets_profiles.dart';
import 'package:provider/provider.dart';
class ClipboardMonitor extends ChangeNotifier {
String? _lastClipboardContent;
Timer? _debounceTimer;
final searchProvider = Provider.of<SearchProvider>(homeContext, listen: false);
void startMonitoring() {
_checkClipboard();
}
void _checkClipboard() async {
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
final newContent = clipboardData?.text;
if (newContent != null && newContent != _lastClipboardContent) {
_lastClipboardContent = newContent;
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(milliseconds: 300), () async {
if (await isAddress(newContent)) {
searchProvider.pastedAddress = newContent;
searchProvider.canPasteAddress = true;
searchProvider.reload();
} else {
searchProvider.pastedAddress = '';
searchProvider.canPasteAddress = false;
searchProvider.reload();
}
});
}
Future.delayed(const Duration(seconds: 1), _checkClipboard);
}
@override
void dispose() {
_debounceTimer?.cancel();
searchProvider.canPasteAddress = false;
super.dispose();
}
}
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