mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
centralized electrum client management
This commit is contained in:
parent
a2f74ced8a
commit
622740a8c0
14 changed files with 327 additions and 901 deletions
|
@ -33,6 +33,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class DbVersionMigrator with WalletDB {
|
||||
|
@ -85,7 +87,7 @@ class DbVersionMigrator with WalletDB {
|
|||
useSSL: node.useSSL),
|
||||
prefs: prefs,
|
||||
failovers: failovers,
|
||||
coin: Coin.firo,
|
||||
cryptoCurrency: Firo(CryptoCurrencyNetwork.main),
|
||||
);
|
||||
|
||||
try {
|
||||
|
|
|
@ -11,9 +11,6 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:electrum_adapter/electrum_adapter.dart' as electrum_adapter;
|
||||
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||
import 'package:electrum_adapter/methods/specific/firo.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -22,41 +19,18 @@ import 'package:string_validator/string_validator.dart';
|
|||
|
||||
class CachedElectrumXClient {
|
||||
final ElectrumXClient electrumXClient;
|
||||
ElectrumClient electrumAdapterClient;
|
||||
final Future<ElectrumClient> Function() electrumAdapterUpdateCallback;
|
||||
|
||||
static const minCacheConfirms = 30;
|
||||
|
||||
CachedElectrumXClient({
|
||||
required this.electrumXClient,
|
||||
required this.electrumAdapterClient,
|
||||
required this.electrumAdapterUpdateCallback,
|
||||
});
|
||||
CachedElectrumXClient({required this.electrumXClient});
|
||||
|
||||
factory CachedElectrumXClient.from({
|
||||
required ElectrumXClient electrumXClient,
|
||||
required ElectrumClient electrumAdapterClient,
|
||||
required Future<ElectrumClient> Function() electrumAdapterUpdateCallback,
|
||||
}) =>
|
||||
CachedElectrumXClient(
|
||||
electrumXClient: electrumXClient,
|
||||
electrumAdapterClient: electrumAdapterClient,
|
||||
electrumAdapterUpdateCallback: electrumAdapterUpdateCallback,
|
||||
);
|
||||
|
||||
/// If the client is closed, use the callback to update it.
|
||||
_checkElectrumAdapterClient() async {
|
||||
if (electrumAdapterClient.peer.isClosed) {
|
||||
Logging.instance.log(
|
||||
"ElectrumAdapterClient is closed, reopening it...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
ElectrumClient? _electrumAdapterClient =
|
||||
await electrumAdapterUpdateCallback.call();
|
||||
electrumAdapterClient = _electrumAdapterClient;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getAnonymitySet({
|
||||
required String groupId,
|
||||
String blockhash = "",
|
||||
|
@ -80,12 +54,9 @@ class CachedElectrumXClient {
|
|||
set = Map<String, dynamic>.from(cachedSet);
|
||||
}
|
||||
|
||||
await _checkElectrumAdapterClient();
|
||||
|
||||
final newSet = await (electrumAdapterClient as FiroElectrumClient)
|
||||
.getLelantusAnonymitySet(
|
||||
final newSet = await electrumXClient.getLelantusAnonymitySet(
|
||||
groupId: groupId,
|
||||
blockHash: set["blockHash"] as String,
|
||||
blockhash: set["blockHash"] as String,
|
||||
);
|
||||
|
||||
// update set with new data
|
||||
|
@ -157,10 +128,7 @@ class CachedElectrumXClient {
|
|||
set = Map<String, dynamic>.from(cachedSet);
|
||||
}
|
||||
|
||||
await _checkElectrumAdapterClient();
|
||||
|
||||
final newSet = await (electrumAdapterClient as FiroElectrumClient)
|
||||
.getSparkAnonymitySet(
|
||||
final newSet = await electrumXClient.getSparkAnonymitySet(
|
||||
coinGroupId: groupId,
|
||||
startBlockHash: set["blockHash"] as String,
|
||||
);
|
||||
|
@ -218,10 +186,11 @@ class CachedElectrumXClient {
|
|||
|
||||
final cachedTx = box.get(txHash) as Map?;
|
||||
if (cachedTx == null) {
|
||||
await _checkElectrumAdapterClient();
|
||||
|
||||
final Map<String, dynamic> result =
|
||||
await electrumAdapterClient.getTransaction(txHash);
|
||||
await electrumXClient.getTransaction(
|
||||
txHash: txHash,
|
||||
verbose: verbose,
|
||||
);
|
||||
|
||||
result.remove("hex");
|
||||
result.remove("lelantusData");
|
||||
|
@ -263,10 +232,7 @@ class CachedElectrumXClient {
|
|||
cachedSerials.length - 100, // 100 being some arbitrary buffer
|
||||
);
|
||||
|
||||
await _checkElectrumAdapterClient();
|
||||
|
||||
final serials = await (electrumAdapterClient as FiroElectrumClient)
|
||||
.getLelantusUsedCoinSerials(
|
||||
final serials = await electrumXClient.getLelantusUsedCoinSerials(
|
||||
startNumber: startNumber,
|
||||
);
|
||||
|
||||
|
@ -314,22 +280,12 @@ class CachedElectrumXClient {
|
|||
cachedTags.length - 100, // 100 being some arbitrary buffer
|
||||
);
|
||||
|
||||
await _checkElectrumAdapterClient();
|
||||
|
||||
final tags =
|
||||
await (electrumAdapterClient as FiroElectrumClient).getUsedCoinsTags(
|
||||
final newTags = await electrumXClient.getSparkUsedCoinsTags(
|
||||
startNumber: startNumber,
|
||||
);
|
||||
|
||||
// final newSerials = List<String>.from(serials["serials"] as List)
|
||||
// .map((e) => !isHexadecimal(e) ? base64ToHex(e) : e)
|
||||
// .toSet();
|
||||
|
||||
// Convert the Map<String, dynamic> tags to a Set<Object?>.
|
||||
final newTags = (tags["tags"] as List).toSet();
|
||||
|
||||
// ensure we are getting some overlap so we know we are not missing any
|
||||
if (cachedTags.isNotEmpty && tags.isNotEmpty) {
|
||||
if (cachedTags.isNotEmpty && newTags.isNotEmpty) {
|
||||
assert(cachedTags.intersection(newTags).isNotEmpty);
|
||||
}
|
||||
|
||||
|
|
96
lib/electrumx_rpc/client_manager.dart
Normal file
96
lib/electrumx_rpc/client_manager.dart
Normal file
|
@ -0,0 +1,96 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
|
||||
class ClientManager {
|
||||
ClientManager._();
|
||||
static final ClientManager sharedInstance = ClientManager._();
|
||||
|
||||
final Map<String, ElectrumClient> _map = {};
|
||||
final Map<String, int> _heights = {};
|
||||
final Map<String, StreamSubscription<BlockHeader>> _subscriptions = {};
|
||||
final Map<String, Completer<int>> _heightCompleters = {};
|
||||
|
||||
String _keyHelper(CryptoCurrency cryptoCurrency) {
|
||||
return "${cryptoCurrency.runtimeType}_${cryptoCurrency.network.name}";
|
||||
}
|
||||
|
||||
final Finalizer<ClientManager> _finalizer = Finalizer((manager) async {
|
||||
await manager._kill();
|
||||
});
|
||||
|
||||
ElectrumClient? getClient({
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
}) =>
|
||||
_map[_keyHelper(cryptoCurrency)];
|
||||
|
||||
void addClient(
|
||||
ElectrumClient client, {
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
}) {
|
||||
final key = _keyHelper(cryptoCurrency);
|
||||
if (_map[key] != null) {
|
||||
throw Exception("ElectrumX Client for $key already exists.");
|
||||
} else {
|
||||
_map[key] = client;
|
||||
}
|
||||
|
||||
_heightCompleters[key] = Completer<int>();
|
||||
_subscriptions[key] = client.subscribeHeaders().listen((event) {
|
||||
_heights[key] = event.height;
|
||||
|
||||
if (!_heightCompleters[key]!.isCompleted) {
|
||||
_heightCompleters[key]!.complete(event.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> getChainHeightFor(CryptoCurrency cryptoCurrency) async {
|
||||
final key = _keyHelper(cryptoCurrency);
|
||||
|
||||
if (_map[key] == null) {
|
||||
throw Exception(
|
||||
"No managed ElectrumClient for $cryptoCurrency found.",
|
||||
);
|
||||
}
|
||||
if (_heightCompleters[key] == null) {
|
||||
throw Exception(
|
||||
"No managed _heightCompleters for $cryptoCurrency found.",
|
||||
);
|
||||
}
|
||||
|
||||
return _heights[key] ?? await _heightCompleters[key]!.future;
|
||||
}
|
||||
|
||||
Future<ElectrumClient?> remove({
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
}) async {
|
||||
final key = _keyHelper(cryptoCurrency);
|
||||
await _subscriptions[key]?.cancel();
|
||||
_subscriptions.remove(key);
|
||||
_heights.remove(key);
|
||||
_heightCompleters.remove(key);
|
||||
|
||||
return _map.remove(key);
|
||||
}
|
||||
|
||||
Future<void> closeAll() async {
|
||||
await _kill();
|
||||
_finalizer.detach(this);
|
||||
}
|
||||
|
||||
Future<void> _kill() async {
|
||||
for (final sub in _subscriptions.values) {
|
||||
await sub.cancel();
|
||||
}
|
||||
for (final client in _map.values) {
|
||||
await client.close();
|
||||
}
|
||||
|
||||
_heightCompleters.clear();
|
||||
_heights.clear();
|
||||
_subscriptions.clear();
|
||||
_map.clear();
|
||||
}
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
/// Manage chain height subscriptions for each coin.
|
||||
abstract class ChainHeightServiceManager {
|
||||
// A map of chain height services for each coin.
|
||||
static final Map<Coin, ChainHeightService> _services = {};
|
||||
// Map<Coin, ChainHeightService> get services => _services;
|
||||
|
||||
// Get the chain height service for a specific coin.
|
||||
static ChainHeightService? getService(Coin coin) {
|
||||
return _services[coin];
|
||||
}
|
||||
|
||||
// Add a chain height service for a specific coin.
|
||||
static void add(ChainHeightService service, Coin coin) {
|
||||
// Don't add a new service if one already exists.
|
||||
if (_services[coin] == null) {
|
||||
_services[coin] = service;
|
||||
} else {
|
||||
throw Exception("Chain height service for $coin already managed");
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a chain height service for a specific coin.
|
||||
static void remove(Coin coin) {
|
||||
_services.remove(coin);
|
||||
}
|
||||
|
||||
// Close all subscriptions and clean up resources.
|
||||
static Future<void> dispose() async {
|
||||
// Close each subscription.
|
||||
//
|
||||
// Create a list of keys to avoid concurrent modification during iteration
|
||||
var keys = List<Coin>.from(_services.keys);
|
||||
|
||||
// Iterate over the copy of the keys
|
||||
for (final coin in keys) {
|
||||
final ChainHeightService? service = getService(coin);
|
||||
await service?.cancelListen();
|
||||
remove(coin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A service to fetch and listen for chain height updates.
|
||||
///
|
||||
/// TODO: Add error handling and branching to handle various other scenarios.
|
||||
class ChainHeightService {
|
||||
// The electrum_adapter client to use for fetching chain height updates.
|
||||
ElectrumClient client;
|
||||
|
||||
// The subscription to listen for chain height updates.
|
||||
StreamSubscription<dynamic>? _subscription;
|
||||
|
||||
// Whether the service has started listening for updates.
|
||||
bool get started => _subscription != null;
|
||||
|
||||
// The current chain height.
|
||||
int? _height;
|
||||
int? get height => _height;
|
||||
|
||||
// Whether the service is currently reconnecting.
|
||||
bool _isReconnecting = false;
|
||||
|
||||
// The reconnect timer.
|
||||
Timer? _reconnectTimer;
|
||||
|
||||
// The reconnection timeout duration.
|
||||
static const Duration _connectionTimeout = Duration(seconds: 10);
|
||||
|
||||
ChainHeightService({required this.client});
|
||||
|
||||
/// Fetch the current chain height and start listening for updates.
|
||||
Future<int> fetchHeightAndStartListenForUpdates() async {
|
||||
// Don't start a new subscription if one already exists.
|
||||
if (_subscription != null) {
|
||||
throw Exception(
|
||||
"Attempted to start a chain height service where an existing"
|
||||
" subscription already exists!",
|
||||
);
|
||||
}
|
||||
|
||||
// A completer to wait for the current chain height to be fetched.
|
||||
final completer = Completer<int>();
|
||||
|
||||
// Fetch the current chain height.
|
||||
_subscription = client.subscribeHeaders().listen((BlockHeader event) {
|
||||
_height = event.height;
|
||||
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(_height);
|
||||
}
|
||||
});
|
||||
|
||||
_subscription?.onError((dynamic error) {
|
||||
_handleError(error);
|
||||
});
|
||||
|
||||
// Wait for the current chain height to be fetched.
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Handle an error from the subscription.
|
||||
void _handleError(dynamic error) {
|
||||
Logging.instance.log(
|
||||
"Error reconnecting for chain height: ${error.toString()}",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
|
||||
_subscription?.cancel();
|
||||
_subscription = null;
|
||||
_attemptReconnect();
|
||||
}
|
||||
|
||||
/// Attempt to reconnect to the electrum server.
|
||||
void _attemptReconnect() {
|
||||
// Avoid multiple reconnection attempts.
|
||||
if (_isReconnecting) return;
|
||||
_isReconnecting = true;
|
||||
|
||||
// Attempt to reconnect.
|
||||
unawaited(fetchHeightAndStartListenForUpdates().then((_) {
|
||||
_isReconnecting = false;
|
||||
}));
|
||||
|
||||
// Set a timer to on the reconnection attempt and clean up if it fails.
|
||||
_reconnectTimer?.cancel();
|
||||
_reconnectTimer = Timer(_connectionTimeout, () async {
|
||||
if (_subscription == null) {
|
||||
await _subscription?.cancel();
|
||||
_subscription = null; // Will also occur on an error via handleError.
|
||||
_reconnectTimer?.cancel();
|
||||
_reconnectTimer = null;
|
||||
_isReconnecting = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Stop listening for chain height updates.
|
||||
Future<void> cancelListen() async {
|
||||
await _subscription?.cancel();
|
||||
_subscription = null;
|
||||
_reconnectTimer?.cancel();
|
||||
}
|
||||
}
|
|
@ -20,16 +20,17 @@ import 'package:event_bus/event_bus.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_chain_height_service.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/rpc.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/client_manager.dart';
|
||||
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/tor_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/dogecoin.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
class WifiOnlyException implements Exception {}
|
||||
|
@ -65,6 +66,8 @@ class ElectrumXNode {
|
|||
}
|
||||
|
||||
class ElectrumXClient {
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
|
||||
String get host => _host;
|
||||
late String _host;
|
||||
|
||||
|
@ -74,14 +77,13 @@ class ElectrumXClient {
|
|||
bool get useSSL => _useSSL;
|
||||
late bool _useSSL;
|
||||
|
||||
JsonRPC? get rpcClient => _rpcClient;
|
||||
JsonRPC? _rpcClient;
|
||||
|
||||
StreamChannel<dynamic>? get electrumAdapterChannel => _electrumAdapterChannel;
|
||||
// StreamChannel<dynamic>? get electrumAdapterChannel => _electrumAdapterChannel;
|
||||
StreamChannel<dynamic>? _electrumAdapterChannel;
|
||||
|
||||
ElectrumClient? get electrumAdapterClient => _electrumAdapterClient;
|
||||
ElectrumClient? _electrumAdapterClient;
|
||||
ElectrumClient? getElectrumAdapter() =>
|
||||
ClientManager.sharedInstance.getClient(
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
);
|
||||
|
||||
late Prefs _prefs;
|
||||
late TorService _torService;
|
||||
|
@ -91,9 +93,6 @@ class ElectrumXClient {
|
|||
|
||||
final Duration connectionTimeoutForSpecialCaseJsonRPCClients;
|
||||
|
||||
Coin? get coin => _coin;
|
||||
late Coin? _coin;
|
||||
|
||||
// add finalizer to cancel stream subscription when all references to an
|
||||
// instance of ElectrumX becomes inaccessible
|
||||
static final Finalizer<ElectrumXClient> _finalizer = Finalizer(
|
||||
|
@ -114,7 +113,7 @@ class ElectrumXClient {
|
|||
required bool useSSL,
|
||||
required Prefs prefs,
|
||||
required List<ElectrumXNode> failovers,
|
||||
Coin? coin,
|
||||
required this.cryptoCurrency,
|
||||
this.connectionTimeoutForSpecialCaseJsonRPCClients =
|
||||
const Duration(seconds: 60),
|
||||
TorService? torService,
|
||||
|
@ -125,7 +124,6 @@ class ElectrumXClient {
|
|||
_host = host;
|
||||
_port = port;
|
||||
_useSSL = useSSL;
|
||||
_coin = coin;
|
||||
|
||||
final bus = globalEventBusForTesting ?? GlobalEventBus.instance;
|
||||
|
||||
|
@ -161,10 +159,12 @@ class ElectrumXClient {
|
|||
// setting to null should force the creation of a new json rpc client
|
||||
// on the next request sent through this electrumx instance
|
||||
_electrumAdapterChannel = null;
|
||||
_electrumAdapterClient = null;
|
||||
await (await ClientManager.sharedInstance
|
||||
.remove(cryptoCurrency: cryptoCurrency))
|
||||
?.close();
|
||||
|
||||
// Also close any chain height services that are currently open.
|
||||
await ChainHeightServiceManager.dispose();
|
||||
// await ChainHeightServiceManager.dispose();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ class ElectrumXClient {
|
|||
required ElectrumXNode node,
|
||||
required Prefs prefs,
|
||||
required List<ElectrumXNode> failovers,
|
||||
required Coin coin,
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
TorService? torService,
|
||||
EventBus? globalEventBusForTesting,
|
||||
}) {
|
||||
|
@ -185,7 +185,7 @@ class ElectrumXClient {
|
|||
torService: torService,
|
||||
failovers: failovers,
|
||||
globalEventBusForTesting: globalEventBusForTesting,
|
||||
coin: coin,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -197,7 +197,11 @@ class ElectrumXClient {
|
|||
return true;
|
||||
}
|
||||
|
||||
Future<void> checkElectrumAdapter() async {
|
||||
Future<void> closeAdapter() async {
|
||||
await getElectrumAdapter()?.close();
|
||||
}
|
||||
|
||||
Future<void> _checkElectrumAdapter() async {
|
||||
({InternetAddress host, int port})? proxyInfo;
|
||||
|
||||
// If we're supposed to use Tor...
|
||||
|
@ -223,75 +227,60 @@ class ElectrumXClient {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO [prio=med]: Add proxyInfo to StreamChannel (or add to wrapper).
|
||||
// if (_electrumAdapter!.proxyInfo != proxyInfo) {
|
||||
// _electrumAdapter!.proxyInfo = proxyInfo;
|
||||
// _electrumAdapter!.disconnect(
|
||||
// reason: "Tor proxyInfo does not match current info",
|
||||
// );
|
||||
// }
|
||||
|
||||
// If the current ElectrumAdapterClient is closed, create a new one.
|
||||
if (_electrumAdapterClient != null &&
|
||||
_electrumAdapterClient!.peer.isClosed) {
|
||||
if (getElectrumAdapter() != null && getElectrumAdapter()!.peer.isClosed) {
|
||||
_electrumAdapterChannel = null;
|
||||
_electrumAdapterClient = null;
|
||||
ClientManager.sharedInstance.remove(cryptoCurrency: cryptoCurrency);
|
||||
}
|
||||
|
||||
final String useHost;
|
||||
final int usePort;
|
||||
final bool useUseSSL;
|
||||
|
||||
if (currentFailoverIndex == -1) {
|
||||
_electrumAdapterChannel ??= await electrum_adapter.connect(
|
||||
host,
|
||||
port: port,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
aliveTimerDuration: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
acceptUnverified: true,
|
||||
useSSL: useSSL,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
if (_coin == Coin.firo || _coin == Coin.firoTestNet) {
|
||||
_electrumAdapterClient ??= FiroElectrumClient(
|
||||
_electrumAdapterChannel!,
|
||||
host,
|
||||
port,
|
||||
useSSL,
|
||||
proxyInfo,
|
||||
);
|
||||
} else {
|
||||
_electrumAdapterClient ??= ElectrumClient(
|
||||
_electrumAdapterChannel!,
|
||||
host,
|
||||
port,
|
||||
useSSL,
|
||||
proxyInfo,
|
||||
);
|
||||
}
|
||||
useHost = host;
|
||||
usePort = port;
|
||||
useUseSSL = useSSL;
|
||||
} else {
|
||||
_electrumAdapterChannel ??= await electrum_adapter.connect(
|
||||
failovers![currentFailoverIndex].address,
|
||||
port: failovers![currentFailoverIndex].port,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
aliveTimerDuration: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
acceptUnverified: true,
|
||||
useSSL: failovers![currentFailoverIndex].useSSL,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
if (_coin == Coin.firo || _coin == Coin.firoTestNet) {
|
||||
_electrumAdapterClient ??= FiroElectrumClient(
|
||||
useHost = failovers![currentFailoverIndex].address;
|
||||
usePort = failovers![currentFailoverIndex].port;
|
||||
useUseSSL = failovers![currentFailoverIndex].useSSL;
|
||||
}
|
||||
|
||||
_electrumAdapterChannel ??= await electrum_adapter.connect(
|
||||
useHost,
|
||||
port: usePort,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
aliveTimerDuration: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
acceptUnverified: true,
|
||||
useSSL: useUseSSL,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
|
||||
if (getElectrumAdapter() == null) {
|
||||
final ElectrumClient newClient;
|
||||
if (cryptoCurrency is Firo) {
|
||||
newClient = FiroElectrumClient(
|
||||
_electrumAdapterChannel!,
|
||||
failovers![currentFailoverIndex].address,
|
||||
failovers![currentFailoverIndex].port,
|
||||
failovers![currentFailoverIndex].useSSL,
|
||||
useHost,
|
||||
usePort,
|
||||
useUseSSL,
|
||||
proxyInfo,
|
||||
);
|
||||
} else {
|
||||
_electrumAdapterClient ??= ElectrumClient(
|
||||
newClient = ElectrumClient(
|
||||
_electrumAdapterChannel!,
|
||||
failovers![currentFailoverIndex].address,
|
||||
failovers![currentFailoverIndex].port,
|
||||
failovers![currentFailoverIndex].useSSL,
|
||||
useHost,
|
||||
usePort,
|
||||
useUseSSL,
|
||||
proxyInfo,
|
||||
);
|
||||
}
|
||||
|
||||
ClientManager.sharedInstance.addClient(
|
||||
newClient,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -311,13 +300,13 @@ class ElectrumXClient {
|
|||
|
||||
if (_requireMutex) {
|
||||
await _torConnectingLock
|
||||
.protect(() async => await checkElectrumAdapter());
|
||||
.protect(() async => await _checkElectrumAdapter());
|
||||
} else {
|
||||
await checkElectrumAdapter();
|
||||
await _checkElectrumAdapter();
|
||||
}
|
||||
|
||||
try {
|
||||
final response = await _electrumAdapterClient!.request(
|
||||
final response = await getElectrumAdapter()!.request(
|
||||
command,
|
||||
args,
|
||||
);
|
||||
|
@ -397,16 +386,16 @@ class ElectrumXClient {
|
|||
|
||||
if (_requireMutex) {
|
||||
await _torConnectingLock
|
||||
.protect(() async => await checkElectrumAdapter());
|
||||
.protect(() async => await _checkElectrumAdapter());
|
||||
} else {
|
||||
await checkElectrumAdapter();
|
||||
await _checkElectrumAdapter();
|
||||
}
|
||||
|
||||
try {
|
||||
var futures = <Future<dynamic>>[];
|
||||
_electrumAdapterClient!.peer.withBatch(() {
|
||||
getElectrumAdapter()!.peer.withBatch(() {
|
||||
for (final arg in args) {
|
||||
futures.add(_electrumAdapterClient!.request(command, arg));
|
||||
futures.add(getElectrumAdapter()!.request(command, arg));
|
||||
}
|
||||
});
|
||||
final response = await Future.wait(futures);
|
||||
|
@ -778,8 +767,8 @@ class ElectrumXClient {
|
|||
}) async {
|
||||
Logging.instance.log("attempting to fetch blockchain.transaction.get...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
dynamic response = await _electrumAdapterClient!.getTransaction(txHash);
|
||||
await _checkElectrumAdapter();
|
||||
dynamic response = await getElectrumAdapter()!.getTransaction(txHash);
|
||||
Logging.instance.log("Fetching blockchain.transaction.get finished",
|
||||
level: LogLevel.Info);
|
||||
|
||||
|
@ -811,9 +800,9 @@ class ElectrumXClient {
|
|||
}) async {
|
||||
Logging.instance.log("attempting to fetch lelantus.getanonymityset...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
await _checkElectrumAdapter();
|
||||
Map<String, dynamic> response =
|
||||
await (_electrumAdapterClient as FiroElectrumClient)!
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getLelantusAnonymitySet(groupId: groupId, blockHash: blockhash);
|
||||
Logging.instance.log("Fetching lelantus.getanonymityset finished",
|
||||
level: LogLevel.Info);
|
||||
|
@ -830,8 +819,8 @@ class ElectrumXClient {
|
|||
}) async {
|
||||
Logging.instance.log("attempting to fetch lelantus.getmintmetadata...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
dynamic response = await (_electrumAdapterClient as FiroElectrumClient)!
|
||||
await _checkElectrumAdapter();
|
||||
dynamic response = await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getLelantusMintData(mints: mints);
|
||||
Logging.instance.log("Fetching lelantus.getmintmetadata finished",
|
||||
level: LogLevel.Info);
|
||||
|
@ -846,13 +835,13 @@ class ElectrumXClient {
|
|||
}) async {
|
||||
Logging.instance.log("attempting to fetch lelantus.getusedcoinserials...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
await _checkElectrumAdapter();
|
||||
|
||||
int retryCount = 3;
|
||||
dynamic response;
|
||||
|
||||
while (retryCount > 0 && response is! List) {
|
||||
response = await (_electrumAdapterClient as FiroElectrumClient)!
|
||||
response = await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getLelantusUsedCoinSerials(startNumber: startNumber);
|
||||
// TODO add 2 minute timeout.
|
||||
Logging.instance.log("Fetching lelantus.getusedcoinserials finished",
|
||||
|
@ -870,9 +859,9 @@ class ElectrumXClient {
|
|||
Future<int> getLelantusLatestCoinId({String? requestID}) async {
|
||||
Logging.instance.log("attempting to fetch lelantus.getlatestcoinid...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
await _checkElectrumAdapter();
|
||||
int response =
|
||||
await (_electrumAdapterClient as FiroElectrumClient).getLatestCoinId();
|
||||
await (getElectrumAdapter() as FiroElectrumClient).getLatestCoinId();
|
||||
Logging.instance.log("Fetching lelantus.getlatestcoinid finished",
|
||||
level: LogLevel.Info);
|
||||
return response;
|
||||
|
@ -901,9 +890,9 @@ class ElectrumXClient {
|
|||
try {
|
||||
Logging.instance.log("attempting to fetch spark.getsparkanonymityset...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
await _checkElectrumAdapter();
|
||||
Map<String, dynamic> response =
|
||||
await (_electrumAdapterClient as FiroElectrumClient)
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getSparkAnonymitySet(
|
||||
coinGroupId: coinGroupId, startBlockHash: startBlockHash);
|
||||
Logging.instance.log("Fetching spark.getsparkanonymityset finished",
|
||||
|
@ -924,11 +913,12 @@ class ElectrumXClient {
|
|||
// Use electrum_adapter package's getSparkUsedCoinsTags method.
|
||||
Logging.instance.log("attempting to fetch spark.getusedcoinstags...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
await _checkElectrumAdapter();
|
||||
Map<String, dynamic> response =
|
||||
await (_electrumAdapterClient as FiroElectrumClient)
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getUsedCoinsTags(startNumber: startNumber);
|
||||
// TODO: Add 2 minute timeout.
|
||||
// Why 2 minutes?
|
||||
Logging.instance.log("Fetching spark.getusedcoinstags finished",
|
||||
level: LogLevel.Info);
|
||||
final map = Map<String, dynamic>.from(response);
|
||||
|
@ -957,9 +947,9 @@ class ElectrumXClient {
|
|||
try {
|
||||
Logging.instance.log("attempting to fetch spark.getsparkmintmetadata...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
await _checkElectrumAdapter();
|
||||
List<dynamic> response =
|
||||
await (_electrumAdapterClient as FiroElectrumClient)
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getSparkMintMetaData(sparkCoinHashes: sparkCoinHashes);
|
||||
Logging.instance.log("Fetching spark.getsparkmintmetadata finished",
|
||||
level: LogLevel.Info);
|
||||
|
@ -979,8 +969,8 @@ class ElectrumXClient {
|
|||
try {
|
||||
Logging.instance.log("attempting to fetch spark.getsparklatestcoinid...",
|
||||
level: LogLevel.Info);
|
||||
await checkElectrumAdapter();
|
||||
int response = await (_electrumAdapterClient as FiroElectrumClient)
|
||||
await _checkElectrumAdapter();
|
||||
int response = await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getSparkLatestCoinId();
|
||||
Logging.instance.log("Fetching spark.getsparklatestcoinid finished",
|
||||
level: LogLevel.Info);
|
||||
|
@ -1001,8 +991,8 @@ class ElectrumXClient {
|
|||
/// "rate": 1000,
|
||||
/// }
|
||||
Future<Map<String, dynamic>> getFeeRate({String? requestID}) async {
|
||||
await checkElectrumAdapter();
|
||||
return await _electrumAdapterClient!.getFeeRate();
|
||||
await _checkElectrumAdapter();
|
||||
return await getElectrumAdapter()!.getFeeRate();
|
||||
}
|
||||
|
||||
/// Return the estimated transaction fee per kilobyte for a transaction to be confirmed within a certain number of [blocks].
|
||||
|
@ -1022,7 +1012,7 @@ class ElectrumXClient {
|
|||
try {
|
||||
// If the response is -1 or null, return a temporary hardcoded value for
|
||||
// Dogecoin. This is a temporary fix until the fee estimation is fixed.
|
||||
if (coin == Coin.dogecoin &&
|
||||
if (cryptoCurrency is Dogecoin &&
|
||||
(response == null ||
|
||||
response == -1 ||
|
||||
Decimal.parse(response.toString()) == Decimal.parse("-1"))) {
|
||||
|
@ -1035,7 +1025,7 @@ class ElectrumXClient {
|
|||
return Decimal.parse(response.toString());
|
||||
} catch (e, s) {
|
||||
final String msg = "Error parsing fee rate. Response: $response"
|
||||
"\nResult: ${response}\nError: $e\nStack trace: $s";
|
||||
"\nResult: $response\nError: $e\nStack trace: $s";
|
||||
Logging.instance.log(msg, level: LogLevel.Fatal);
|
||||
throw Exception(msg);
|
||||
}
|
||||
|
|
|
@ -1,413 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:stackwallet/exceptions/json_rpc/json_rpc_exception.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:tor_ffi_plugin/socks_socket.dart';
|
||||
|
||||
// Json RPC class to handle connecting to electrumx servers
|
||||
class JsonRPC {
|
||||
JsonRPC({
|
||||
required this.host,
|
||||
required this.port,
|
||||
this.useSSL = false,
|
||||
this.connectionTimeout = const Duration(seconds: 60),
|
||||
required ({InternetAddress host, int port})? proxyInfo,
|
||||
});
|
||||
final bool useSSL;
|
||||
final String host;
|
||||
final int port;
|
||||
final Duration connectionTimeout;
|
||||
({InternetAddress host, int port})? proxyInfo;
|
||||
|
||||
final _requestMutex = Mutex();
|
||||
final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue();
|
||||
Socket? _socket;
|
||||
SOCKSSocket? _socksSocket;
|
||||
StreamSubscription<List<int>>? _subscription;
|
||||
|
||||
void _dataHandler(List<int> data) {
|
||||
_requestQueue.nextIncompleteReq.then((req) {
|
||||
if (req != null) {
|
||||
req.appendDataAndCheckIfComplete(data);
|
||||
|
||||
if (req.isComplete) {
|
||||
_onReqCompleted(req);
|
||||
}
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"_dataHandler found a null req!",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _errorHandler(Object error, StackTrace trace) {
|
||||
_requestQueue.nextIncompleteReq.then((req) {
|
||||
if (req != null) {
|
||||
req.completer.completeError(error, trace);
|
||||
_onReqCompleted(req);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _doneHandler() {
|
||||
disconnect(reason: "JsonRPC _doneHandler() called");
|
||||
}
|
||||
|
||||
void _onReqCompleted(_JsonRPCRequest req) {
|
||||
_requestQueue.remove(req).then((_) {
|
||||
// attempt to send next request
|
||||
_sendNextAvailableRequest();
|
||||
});
|
||||
}
|
||||
|
||||
void _sendNextAvailableRequest() {
|
||||
_requestQueue.nextIncompleteReq.then((req) {
|
||||
if (req != null) {
|
||||
if (!Prefs.instance.useTor) {
|
||||
if (_socket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC _sendNextAvailableRequest attempted with"
|
||||
" _socket=null on $host:$port",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
// \r\n required by electrumx server
|
||||
_socket!.write('${req.jsonRequest}\r\n');
|
||||
} else {
|
||||
if (_socksSocket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC _sendNextAvailableRequest attempted with"
|
||||
" _socksSocket=null on $host:$port",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
// \r\n required by electrumx server
|
||||
_socksSocket?.write('${req.jsonRequest}\r\n');
|
||||
}
|
||||
|
||||
// TODO different timeout length?
|
||||
req.initiateTimeout(
|
||||
onTimedOut: () {
|
||||
_onReqCompleted(req);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<JsonRPCResponse> request(
|
||||
String jsonRpcRequest,
|
||||
Duration requestTimeout,
|
||||
) async {
|
||||
await _requestMutex.protect(() async {
|
||||
if (!Prefs.instance.useTor) {
|
||||
if (_socket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC request: opening socket $host:$port",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await _connect().timeout(requestTimeout, onTimeout: () {
|
||||
throw Exception("Request timeout: $jsonRpcRequest");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (_socksSocket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC request: opening SOCKS socket to $host:$port",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await _connect().timeout(requestTimeout, onTimeout: () {
|
||||
throw Exception("Request timeout: $jsonRpcRequest");
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final req = _JsonRPCRequest(
|
||||
jsonRequest: jsonRpcRequest,
|
||||
requestTimeout: requestTimeout,
|
||||
completer: Completer<JsonRPCResponse>(),
|
||||
);
|
||||
|
||||
final future = req.completer.future.onError(
|
||||
(error, stackTrace) async {
|
||||
await disconnect(
|
||||
reason: "return req.completer.future.onError: $error\n$stackTrace",
|
||||
);
|
||||
return JsonRPCResponse(
|
||||
exception: error is JsonRpcException
|
||||
? error
|
||||
: JsonRpcException(
|
||||
"req.completer.future.onError: $error\n$stackTrace",
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// if this is the only/first request then send it right away
|
||||
await _requestQueue.add(
|
||||
req,
|
||||
onInitialRequestAdded: _sendNextAvailableRequest,
|
||||
);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/// DO NOT set [ignoreMutex] to true unless fully aware of the consequences
|
||||
Future<void> disconnect({
|
||||
required String reason,
|
||||
bool ignoreMutex = false,
|
||||
}) async {
|
||||
if (ignoreMutex) {
|
||||
await _disconnectHelper(reason: reason);
|
||||
} else {
|
||||
await _requestMutex.protect(() async {
|
||||
await _disconnectHelper(reason: reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _disconnectHelper({required String reason}) async {
|
||||
await _subscription?.cancel();
|
||||
_subscription = null;
|
||||
_socket?.destroy();
|
||||
_socket = null;
|
||||
await _socksSocket?.close();
|
||||
_socksSocket = null;
|
||||
|
||||
// clean up remaining queue
|
||||
await _requestQueue.completeRemainingWithError(
|
||||
"JsonRPC disconnect() called with reason: \"$reason\"",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _connect() async {
|
||||
// ignore mutex is set to true here as _connect is already called within
|
||||
// the mutex.protect block. Setting to false here leads to a deadlock
|
||||
await disconnect(
|
||||
reason: "New connection requested",
|
||||
ignoreMutex: true,
|
||||
);
|
||||
|
||||
if (!Prefs.instance.useTor) {
|
||||
if (useSSL) {
|
||||
_socket = await SecureSocket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: connectionTimeout,
|
||||
onBadCertificate: (_) => true,
|
||||
); // TODO do not automatically trust bad certificates.
|
||||
} else {
|
||||
_socket = await Socket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: connectionTimeout,
|
||||
);
|
||||
}
|
||||
|
||||
_subscription = _socket!.listen(
|
||||
_dataHandler,
|
||||
onError: _errorHandler,
|
||||
onDone: _doneHandler,
|
||||
cancelOnError: true,
|
||||
);
|
||||
} else {
|
||||
if (proxyInfo == null) {
|
||||
throw JsonRpcException(
|
||||
"JsonRPC.connect failed with useTor=${Prefs.instance.useTor} and proxyInfo is null");
|
||||
}
|
||||
|
||||
// instantiate a socks socket at localhost and on the port selected by the tor service
|
||||
_socksSocket = await SOCKSSocket.create(
|
||||
proxyHost: proxyInfo!.host.address,
|
||||
proxyPort: proxyInfo!.port,
|
||||
sslEnabled: useSSL,
|
||||
);
|
||||
|
||||
try {
|
||||
Logging.instance.log(
|
||||
"JsonRPC.connect(): connecting to SOCKS socket at $proxyInfo (SSL $useSSL)...",
|
||||
level: LogLevel.Info);
|
||||
|
||||
await _socksSocket?.connect();
|
||||
|
||||
Logging.instance.log(
|
||||
"JsonRPC.connect(): connected to SOCKS socket at $proxyInfo...",
|
||||
level: LogLevel.Info);
|
||||
} catch (e) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC.connect(): failed to connect to SOCKS socket at $proxyInfo, $e",
|
||||
level: LogLevel.Error);
|
||||
throw JsonRpcException(
|
||||
"JsonRPC.connect(): failed to connect to SOCKS socket at $proxyInfo, $e");
|
||||
}
|
||||
|
||||
try {
|
||||
Logging.instance.log(
|
||||
"JsonRPC.connect(): connecting to $host:$port over SOCKS socket at $proxyInfo...",
|
||||
level: LogLevel.Info);
|
||||
|
||||
await _socksSocket?.connectTo(host, port);
|
||||
|
||||
Logging.instance.log(
|
||||
"JsonRPC.connect(): connected to $host:$port over SOCKS socket at $proxyInfo",
|
||||
level: LogLevel.Info);
|
||||
} catch (e) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC.connect(): failed to connect to $host over tor proxy at $proxyInfo, $e",
|
||||
level: LogLevel.Error);
|
||||
throw JsonRpcException(
|
||||
"JsonRPC.connect(): failed to connect to tor proxy, $e");
|
||||
}
|
||||
|
||||
_subscription = _socksSocket!.listen(
|
||||
_dataHandler,
|
||||
onError: _errorHandler,
|
||||
onDone: _doneHandler,
|
||||
cancelOnError: true,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
class _JsonRPCRequestQueue {
|
||||
final _lock = Mutex();
|
||||
final List<_JsonRPCRequest> _rq = [];
|
||||
|
||||
Future<void> add(
|
||||
_JsonRPCRequest req, {
|
||||
VoidCallback? onInitialRequestAdded,
|
||||
}) async {
|
||||
return await _lock.protect(() async {
|
||||
_rq.add(req);
|
||||
if (_rq.length == 1) {
|
||||
onInitialRequestAdded?.call();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> remove(_JsonRPCRequest req) async {
|
||||
return await _lock.protect(() async {
|
||||
final result = _rq.remove(req);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
Future<_JsonRPCRequest?> get nextIncompleteReq async {
|
||||
return await _lock.protect(() async {
|
||||
int removeCount = 0;
|
||||
_JsonRPCRequest? returnValue;
|
||||
for (final req in _rq) {
|
||||
if (req.isComplete) {
|
||||
removeCount++;
|
||||
} else {
|
||||
returnValue = req;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_rq.removeRange(0, removeCount);
|
||||
|
||||
return returnValue;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> completeRemainingWithError(
|
||||
String error, {
|
||||
StackTrace? stackTrace,
|
||||
}) async {
|
||||
await _lock.protect(() async {
|
||||
for (final req in _rq) {
|
||||
if (!req.isComplete) {
|
||||
req.completer.completeError(Exception(error), stackTrace);
|
||||
}
|
||||
}
|
||||
_rq.clear();
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> get isEmpty async {
|
||||
return await _lock.protect(() async {
|
||||
return _rq.isEmpty;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _JsonRPCRequest {
|
||||
// 0x0A is newline
|
||||
// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html
|
||||
static const int separatorByte = 0x0A;
|
||||
|
||||
final String jsonRequest;
|
||||
final Completer<JsonRPCResponse> completer;
|
||||
final Duration requestTimeout;
|
||||
final List<int> _responseData = [];
|
||||
|
||||
_JsonRPCRequest({
|
||||
required this.jsonRequest,
|
||||
required this.completer,
|
||||
required this.requestTimeout,
|
||||
});
|
||||
|
||||
void appendDataAndCheckIfComplete(List<int> data) {
|
||||
_responseData.addAll(data);
|
||||
if (data.last == separatorByte) {
|
||||
try {
|
||||
final response = json.decode(String.fromCharCodes(_responseData));
|
||||
completer.complete(JsonRPCResponse(data: response));
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC json.decode: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
completer.completeError(e, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void initiateTimeout({
|
||||
required VoidCallback onTimedOut,
|
||||
}) {
|
||||
Future<void>.delayed(requestTimeout).then((_) {
|
||||
if (!isComplete) {
|
||||
completer.complete(
|
||||
JsonRPCResponse(
|
||||
data: null,
|
||||
exception: JsonRpcException(
|
||||
"_JsonRPCRequest timed out: $jsonRequest",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
onTimedOut.call();
|
||||
});
|
||||
}
|
||||
|
||||
bool get isComplete => completer.isCompleted;
|
||||
}
|
||||
|
||||
class JsonRPCResponse {
|
||||
final dynamic data;
|
||||
final JsonRpcException? exception;
|
||||
|
||||
JsonRPCResponse({this.data, this.exception});
|
||||
}
|
|
@ -14,18 +14,20 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/connection_check/electrum_connection_check.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/test_epic_box_connection.dart';
|
||||
import 'package:stackwallet/utilities/test_eth_node_connection.dart';
|
||||
import 'package:stackwallet/utilities/test_monero_node_connection.dart';
|
||||
import 'package:stackwallet/utilities/test_stellar_node_connection.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
@ -173,17 +175,14 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
case Coin.bitcoincashTestnet:
|
||||
case Coin.firoTestNet:
|
||||
case Coin.dogecoinTestNet:
|
||||
final client = ElectrumXClient(
|
||||
host: formData.host!,
|
||||
port: formData.port!,
|
||||
useSSL: formData.useSSL!,
|
||||
failovers: [],
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
try {
|
||||
testPassed = await client.ping();
|
||||
testPassed = await checkElectrumServer(
|
||||
host: formData.host!,
|
||||
port: formData.port!,
|
||||
useSSL: formData.useSSL!,
|
||||
overridePrefs: ref.read(prefsChangeNotifierProvider),
|
||||
overrideTorService: ref.read(pTorService),
|
||||
);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
|
@ -191,14 +190,13 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
break;
|
||||
|
||||
case Coin.ethereum:
|
||||
// TODO fix this
|
||||
// final client = Web3Client(
|
||||
// "https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba",
|
||||
// Client());
|
||||
try {
|
||||
// await client.getSyncStatus();
|
||||
} catch (_) {}
|
||||
testPassed = await testEthNodeConnection(formData.host!);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
try {
|
||||
|
|
|
@ -13,13 +13,14 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/connection_check/electrum_connection_check.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
@ -150,17 +151,14 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
case Coin.eCash:
|
||||
case Coin.bitcoinFrost:
|
||||
case Coin.bitcoinFrostTestNet:
|
||||
final client = ElectrumXClient(
|
||||
host: node!.host,
|
||||
port: node.port,
|
||||
useSSL: node.useSSL,
|
||||
failovers: [],
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
try {
|
||||
testPassed = await client.ping();
|
||||
testPassed = await checkElectrumServer(
|
||||
host: node!.host,
|
||||
port: node.port,
|
||||
useSSL: node.useSSL,
|
||||
overridePrefs: ref.read(prefsChangeNotifierProvider),
|
||||
overrideTorService: ref.read(pTorService),
|
||||
);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@ class NotificationsService extends ChangeNotifier {
|
|||
node: eNode,
|
||||
failovers: failovers,
|
||||
prefs: prefs,
|
||||
coin: coin,
|
||||
cryptoCurrency: wallet.cryptoCurrency,
|
||||
);
|
||||
final tx = await client.getTransaction(txHash: txid);
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
|
||||
Future<bool> checkElectrumServer({
|
||||
required String host,
|
||||
required int port,
|
||||
required bool useSSL,
|
||||
Prefs? overridePrefs,
|
||||
TorService? overrideTorService,
|
||||
}) async {
|
||||
final _prefs = overridePrefs ?? Prefs.instance;
|
||||
final _torService = overrideTorService ?? TorService.sharedInstance;
|
||||
|
||||
({InternetAddress host, int port})? proxyInfo;
|
||||
|
||||
try {
|
||||
// If we're supposed to use Tor...
|
||||
if (_prefs.useTor) {
|
||||
// But Tor isn't running...
|
||||
if (_torService.status != TorConnectionStatus.connected) {
|
||||
// And the killswitch isn't set...
|
||||
if (!_prefs.torKillSwitch) {
|
||||
// Then we'll just proceed and connect to ElectrumX through clearnet at the bottom of this function.
|
||||
Logging.instance.log(
|
||||
"Tor preference set but Tor is not enabled, killswitch not set, connecting to Electrum adapter through clearnet",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
} else {
|
||||
// ... But if the killswitch is set, then we throw an exception.
|
||||
throw Exception(
|
||||
"Tor preference and killswitch set but Tor is not enabled, not connecting to Electrum adapter");
|
||||
// TODO [prio=low]: Try to start Tor.
|
||||
}
|
||||
} else {
|
||||
// Get the proxy info from the TorService.
|
||||
proxyInfo = _torService.getProxyInfo();
|
||||
}
|
||||
}
|
||||
|
||||
final client = await ElectrumClient.connect(
|
||||
host: host,
|
||||
port: port,
|
||||
useSSL: useSSL,
|
||||
proxyInfo: proxyInfo,
|
||||
).timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => throw Exception(
|
||||
"The checkElectrumServer connect() call timed out.",
|
||||
),
|
||||
);
|
||||
|
||||
await client.ping().timeout(const Duration(seconds: 5));
|
||||
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:electrum_adapter/electrum_adapter.dart' as electrum_adapter;
|
||||
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:frostdart/frostdart.dart' as frost;
|
||||
import 'package:frostdart/frostdart_bindings_generated.dart';
|
||||
|
@ -20,19 +18,15 @@ import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
|||
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/services/frost.dart';
|
||||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/extensions/extensions.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin_frost.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/private_key_currency.dart';
|
||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T> {
|
||||
BitcoinFrostWallet(CryptoCurrencyNetwork network)
|
||||
|
@ -44,8 +38,6 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T> {
|
|||
.findFirstSync()!;
|
||||
|
||||
late ElectrumXClient electrumXClient;
|
||||
late StreamChannel electrumAdapterChannel;
|
||||
late ElectrumClient electrumAdapterClient;
|
||||
late CachedElectrumXClient electrumXCachedClient;
|
||||
|
||||
Future<void> initializeNewFrost({
|
||||
|
@ -1102,66 +1094,29 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T> {
|
|||
|
||||
final newNode = await _getCurrentElectrumXNode();
|
||||
try {
|
||||
await electrumXClient.electrumAdapterClient?.close();
|
||||
} catch (e, s) {
|
||||
await electrumXClient.closeAdapter();
|
||||
} catch (e) {
|
||||
if (e.toString().contains("initialized")) {
|
||||
// Ignore. This should happen every first time the wallet is opened.
|
||||
} else {
|
||||
Logging.instance
|
||||
.log("Error closing electrumXClient: $e", level: LogLevel.Error);
|
||||
Logging.instance.log(
|
||||
"Error closing electrumXClient: $e",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
electrumXClient = ElectrumXClient.from(
|
||||
node: newNode,
|
||||
prefs: prefs,
|
||||
failovers: failovers,
|
||||
coin: cryptoCurrency.coin,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
);
|
||||
electrumAdapterChannel = await electrum_adapter.connect(
|
||||
newNode.address,
|
||||
port: newNode.port,
|
||||
acceptUnverified: true,
|
||||
useSSL: newNode.useSSL,
|
||||
proxyInfo: Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null,
|
||||
);
|
||||
if (electrumXClient.coin == Coin.firo ||
|
||||
electrumXClient.coin == Coin.firoTestNet) {
|
||||
electrumAdapterClient = FiroElectrumClient(
|
||||
electrumAdapterChannel,
|
||||
newNode.address,
|
||||
newNode.port,
|
||||
newNode.useSSL,
|
||||
Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null);
|
||||
} else {
|
||||
electrumAdapterClient = ElectrumClient(
|
||||
electrumAdapterChannel,
|
||||
newNode.address,
|
||||
newNode.port,
|
||||
newNode.useSSL,
|
||||
Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null);
|
||||
}
|
||||
|
||||
electrumXCachedClient = CachedElectrumXClient.from(
|
||||
electrumXClient: electrumXClient,
|
||||
electrumAdapterClient: electrumAdapterClient,
|
||||
electrumAdapterUpdateCallback: updateClient,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO [prio=low]: Use ElectrumXInterface method.
|
||||
Future<ElectrumClient> updateClient() async {
|
||||
Logging.instance.log(
|
||||
"Updating electrum node and ElectrumAdapterClient from Frost wallet.",
|
||||
level: LogLevel.Info);
|
||||
await updateNode();
|
||||
return electrumAdapterClient;
|
||||
}
|
||||
|
||||
bool _duplicateTxCheck(
|
||||
List<Map<String, dynamic>> allTransactions, String txid) {
|
||||
for (int i = 0; i < allTransactions.length; i++) {
|
||||
|
|
|
@ -3,11 +3,9 @@ import 'dart:math';
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
|
||||
import 'package:electrum_adapter/electrum_adapter.dart' as electrum_adapter;
|
||||
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_chain_height_service.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/client_manager.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
|
@ -15,7 +13,6 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2
|
|||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/models/signing_data.dart';
|
||||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
|
@ -23,22 +20,16 @@ import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
|||
import 'package:stackwallet/utilities/extensions/extensions.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/paynym_is_api.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
|
||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
||||
late ElectrumXClient electrumXClient;
|
||||
late StreamChannel<dynamic> electrumAdapterChannel;
|
||||
late ElectrumClient electrumAdapterClient;
|
||||
late CachedElectrumXClient electrumXCachedClient;
|
||||
// late SubscribableElectrumXClient subscribableElectrumXClient;
|
||||
late ChainHeightServiceManager chainHeightServiceManager;
|
||||
|
||||
int? get maximumFeerate => null;
|
||||
|
||||
|
@ -706,6 +697,12 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
cryptoCurrency.networkParams,
|
||||
);
|
||||
|
||||
print("=============================================================");
|
||||
print("$i ${txData.recipients![i].amount.decimal}");
|
||||
print("$i ${txData.recipients![i].amount.raw}");
|
||||
print("$address");
|
||||
print("=============================================================");
|
||||
|
||||
final output = coinlib.Output.fromAddress(
|
||||
txData.recipients![i].amount.raw,
|
||||
address,
|
||||
|
@ -785,24 +782,9 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
|
||||
Future<int> fetchChainHeight() async {
|
||||
try {
|
||||
// Get the chain height service for the current coin.
|
||||
ChainHeightService? service = ChainHeightServiceManager.getService(
|
||||
cryptoCurrency.coin,
|
||||
return await ClientManager.sharedInstance.getChainHeightFor(
|
||||
cryptoCurrency,
|
||||
);
|
||||
|
||||
// ... or create a new one if it doesn't exist.
|
||||
if (service == null) {
|
||||
service = ChainHeightService(client: electrumAdapterClient);
|
||||
ChainHeightServiceManager.add(service, cryptoCurrency.coin);
|
||||
}
|
||||
|
||||
// If the service hasn't been started, start it and fetch the chain height.
|
||||
if (!service.started) {
|
||||
return await service.fetchHeightAndStartListenForUpdates();
|
||||
}
|
||||
|
||||
// Return the height as per the service if available or the cached height.
|
||||
return service.height ?? info.cachedChainHeight;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown in fetchChainHeight\nError: $e\nStack trace: $s",
|
||||
|
@ -868,7 +850,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
|
||||
final newNode = await _getCurrentElectrumXNode();
|
||||
try {
|
||||
await electrumXClient.electrumAdapterClient?.close();
|
||||
await electrumXClient.closeAdapter();
|
||||
} catch (e) {
|
||||
if (e.toString().contains("initialized")) {
|
||||
// Ignore. This should happen every first time the wallet is opened.
|
||||
|
@ -881,50 +863,11 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
node: newNode,
|
||||
prefs: prefs,
|
||||
failovers: failovers,
|
||||
coin: cryptoCurrency.coin,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
);
|
||||
electrumAdapterChannel = await electrum_adapter.connect(
|
||||
newNode.address,
|
||||
port: newNode.port,
|
||||
acceptUnverified: true,
|
||||
useSSL: newNode.useSSL,
|
||||
proxyInfo: Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null,
|
||||
);
|
||||
if (electrumXClient.coin == Coin.firo ||
|
||||
electrumXClient.coin == Coin.firoTestNet) {
|
||||
electrumAdapterClient = FiroElectrumClient(
|
||||
electrumAdapterChannel,
|
||||
newNode.address,
|
||||
newNode.port,
|
||||
newNode.useSSL,
|
||||
Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null);
|
||||
} else {
|
||||
electrumAdapterClient = ElectrumClient(
|
||||
electrumAdapterChannel,
|
||||
newNode.address,
|
||||
newNode.port,
|
||||
newNode.useSSL,
|
||||
Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
: null);
|
||||
}
|
||||
electrumXCachedClient = CachedElectrumXClient.from(
|
||||
electrumXClient: electrumXClient,
|
||||
electrumAdapterClient: electrumAdapterClient,
|
||||
electrumAdapterUpdateCallback: updateClient,
|
||||
);
|
||||
// Replaced using electrum_adapters' SubscribableClient in fetchChainHeight.
|
||||
// subscribableElectrumXClient = SubscribableElectrumXClient.from(
|
||||
// node: newNode,
|
||||
// prefs: prefs,
|
||||
// failovers: failovers,
|
||||
// );
|
||||
// await subscribableElectrumXClient.connect(
|
||||
// host: newNode.address, port: newNode.port);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
@ -1219,13 +1162,6 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
await updateElectrumX();
|
||||
}
|
||||
|
||||
Future<ElectrumClient> updateClient() async {
|
||||
Logging.instance.log("Updating electrum node and ElectrumAdapterClient.",
|
||||
level: LogLevel.Info);
|
||||
await updateNode();
|
||||
return electrumAdapterClient;
|
||||
}
|
||||
|
||||
FeeObject? _cachedFees;
|
||||
|
||||
@override
|
||||
|
|
|
@ -13,15 +13,16 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart';
|
||||
import 'package:stackwallet/providers/global/active_wallet_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/connection_check/electrum_connection_check.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -171,17 +172,14 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
|||
case Coin.eCash:
|
||||
case Coin.bitcoinFrost:
|
||||
case Coin.bitcoinFrostTestNet:
|
||||
final client = ElectrumXClient(
|
||||
host: node.host,
|
||||
port: node.port,
|
||||
useSSL: node.useSSL,
|
||||
failovers: [],
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
coin: widget.coin,
|
||||
);
|
||||
|
||||
try {
|
||||
testPassed = await client.ping();
|
||||
testPassed = await checkElectrumServer(
|
||||
host: node.host,
|
||||
port: node.port,
|
||||
useSSL: node.useSSL,
|
||||
overridePrefs: ref.read(prefsChangeNotifierProvider),
|
||||
overrideTorService: ref.read(pTorService),
|
||||
);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||
|
@ -23,6 +22,7 @@ import 'package:stackwallet/providers/providers.dart';
|
|||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/connection_check/electrum_connection_check.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -153,18 +153,14 @@ class NodeOptionsSheet extends ConsumerWidget {
|
|||
case Coin.eCash:
|
||||
case Coin.bitcoinFrost:
|
||||
case Coin.bitcoinFrostTestNet:
|
||||
final client = ElectrumXClient(
|
||||
host: node.host,
|
||||
port: node.port,
|
||||
useSSL: node.useSSL,
|
||||
failovers: [],
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
torService: ref.read(pTorService),
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
try {
|
||||
testPassed = await client.ping();
|
||||
testPassed = await checkElectrumServer(
|
||||
host: node.host,
|
||||
port: node.port,
|
||||
useSSL: node.useSSL,
|
||||
overridePrefs: ref.read(prefsChangeNotifierProvider),
|
||||
overrideTorService: ref.read(pTorService),
|
||||
);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue