2023-05-26 21:21:16 +00:00
|
|
|
/*
|
|
|
|
* This file is part of Stack Wallet.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2023 Cypher Stack
|
|
|
|
* All Rights Reserved.
|
|
|
|
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
|
|
|
* Generated by Cypher Stack on 2023-05-26
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:http/http.dart';
|
2024-11-25 19:33:58 +00:00
|
|
|
|
2024-05-23 00:37:06 +00:00
|
|
|
import '../app_config.dart';
|
|
|
|
import '../db/hive/db.dart';
|
|
|
|
import '../models/node_model.dart';
|
|
|
|
import '../utilities/default_nodes.dart';
|
|
|
|
import '../utilities/flutter_secure_storage_interface.dart';
|
|
|
|
import '../utilities/logger.dart';
|
|
|
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
2022-08-26 08:11:35 +00:00
|
|
|
|
|
|
|
const kStackCommunityNodesEndpoint = "https://extras.stackwallet.com";
|
|
|
|
|
|
|
|
class NodeService extends ChangeNotifier {
|
2022-11-09 23:48:43 +00:00
|
|
|
final SecureStorageInterface secureStorageInterface;
|
2022-08-26 08:11:35 +00:00
|
|
|
|
|
|
|
/// Exposed [secureStorageInterface] in order to inject mock for tests
|
|
|
|
NodeService({
|
2022-11-09 22:43:26 +00:00
|
|
|
required this.secureStorageInterface,
|
2022-08-26 08:11:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Future<void> updateDefaults() async {
|
2024-05-22 19:38:49 +00:00
|
|
|
for (final defaultNode in AppConfig.coins.map(
|
2024-05-15 21:20:45 +00:00
|
|
|
(e) => e.defaultNode,
|
|
|
|
)) {
|
2022-08-26 08:11:35 +00:00
|
|
|
final savedNode = DB.instance
|
|
|
|
.get<NodeModel>(boxName: DB.boxNameNodeModels, key: defaultNode.id);
|
|
|
|
if (savedNode == null) {
|
2022-11-25 19:50:13 +00:00
|
|
|
// save the default node to hive only if no other nodes for the specific coin exist
|
2024-05-15 21:20:45 +00:00
|
|
|
if (getNodesFor(
|
2024-05-22 19:38:49 +00:00
|
|
|
AppConfig.getCryptoCurrencyByPrettyName(
|
2024-05-15 21:20:45 +00:00
|
|
|
defaultNode.coinName,
|
|
|
|
),
|
|
|
|
).isEmpty) {
|
2022-11-25 19:50:13 +00:00
|
|
|
await DB.instance.put<NodeModel>(
|
2022-08-26 08:11:35 +00:00
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: defaultNode.id,
|
2022-11-25 19:50:13 +00:00
|
|
|
value: defaultNode,
|
|
|
|
);
|
|
|
|
}
|
2022-08-26 08:11:35 +00:00
|
|
|
} else {
|
2023-01-06 22:57:46 +00:00
|
|
|
// update all fields but copy over previously set enabled and trusted states
|
2022-08-26 08:11:35 +00:00
|
|
|
await DB.instance.put<NodeModel>(
|
2024-05-15 21:20:45 +00:00
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: savedNode.id,
|
|
|
|
value: defaultNode.copyWith(
|
|
|
|
enabled: savedNode.enabled,
|
|
|
|
isFailover: savedNode.isFailover,
|
|
|
|
trusted: savedNode.trusted,
|
2024-11-26 00:29:58 +00:00
|
|
|
torEnabled: savedNode.torEnabled,
|
2024-11-26 15:18:35 +00:00
|
|
|
clearnetEnabled: savedNode.clearnetEnabled,
|
2024-05-15 21:20:45 +00:00
|
|
|
),
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
2022-10-20 17:01:25 +00:00
|
|
|
|
|
|
|
// check if a default node is the primary node for the crypto currency
|
|
|
|
// and update it if needed
|
2024-05-22 19:38:49 +00:00
|
|
|
final coin =
|
|
|
|
AppConfig.getCryptoCurrencyByPrettyName(defaultNode.coinName);
|
2024-05-15 21:20:45 +00:00
|
|
|
final primaryNode = getPrimaryNodeFor(currency: coin);
|
2022-10-20 17:01:25 +00:00
|
|
|
if (primaryNode != null && primaryNode.id == defaultNode.id) {
|
|
|
|
await setPrimaryNodeFor(
|
|
|
|
coin: coin,
|
|
|
|
node: defaultNode.copyWith(
|
|
|
|
enabled: primaryNode.enabled,
|
2023-01-06 23:10:17 +00:00
|
|
|
isFailover: primaryNode.isFailover,
|
2023-01-06 22:57:46 +00:00
|
|
|
trusted: primaryNode.trusted,
|
2024-11-26 00:29:58 +00:00
|
|
|
torEnabled: primaryNode.torEnabled,
|
2024-11-26 15:18:35 +00:00
|
|
|
clearnetEnabled: primaryNode.clearnetEnabled,
|
2022-10-20 17:01:25 +00:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setPrimaryNodeFor({
|
2024-05-15 21:20:45 +00:00
|
|
|
required CryptoCurrency coin,
|
2022-08-26 08:11:35 +00:00
|
|
|
required NodeModel node,
|
|
|
|
bool shouldNotifyListeners = false,
|
|
|
|
}) async {
|
|
|
|
await DB.instance.put<NodeModel>(
|
2024-05-15 21:20:45 +00:00
|
|
|
boxName: DB.boxNamePrimaryNodes,
|
|
|
|
key: coin.identifier,
|
|
|
|
value: node,
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
if (shouldNotifyListeners) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-15 21:20:45 +00:00
|
|
|
NodeModel? getPrimaryNodeFor({required CryptoCurrency currency}) {
|
|
|
|
return DB.instance.get<NodeModel>(
|
|
|
|
boxName: DB.boxNamePrimaryNodes,
|
|
|
|
key: currency.identifier,
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
List<NodeModel> get primaryNodes {
|
|
|
|
return DB.instance.values<NodeModel>(boxName: DB.boxNamePrimaryNodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<NodeModel> get nodes {
|
|
|
|
return DB.instance.values<NodeModel>(boxName: DB.boxNameNodeModels);
|
|
|
|
}
|
|
|
|
|
2024-05-15 21:20:45 +00:00
|
|
|
List<NodeModel> getNodesFor(CryptoCurrency coin) {
|
2022-08-26 08:11:35 +00:00
|
|
|
final list = DB.instance
|
|
|
|
.values<NodeModel>(boxName: DB.boxNameNodeModels)
|
2024-05-15 21:20:45 +00:00
|
|
|
.where(
|
|
|
|
(e) =>
|
|
|
|
e.coinName == coin.identifier &&
|
|
|
|
!e.id.startsWith(DefaultNodes.defaultNodeIdPrefix),
|
|
|
|
)
|
2022-08-26 08:11:35 +00:00
|
|
|
.toList();
|
|
|
|
|
|
|
|
// add default to end of list
|
2024-05-15 21:20:45 +00:00
|
|
|
list.addAll(
|
|
|
|
DB.instance
|
|
|
|
.values<NodeModel>(boxName: DB.boxNameNodeModels)
|
|
|
|
.where(
|
|
|
|
(e) =>
|
|
|
|
e.coinName == coin.identifier &&
|
|
|
|
e.id.startsWith(DefaultNodes.defaultNodeIdPrefix),
|
|
|
|
)
|
|
|
|
.toList(),
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
|
|
|
|
// return reversed list so default node appears at beginning
|
|
|
|
return list.reversed.toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
NodeModel? getNodeById({required String id}) {
|
|
|
|
return DB.instance.get<NodeModel>(boxName: DB.boxNameNodeModels, key: id);
|
|
|
|
}
|
|
|
|
|
2024-05-15 21:20:45 +00:00
|
|
|
List<NodeModel> failoverNodesFor({required CryptoCurrency currency}) {
|
|
|
|
return getNodesFor(currency)
|
|
|
|
.where((e) => e.isFailover && !e.isDown)
|
|
|
|
.toList();
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// should probably just combine this and edit into a save() func at some point
|
|
|
|
/// Over write node in hive if a node with existing id already exists.
|
|
|
|
/// Otherwise add node to hive
|
|
|
|
Future<void> add(
|
|
|
|
NodeModel node,
|
|
|
|
String? password,
|
|
|
|
bool shouldNotifyListeners,
|
|
|
|
) async {
|
|
|
|
await DB.instance.put<NodeModel>(
|
2024-05-15 21:20:45 +00:00
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: node.id,
|
|
|
|
value: node,
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
|
|
|
|
if (password != null) {
|
|
|
|
await secureStorageInterface.write(
|
2024-05-15 21:20:45 +00:00
|
|
|
key: "${node.id}_nodePW",
|
|
|
|
value: password,
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
if (shouldNotifyListeners) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> delete(String id, bool shouldNotifyListeners) async {
|
|
|
|
await DB.instance.delete<NodeModel>(boxName: DB.boxNameNodeModels, key: id);
|
|
|
|
|
|
|
|
await secureStorageInterface.delete(key: "${id}_nodePW");
|
|
|
|
if (shouldNotifyListeners) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setEnabledState(
|
|
|
|
String id,
|
|
|
|
bool enabled,
|
|
|
|
bool shouldNotifyListeners,
|
|
|
|
) async {
|
|
|
|
final model = DB.instance.get<NodeModel>(
|
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: id,
|
|
|
|
)!;
|
|
|
|
await DB.instance.put<NodeModel>(
|
2024-05-15 21:20:45 +00:00
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: model.id,
|
|
|
|
value: model.copyWith(enabled: enabled),
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
if (shouldNotifyListeners) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// convenience wrapper for add
|
|
|
|
Future<void> edit(
|
|
|
|
NodeModel editedNode,
|
|
|
|
String? password,
|
|
|
|
bool shouldNotifyListeners,
|
|
|
|
) async {
|
2023-01-06 21:49:27 +00:00
|
|
|
// check if the node being edited is the primary one; if it is, setPrimaryNodeFor coin
|
2024-05-22 19:38:49 +00:00
|
|
|
final coin = AppConfig.getCryptoCurrencyByPrettyName(editedNode.coinName);
|
2024-05-15 21:20:45 +00:00
|
|
|
final primaryNode = getPrimaryNodeFor(currency: coin);
|
2023-01-06 21:49:27 +00:00
|
|
|
if (primaryNode?.id == editedNode.id) {
|
|
|
|
await setPrimaryNodeFor(
|
|
|
|
coin: coin,
|
|
|
|
node: editedNode,
|
|
|
|
shouldNotifyListeners: true,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
return add(editedNode, password, shouldNotifyListeners);
|
|
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
Future<void> updateCommunityNodes() async {
|
|
|
|
final Client client = Client();
|
|
|
|
try {
|
|
|
|
final uri = Uri.parse("$kStackCommunityNodesEndpoint/getNodes");
|
|
|
|
final response = await client.post(
|
|
|
|
uri,
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
body: jsonEncode({
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"id": "0",
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
final result = jsonDecode(json['result'] as String);
|
|
|
|
final map = jsonDecode(result as String);
|
2022-08-26 18:47:47 +00:00
|
|
|
Logging.instance.log(map, level: LogLevel.Info);
|
2022-08-26 08:11:35 +00:00
|
|
|
|
2024-05-22 19:38:49 +00:00
|
|
|
for (final coin in AppConfig.coins) {
|
2022-08-26 08:11:35 +00:00
|
|
|
final nodeList = List<Map<String, dynamic>>.from(
|
2024-05-15 21:20:45 +00:00
|
|
|
map["nodes"][coin.identifier] as List? ?? [],
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
for (final nodeMap in nodeList) {
|
|
|
|
NodeModel node = NodeModel(
|
|
|
|
host: nodeMap["host"] as String,
|
|
|
|
port: nodeMap["port"] as int,
|
|
|
|
name: nodeMap["name"] as String,
|
|
|
|
id: nodeMap["id"] as String,
|
|
|
|
useSSL: nodeMap["useSSL"] == "true",
|
|
|
|
enabled: true,
|
2024-05-15 21:20:45 +00:00
|
|
|
coinName: coin.identifier,
|
2022-08-26 08:11:35 +00:00
|
|
|
isFailover: true,
|
2024-11-25 19:33:58 +00:00
|
|
|
torEnabled: nodeMap["torEnabled"] == "true",
|
2022-08-26 08:11:35 +00:00
|
|
|
isDown: nodeMap["isDown"] == "true",
|
2024-11-26 15:18:35 +00:00
|
|
|
clearnetEnabled: nodeMap["plainEnabled"] == "true",
|
2022-08-26 08:11:35 +00:00
|
|
|
);
|
|
|
|
final currentNode = getNodeById(id: nodeMap["id"] as String);
|
|
|
|
if (currentNode != null) {
|
|
|
|
node = currentNode.copyWith(
|
|
|
|
host: node.host,
|
|
|
|
port: node.port,
|
|
|
|
name: node.name,
|
|
|
|
useSSL: node.useSSL,
|
|
|
|
coinName: node.coinName,
|
|
|
|
isDown: node.isDown,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
await add(node, null, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance
|
|
|
|
.log("updateCommunityNodes() failed: $e\n$s", level: LogLevel.Error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|