2024-02-15 23:53:39 +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
|
|
|
|
// *
|
|
|
|
// */
|
|
|
|
//
|
|
|
|
// import 'dart:async';
|
|
|
|
// import 'dart:convert';
|
|
|
|
// import 'dart:io';
|
|
|
|
//
|
|
|
|
// import 'package:event_bus/event_bus.dart';
|
|
|
|
// import 'package:mutex/mutex.dart';
|
|
|
|
// import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
|
|
|
// import 'package:stackwallet/exceptions/json_rpc/json_rpc_exception.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/logger.dart';
|
|
|
|
// import 'package:stackwallet/utilities/prefs.dart';
|
|
|
|
// import 'package:tor_ffi_plugin/socks_socket.dart';
|
|
|
|
//
|
|
|
|
// class ElectrumXSubscription {
|
|
|
|
// final StreamController<dynamic> _controller =
|
|
|
|
// StreamController(); // TODO controller params
|
|
|
|
//
|
|
|
|
// Stream<dynamic> get responseStream => _controller.stream;
|
|
|
|
//
|
|
|
|
// void addToStream(dynamic data) => _controller.add(data);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// class SocketTask {
|
|
|
|
// SocketTask({this.completer, this.subscription});
|
|
|
|
//
|
|
|
|
// final Completer<dynamic>? completer;
|
|
|
|
// final ElectrumXSubscription? subscription;
|
|
|
|
//
|
|
|
|
// bool get isSubscription => subscription != null;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// class SubscribableElectrumXClient {
|
|
|
|
// int _currentRequestID = 0;
|
|
|
|
// bool _isConnected = false;
|
|
|
|
// List<int> _responseData = [];
|
|
|
|
// final Map<String, SocketTask> _tasks = {};
|
|
|
|
// Timer? _aliveTimer;
|
|
|
|
// Socket? _socket;
|
|
|
|
// SOCKSSocket? _socksSocket;
|
|
|
|
// late final bool _useSSL;
|
|
|
|
// late final Duration _connectionTimeout;
|
|
|
|
// late final Duration _keepAlive;
|
|
|
|
//
|
|
|
|
// bool get isConnected => _isConnected;
|
|
|
|
// bool get useSSL => _useSSL;
|
|
|
|
// // Used to reconnect.
|
|
|
|
// String? _host;
|
|
|
|
// int? _port;
|
|
|
|
//
|
|
|
|
// void Function(bool)? onConnectionStatusChanged;
|
|
|
|
//
|
|
|
|
// late Prefs _prefs;
|
|
|
|
// late TorService _torService;
|
|
|
|
// StreamSubscription<TorPreferenceChangedEvent>? _torPreferenceListener;
|
|
|
|
// StreamSubscription<TorConnectionStatusChangedEvent>? _torStatusListener;
|
|
|
|
// final Mutex _torConnectingLock = Mutex();
|
|
|
|
// bool _requireMutex = false;
|
|
|
|
//
|
|
|
|
// List<ElectrumXNode>? failovers;
|
|
|
|
// int currentFailoverIndex = -1;
|
|
|
|
//
|
|
|
|
// SubscribableElectrumXClient({
|
|
|
|
// required bool useSSL,
|
|
|
|
// required Prefs prefs,
|
|
|
|
// required List<ElectrumXNode> failovers,
|
|
|
|
// TorService? torService,
|
|
|
|
// this.onConnectionStatusChanged,
|
|
|
|
// Duration connectionTimeout = const Duration(seconds: 5),
|
|
|
|
// Duration keepAlive = const Duration(seconds: 10),
|
|
|
|
// EventBus? globalEventBusForTesting,
|
|
|
|
// }) {
|
|
|
|
// _useSSL = useSSL;
|
|
|
|
// _prefs = prefs;
|
|
|
|
// _torService = torService ?? TorService.sharedInstance;
|
|
|
|
// _connectionTimeout = connectionTimeout;
|
|
|
|
// _keepAlive = keepAlive;
|
|
|
|
//
|
|
|
|
// // If we're testing, use the global event bus for testing.
|
|
|
|
// final bus = globalEventBusForTesting ?? GlobalEventBus.instance;
|
|
|
|
//
|
|
|
|
// // Listen to global event bus for Tor status changes.
|
|
|
|
// _torStatusListener = bus.on<TorConnectionStatusChangedEvent>().listen(
|
|
|
|
// (event) async {
|
|
|
|
// try {
|
|
|
|
// switch (event.newStatus) {
|
|
|
|
// case TorConnectionStatus.connecting:
|
|
|
|
// // If Tor is connecting, we need to wait.
|
|
|
|
// await _torConnectingLock.acquire();
|
|
|
|
// _requireMutex = true;
|
|
|
|
// break;
|
|
|
|
//
|
|
|
|
// case TorConnectionStatus.connected:
|
|
|
|
// case TorConnectionStatus.disconnected:
|
|
|
|
// // If Tor is connected or disconnected, we can release the lock.
|
|
|
|
// if (_torConnectingLock.isLocked) {
|
|
|
|
// _torConnectingLock.release();
|
|
|
|
// }
|
|
|
|
// _requireMutex = false;
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
// } finally {
|
|
|
|
// // Ensure the lock is released.
|
|
|
|
// if (_torConnectingLock.isLocked) {
|
|
|
|
// _torConnectingLock.release();
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// // Listen to global event bus for Tor preference changes.
|
|
|
|
// _torPreferenceListener = bus.on<TorPreferenceChangedEvent>().listen(
|
|
|
|
// (event) async {
|
|
|
|
// // Close open socket (if open).
|
|
|
|
// final tempSocket = _socket;
|
|
|
|
// _socket = null;
|
|
|
|
// await tempSocket?.close();
|
|
|
|
//
|
|
|
|
// // Close open SOCKS socket (if open).
|
|
|
|
// final tempSOCKSSocket = _socksSocket;
|
|
|
|
// _socksSocket = null;
|
|
|
|
// await tempSOCKSSocket?.close();
|
|
|
|
//
|
|
|
|
// // Clear subscriptions.
|
|
|
|
// _tasks.clear();
|
|
|
|
//
|
|
|
|
// // Cancel alive timer
|
|
|
|
// _aliveTimer?.cancel();
|
|
|
|
// },
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// factory SubscribableElectrumXClient.from({
|
|
|
|
// required ElectrumXNode node,
|
|
|
|
// required Prefs prefs,
|
|
|
|
// required List<ElectrumXNode> failovers,
|
|
|
|
// TorService? torService,
|
|
|
|
// }) {
|
|
|
|
// return SubscribableElectrumXClient(
|
|
|
|
// useSSL: node.useSSL,
|
|
|
|
// prefs: prefs,
|
|
|
|
// failovers: failovers,
|
|
|
|
// torService: torService ?? TorService.sharedInstance,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Example for returning a future which completes upon connection.
|
|
|
|
// // static Future<SubscribableElectrumXClient> from({
|
|
|
|
// // required ElectrumXNode node,
|
|
|
|
// // TorService? torService,
|
|
|
|
// // }) async {
|
|
|
|
// // final client = SubscribableElectrumXClient(
|
|
|
|
// // useSSL: node.useSSL,
|
|
|
|
// // );
|
|
|
|
// //
|
|
|
|
// // await client.connect(host: node.address, port: node.port);
|
|
|
|
// //
|
|
|
|
// // return client;
|
|
|
|
// // }
|
|
|
|
//
|
|
|
|
// /// Check if the RPC client is connected and connect if needed.
|
|
|
|
// ///
|
|
|
|
// /// If Tor is enabled but not running, it will attempt to start Tor.
|
|
|
|
// Future<void> _checkSocket({bool connecting = false}) async {
|
|
|
|
// if (_prefs.useTor) {
|
|
|
|
// // If we're supposed to use Tor...
|
|
|
|
// if (_torService.status != TorConnectionStatus.connected) {
|
|
|
|
// // ... but Tor isn't running...
|
|
|
|
// if (!_prefs.torKillSwitch) {
|
|
|
|
// // ... and the killswitch isn't set, then we'll just return below.
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "Tor preference set but Tor is not enabled, killswitch not set, connecting to ElectrumX through clearnet.",
|
|
|
|
// level: LogLevel.Warning,
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// // ... but if the killswitch is set, then let's try to start Tor.
|
|
|
|
// await _torService.start();
|
|
|
|
// // TODO [prio=low]: Attempt to restart Tor if needed. Update Tor package for restart feature.
|
|
|
|
//
|
|
|
|
// // Double-check that Tor is running.
|
|
|
|
// if (_torService.status != TorConnectionStatus.connected) {
|
|
|
|
// // If Tor still isn't running, then we'll throw an exception.
|
|
|
|
// throw Exception("SubscribableElectrumXClient._checkRpcClient: "
|
|
|
|
// "Tor preference and killswitch set but Tor not enabled and could not start, not connecting to ElectrumX.");
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Connect if needed.
|
|
|
|
// if (!connecting) {
|
|
|
|
// if ((!_prefs.useTor && _socket == null) ||
|
|
|
|
// (_prefs.useTor && _socksSocket == null)) {
|
|
|
|
// if (currentFailoverIndex == -1) {
|
|
|
|
// // Check if we have cached node information
|
|
|
|
// if (_host == null && _port == null) {
|
|
|
|
// throw Exception("SubscribableElectrumXClient._checkRpcClient: "
|
|
|
|
// "No host or port provided and no cached node information.");
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Connect to the server.
|
|
|
|
// await connect(host: _host!, port: _port!);
|
|
|
|
// } else {
|
|
|
|
// // Attempt to connect to the next failover server.
|
|
|
|
// await connect(
|
|
|
|
// host: failovers![currentFailoverIndex].address,
|
|
|
|
// port: failovers![currentFailoverIndex].port,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Connect to the server.
|
|
|
|
// ///
|
|
|
|
// /// If Tor is enabled, it will attempt to connect through Tor.
|
|
|
|
// Future<void> connect({
|
|
|
|
// required String host,
|
|
|
|
// required int port,
|
|
|
|
// }) async {
|
|
|
|
// try {
|
|
|
|
// // Cache node information.
|
|
|
|
// _host = host;
|
|
|
|
// _port = port;
|
|
|
|
//
|
|
|
|
// // If we're already connected, disconnect first.
|
|
|
|
// try {
|
|
|
|
// await _socket?.close();
|
|
|
|
// } catch (_) {}
|
|
|
|
//
|
|
|
|
// // If we're connecting to Tor, wait.
|
|
|
|
// if (_requireMutex) {
|
|
|
|
// await _torConnectingLock
|
|
|
|
// .protect(() async => await _checkSocket(connecting: true));
|
|
|
|
// } else {
|
|
|
|
// await _checkSocket(connecting: true);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// if (!Prefs.instance.useTor) {
|
|
|
|
// // If we're not supposed to use Tor, then connect directly.
|
|
|
|
// await connectClearnet(host, port);
|
|
|
|
// } else {
|
|
|
|
// // If we're supposed to use Tor...
|
|
|
|
// if (_torService.status != TorConnectionStatus.connected) {
|
|
|
|
// // ... but Tor isn't running...
|
|
|
|
// if (!_prefs.torKillSwitch) {
|
|
|
|
// // ... and the killswitch isn't set, then we'll connect clearnet.
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "Tor preference set but Tor not enabled, no killswitch set, connecting to ElectrumX through clearnet",
|
|
|
|
// level: LogLevel.Warning,
|
|
|
|
// );
|
|
|
|
// await connectClearnet(host, port);
|
|
|
|
// } else {
|
|
|
|
// // ... but if the killswitch is set, then let's try to start Tor.
|
|
|
|
// await _torService.start();
|
|
|
|
// // TODO [prio=low]: Attempt to restart Tor if needed. Update Tor package for restart feature.
|
|
|
|
//
|
|
|
|
// // Doublecheck that Tor is running.
|
|
|
|
// if (_torService.status != TorConnectionStatus.connected) {
|
|
|
|
// // If Tor still isn't running, then we'll throw an exception.
|
|
|
|
// throw Exception(
|
|
|
|
// "Tor preference and killswitch set but Tor not enabled, not connecting to ElectrumX");
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Connect via Tor.
|
|
|
|
// await connectTor(host, port);
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// // Connect via Tor.
|
|
|
|
// await connectTor(host, port);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// _updateConnectionStatus(true);
|
|
|
|
//
|
|
|
|
// if (_prefs.useTor) {
|
|
|
|
// if (_socksSocket == null) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient.connect(): "
|
|
|
|
// "cannot listen to $host:$port via SOCKSSocket because it is not connected.";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw Exception(msg);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// _socksSocket!.listen(
|
|
|
|
// _dataHandler,
|
|
|
|
// onError: _errorHandler,
|
|
|
|
// onDone: _doneHandler,
|
|
|
|
// cancelOnError: true,
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// if (_socket == null) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient.connect(): "
|
|
|
|
// "cannot listen to $host:$port via socket because it is not connected.";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw Exception(msg);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// _socket!.listen(
|
|
|
|
// _dataHandler,
|
|
|
|
// onError: _errorHandler,
|
|
|
|
// onDone: _doneHandler,
|
|
|
|
// cancelOnError: true,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// _aliveTimer?.cancel();
|
|
|
|
// _aliveTimer = Timer.periodic(
|
|
|
|
// _keepAlive,
|
|
|
|
// (_) async => _updateConnectionStatus(await ping()),
|
|
|
|
// );
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final msg = "SubscribableElectrumXClient.connect: "
|
|
|
|
// "failed to connect to $host:$port."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
//
|
|
|
|
// // Ensure cleanup is performed on failure to avoid resource leaks.
|
|
|
|
// await disconnect(); // Use the disconnect method to clean up.
|
|
|
|
// rethrow; // Rethrow the exception to handle it further up the call stack.
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Connect to the server directly.
|
|
|
|
// Future<void> connectClearnet(String host, int port) async {
|
|
|
|
// try {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.connectClearnet(): "
|
|
|
|
// "creating a socket to $host:$port (SSL $useSSL)...",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
//
|
|
|
|
// 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,
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.connectClearnet(): "
|
|
|
|
// "created socket to $host:$port...",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient.connectClearnet: "
|
|
|
|
// "failed to connect to $host (SSL: $useSSL)."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw JsonRpcException(msg);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Connect to the server using the Tor service.
|
|
|
|
// Future<void> connectTor(String host, int port) async {
|
|
|
|
// // Get the proxy info from the TorService.
|
|
|
|
// final proxyInfo = _torService.getProxyInfo();
|
|
|
|
//
|
|
|
|
// try {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "creating a SOCKS socket at $proxyInfo (SSL $useSSL)...",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
//
|
|
|
|
// // Create a socks socket using the Tor service's proxy info.
|
|
|
|
// _socksSocket = await SOCKSSocket.create(
|
|
|
|
// proxyHost: proxyInfo.host.address,
|
|
|
|
// proxyPort: proxyInfo.port,
|
|
|
|
// sslEnabled: useSSL,
|
|
|
|
// );
|
|
|
|
//
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "created SOCKS socket at $proxyInfo...",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "failed to create a SOCKS socket at $proxyInfo (SSL $useSSL)..."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw JsonRpcException(msg);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// try {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "connecting to SOCKS socket at $proxyInfo (SSL $useSSL)...",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
//
|
|
|
|
// await _socksSocket?.connect();
|
|
|
|
//
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "connected to SOCKS socket at $proxyInfo...",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "failed to connect to SOCKS socket at $proxyInfo.."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw JsonRpcException(msg);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// try {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "connecting to $host:$port over SOCKS socket at $proxyInfo...",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
//
|
|
|
|
// await _socksSocket?.connectTo(host, port);
|
|
|
|
//
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "connected to $host:$port over SOCKS socket at $proxyInfo",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient.connectTor(): "
|
|
|
|
// "failed to connect $host over tor proxy at $proxyInfo."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw JsonRpcException(msg);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Disconnect from the server.
|
|
|
|
// Future<void> disconnect() async {
|
|
|
|
// _aliveTimer?.cancel();
|
|
|
|
// _aliveTimer = null;
|
|
|
|
//
|
|
|
|
// try {
|
|
|
|
// await _socket?.close();
|
|
|
|
// } catch (e, s) {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.disconnect: failed to close socket."
|
|
|
|
// "\nError: $e\nStack trace: $s",
|
|
|
|
// level: LogLevel.Warning);
|
|
|
|
// }
|
|
|
|
// _socket = null;
|
|
|
|
//
|
|
|
|
// try {
|
|
|
|
// await _socksSocket?.close();
|
|
|
|
// } catch (e, s) {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient.disconnect: failed to close SOCKS socket."
|
|
|
|
// "\nError: $e\nStack trace: $s",
|
|
|
|
// level: LogLevel.Warning);
|
|
|
|
// }
|
|
|
|
// _socksSocket = null;
|
|
|
|
//
|
|
|
|
// onConnectionStatusChanged = null;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Format JSON request string.
|
|
|
|
// String _buildJsonRequestString({
|
|
|
|
// required String method,
|
|
|
|
// required String id,
|
|
|
|
// required List<dynamic> params,
|
|
|
|
// }) {
|
|
|
|
// final paramString = jsonEncode(params);
|
|
|
|
// return '{"jsonrpc": "2.0", "id": "$id","method": "$method","params": $paramString}\r\n';
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Update the connection status and call the onConnectionStatusChanged callback if it exists.
|
|
|
|
// void _updateConnectionStatus(bool connectionStatus) {
|
|
|
|
// if (_isConnected != connectionStatus && onConnectionStatusChanged != null) {
|
|
|
|
// onConnectionStatusChanged!(connectionStatus);
|
|
|
|
// }
|
|
|
|
// _isConnected = connectionStatus;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Called when the socket has data.
|
|
|
|
// void _dataHandler(List<int> data) {
|
|
|
|
// _responseData.addAll(data);
|
|
|
|
//
|
|
|
|
// // 0x0A is newline
|
|
|
|
// // https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html
|
|
|
|
// if (data.last == 0x0A) {
|
|
|
|
// try {
|
|
|
|
// final response = jsonDecode(String.fromCharCodes(_responseData))
|
|
|
|
// as Map<String, dynamic>;
|
|
|
|
// _responseHandler(response);
|
|
|
|
// } catch (e, s) {
|
|
|
|
// Logging.instance
|
|
|
|
// .log("JsonRPC jsonDecode: $e\n$s", level: LogLevel.Error);
|
|
|
|
// rethrow;
|
|
|
|
// } finally {
|
|
|
|
// _responseData = [];
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Called when the socket has a response.
|
|
|
|
// void _responseHandler(Map<String, dynamic> response) {
|
|
|
|
// // subscriptions will have a method in the response
|
|
|
|
// if (response['method'] is String) {
|
|
|
|
// _subscriptionHandler(response: response);
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// final id = response['id'] as String;
|
|
|
|
// final result = response['result'];
|
|
|
|
//
|
|
|
|
// _complete(id, result);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Called when the subscription has a response.
|
|
|
|
// void _subscriptionHandler({
|
|
|
|
// required Map<String, dynamic> response,
|
|
|
|
// }) {
|
|
|
|
// final method = response['method'];
|
|
|
|
// switch (method) {
|
|
|
|
// case "blockchain.scripthash.subscribe":
|
|
|
|
// final params = response["params"] as List<dynamic>;
|
|
|
|
// final scripthash = params.first as String;
|
|
|
|
// final taskId = "blockchain.scripthash.subscribe:$scripthash";
|
|
|
|
//
|
|
|
|
// _tasks[taskId]?.subscription?.addToStream(params.last);
|
|
|
|
// break;
|
|
|
|
// case "blockchain.headers.subscribe":
|
|
|
|
// final params = response["params"];
|
|
|
|
// const taskId = "blockchain.headers.subscribe";
|
|
|
|
//
|
|
|
|
// _tasks[taskId]?.subscription?.addToStream(params.first);
|
|
|
|
// break;
|
|
|
|
// default:
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Called when the socket has an error.
|
|
|
|
// void _errorHandler(Object error, StackTrace trace) {
|
|
|
|
// _updateConnectionStatus(false);
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "SubscribableElectrumXClient called _errorHandler with: $error\n$trace",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Called when the socket is closed.
|
|
|
|
// void _doneHandler() {
|
|
|
|
// _updateConnectionStatus(false);
|
|
|
|
// Logging.instance.log("SubscribableElectrumXClient called _doneHandler",
|
|
|
|
// level: LogLevel.Info);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Complete a task with the given id and data.
|
|
|
|
// void _complete(String id, dynamic data) {
|
|
|
|
// if (_tasks[id] == null) {
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// if (!(_tasks[id]?.completer?.isCompleted ?? false)) {
|
|
|
|
// _tasks[id]?.completer?.complete(data);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// if (!(_tasks[id]?.isSubscription ?? false)) {
|
|
|
|
// _tasks.remove(id);
|
|
|
|
// } else {
|
|
|
|
// _tasks[id]?.subscription?.addToStream(data);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Add a task to the task list.
|
|
|
|
// void _addTask({
|
|
|
|
// required String id,
|
|
|
|
// required Completer<dynamic> completer,
|
|
|
|
// }) {
|
|
|
|
// _tasks[id] = SocketTask(completer: completer, subscription: null);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Add a subscription task to the task list.
|
|
|
|
// void _addSubscriptionTask({
|
|
|
|
// required String id,
|
|
|
|
// required ElectrumXSubscription subscription,
|
|
|
|
// }) {
|
|
|
|
// _tasks[id] = SocketTask(completer: null, subscription: subscription);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Write call to socket.
|
|
|
|
// Future<dynamic> _call({
|
|
|
|
// required String method,
|
|
|
|
// List<dynamic> params = const [],
|
|
|
|
// }) async {
|
|
|
|
// // If we're connecting to Tor, wait.
|
|
|
|
// if (_requireMutex) {
|
|
|
|
// await _torConnectingLock.protect(() async => await _checkSocket());
|
|
|
|
// } else {
|
|
|
|
// await _checkSocket();
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Check socket is connected.
|
|
|
|
// if (_prefs.useTor) {
|
|
|
|
// if (_socksSocket == null) {
|
|
|
|
// final msg = "SubscribableElectrumXClient._call: "
|
|
|
|
// "SOCKSSocket is not connected. Method $method, params $params.";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw Exception(msg);
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// if (_socket == null) {
|
|
|
|
// final msg = "SubscribableElectrumXClient._call: "
|
|
|
|
// "Socket is not connected. Method $method, params $params.";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw Exception(msg);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// final completer = Completer<dynamic>();
|
|
|
|
// _currentRequestID++;
|
|
|
|
// final id = _currentRequestID.toString();
|
|
|
|
//
|
|
|
|
// // Write to the socket.
|
|
|
|
// try {
|
|
|
|
// _addTask(id: id, completer: completer);
|
|
|
|
//
|
|
|
|
// if (_prefs.useTor) {
|
|
|
|
// _socksSocket?.write(
|
|
|
|
// _buildJsonRequestString(
|
|
|
|
// method: method,
|
|
|
|
// id: id,
|
|
|
|
// params: params,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// _socket?.write(
|
|
|
|
// _buildJsonRequestString(
|
|
|
|
// method: method,
|
|
|
|
// id: id,
|
|
|
|
// params: params,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return completer.future;
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient._call: "
|
|
|
|
// "failed to request $method with id $id."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw JsonRpcException(msg);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Write call to socket with timeout.
|
|
|
|
// Future<dynamic> _callWithTimeout({
|
|
|
|
// required String method,
|
|
|
|
// List<dynamic> params = const [],
|
|
|
|
// Duration timeout = const Duration(seconds: 2),
|
|
|
|
// }) async {
|
|
|
|
// // If we're connecting to Tor, wait.
|
|
|
|
// if (_requireMutex) {
|
|
|
|
// await _torConnectingLock.protect(() async => await _checkSocket());
|
|
|
|
// } else {
|
|
|
|
// await _checkSocket();
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Check socket is connected.
|
|
|
|
// if (_prefs.useTor) {
|
|
|
|
// if (_socksSocket == null) {
|
|
|
|
// try {
|
|
|
|
// if (_host == null || _port == null) {
|
|
|
|
// throw Exception("No host or port provided");
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Attempt to conect.
|
|
|
|
// await connect(
|
|
|
|
// host: _host!,
|
|
|
|
// port: _port!,
|
|
|
|
// );
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final msg = "SubscribableElectrumXClient._callWithTimeout: "
|
|
|
|
// "SOCKSSocket not connected and cannot connect. "
|
|
|
|
// "Method $method, params $params."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw Exception(msg);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// if (_socket == null) {
|
|
|
|
// try {
|
|
|
|
// if (_host == null || _port == null) {
|
|
|
|
// throw Exception("No host or port provided");
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Attempt to conect.
|
|
|
|
// await connect(
|
|
|
|
// host: _host!,
|
|
|
|
// port: _port!,
|
|
|
|
// );
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final msg = "SubscribableElectrumXClient._callWithTimeout: "
|
|
|
|
// "Socket not connected and cannot connect. "
|
|
|
|
// "Method $method, params $params.";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw Exception(msg);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// final completer = Completer<dynamic>();
|
|
|
|
// _currentRequestID++;
|
|
|
|
// final id = _currentRequestID.toString();
|
|
|
|
//
|
|
|
|
// // Write to the socket.
|
|
|
|
// try {
|
|
|
|
// _addTask(id: id, completer: completer);
|
|
|
|
//
|
|
|
|
// if (_prefs.useTor) {
|
|
|
|
// _socksSocket?.write(
|
|
|
|
// _buildJsonRequestString(
|
|
|
|
// method: method,
|
|
|
|
// id: id,
|
|
|
|
// params: params,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// _socket?.write(
|
|
|
|
// _buildJsonRequestString(
|
|
|
|
// method: method,
|
|
|
|
// id: id,
|
|
|
|
// params: params,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Timer(timeout, () {
|
|
|
|
// if (!completer.isCompleted) {
|
|
|
|
// completer.completeError(
|
|
|
|
// Exception("Request \"id: $id, method: $method\" timed out!"),
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// });
|
|
|
|
//
|
|
|
|
// return completer.future;
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient._callWithTimeout: "
|
|
|
|
// "failed to request $method with id $id (timeout $timeout)."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw JsonRpcException(msg);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// ElectrumXSubscription _subscribe({
|
|
|
|
// required String id,
|
|
|
|
// required String method,
|
|
|
|
// List<dynamic> params = const [],
|
|
|
|
// }) {
|
|
|
|
// try {
|
|
|
|
// final subscription = ElectrumXSubscription();
|
|
|
|
// _addSubscriptionTask(id: id, subscription: subscription);
|
|
|
|
// _currentRequestID++;
|
|
|
|
//
|
|
|
|
// // Check socket is connected.
|
|
|
|
// if (_prefs.useTor) {
|
|
|
|
// if (_socksSocket == null) {
|
|
|
|
// final msg = "SubscribableElectrumXClient._call: "
|
|
|
|
// "SOCKSSocket is not connected. Method $method, params $params.";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw Exception(msg);
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// if (_socket == null) {
|
|
|
|
// final msg = "SubscribableElectrumXClient._call: "
|
|
|
|
// "Socket is not connected. Method $method, params $params.";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw Exception(msg);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Write to the socket.
|
|
|
|
// if (_prefs.useTor) {
|
|
|
|
// _socksSocket?.write(
|
|
|
|
// _buildJsonRequestString(
|
|
|
|
// method: method,
|
|
|
|
// id: id,
|
|
|
|
// params: params,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// _socket?.write(
|
|
|
|
// _buildJsonRequestString(
|
|
|
|
// method: method,
|
|
|
|
// id: id,
|
|
|
|
// params: params,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return subscription;
|
|
|
|
// } catch (e, s) {
|
|
|
|
// final String msg = "SubscribableElectrumXClient._subscribe: "
|
|
|
|
// "failed to subscribe to $method with id $id."
|
|
|
|
// "\nError: $e\nStack trace: $s";
|
|
|
|
// Logging.instance.log(msg, level: LogLevel.Fatal);
|
|
|
|
// throw JsonRpcException(msg);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Ping the server to ensure it is responding
|
|
|
|
// ///
|
|
|
|
// /// Returns true if ping succeeded
|
|
|
|
// Future<bool> ping() async {
|
|
|
|
// // If we're connecting to Tor, wait.
|
|
|
|
// if (_requireMutex) {
|
|
|
|
// await _torConnectingLock.protect(() async => await _checkSocket());
|
|
|
|
// } else {
|
|
|
|
// await _checkSocket();
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Write to the socket.
|
|
|
|
// try {
|
|
|
|
// final response = (await _callWithTimeout(method: "server.ping")) as Map;
|
|
|
|
// return response.keys.contains("result") && response["result"] == null;
|
|
|
|
// } catch (_) {
|
|
|
|
// return false;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Subscribe to a scripthash to receive notifications on status changes
|
|
|
|
// ElectrumXSubscription subscribeToScripthash({required String scripthash}) {
|
|
|
|
// return _subscribe(
|
|
|
|
// id: 'blockchain.scripthash.subscribe:$scripthash',
|
|
|
|
// method: 'blockchain.scripthash.subscribe',
|
|
|
|
// params: [scripthash],
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// /// Subscribe to block headers to receive notifications on new blocks found
|
|
|
|
// ///
|
|
|
|
// /// Returns the existing subscription if found
|
|
|
|
// ElectrumXSubscription subscribeToBlockHeaders() {
|
|
|
|
// return _tasks["blockchain.headers.subscribe"]?.subscription ??
|
|
|
|
// _subscribe(
|
|
|
|
// id: "blockchain.headers.subscribe",
|
|
|
|
// method: "blockchain.headers.subscribe",
|
|
|
|
// params: [],
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// }
|