mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 01:37:54 +00:00
uncomment subscribable electrumx client
This commit is contained in:
parent
f655b51e88
commit
9791d9b362
1 changed files with 324 additions and 324 deletions
|
@ -1,324 +1,324 @@
|
|||
// /*
|
||||
// * 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:stackwallet/utilities/logger.dart';
|
||||
//
|
||||
// class ElectrumXSubscription with ChangeNotifier {
|
||||
// dynamic _response;
|
||||
// dynamic get response => _response;
|
||||
// set response(dynamic newData) {
|
||||
// _response = newData;
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 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;
|
||||
// late final bool _useSSL;
|
||||
// late final Duration _connectionTimeout;
|
||||
// late final Duration _keepAlive;
|
||||
//
|
||||
// bool get isConnected => _isConnected;
|
||||
// bool get useSSL => _useSSL;
|
||||
//
|
||||
// void Function(bool)? onConnectionStatusChanged;
|
||||
//
|
||||
// SubscribableElectrumXClient({
|
||||
// bool useSSL = true,
|
||||
// this.onConnectionStatusChanged,
|
||||
// Duration connectionTimeout = const Duration(seconds: 5),
|
||||
// Duration keepAlive = const Duration(seconds: 10),
|
||||
// }) {
|
||||
// _useSSL = useSSL;
|
||||
// _connectionTimeout = connectionTimeout;
|
||||
// _keepAlive = keepAlive;
|
||||
// }
|
||||
//
|
||||
// Future<void> connect({required String host, required int port}) async {
|
||||
// try {
|
||||
// await _socket?.close();
|
||||
// } catch (_) {}
|
||||
//
|
||||
// if (_useSSL) {
|
||||
// _socket = await SecureSocket.connect(
|
||||
// host,
|
||||
// port,
|
||||
// timeout: _connectionTimeout,
|
||||
// onBadCertificate: (_) => true,
|
||||
// );
|
||||
// } else {
|
||||
// _socket = await Socket.connect(
|
||||
// host,
|
||||
// port,
|
||||
// timeout: _connectionTimeout,
|
||||
// );
|
||||
// }
|
||||
// _updateConnectionStatus(true);
|
||||
//
|
||||
// _socket!.listen(
|
||||
// _dataHandler,
|
||||
// onError: _errorHandler,
|
||||
// onDone: _doneHandler,
|
||||
// cancelOnError: true,
|
||||
// );
|
||||
//
|
||||
// _aliveTimer?.cancel();
|
||||
// _aliveTimer = Timer.periodic(
|
||||
// _keepAlive,
|
||||
// (_) async => _updateConnectionStatus(await ping()),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// Future<void> disconnect() async {
|
||||
// _aliveTimer?.cancel();
|
||||
// await _socket?.close();
|
||||
// onConnectionStatusChanged = null;
|
||||
// }
|
||||
//
|
||||
// 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';
|
||||
// }
|
||||
//
|
||||
// void _updateConnectionStatus(bool connectionStatus) {
|
||||
// if (_isConnected != connectionStatus && onConnectionStatusChanged != null) {
|
||||
// onConnectionStatusChanged!(connectionStatus);
|
||||
// }
|
||||
// _isConnected = connectionStatus;
|
||||
// }
|
||||
//
|
||||
// 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 = [];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 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);
|
||||
// }
|
||||
//
|
||||
// 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?.response = params.last;
|
||||
// break;
|
||||
// case "blockchain.headers.subscribe":
|
||||
// final params = response["params"];
|
||||
// const taskId = "blockchain.headers.subscribe";
|
||||
//
|
||||
// _tasks[taskId]?.subscription?.response = params.first;
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void _errorHandler(Object error, StackTrace trace) {
|
||||
// _updateConnectionStatus(false);
|
||||
// Logging.instance.log(
|
||||
// "SubscribableElectrumXClient called _errorHandler with: $error\n$trace",
|
||||
// level: LogLevel.Info);
|
||||
// }
|
||||
//
|
||||
// void _doneHandler() {
|
||||
// _updateConnectionStatus(false);
|
||||
// Logging.instance.log("SubscribableElectrumXClient called _doneHandler",
|
||||
// level: LogLevel.Info);
|
||||
// }
|
||||
//
|
||||
// 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?.response = data;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void _addTask({
|
||||
// required String id,
|
||||
// required Completer<dynamic> completer,
|
||||
// }) {
|
||||
// _tasks[id] = SocketTask(completer: completer, subscription: null);
|
||||
// }
|
||||
//
|
||||
// void _addSubscriptionTask({
|
||||
// required String id,
|
||||
// required ElectrumXSubscription subscription,
|
||||
// }) {
|
||||
// _tasks[id] = SocketTask(completer: null, subscription: subscription);
|
||||
// }
|
||||
//
|
||||
// Future<dynamic> _call({
|
||||
// required String method,
|
||||
// List<dynamic> params = const [],
|
||||
// }) async {
|
||||
// final completer = Completer<dynamic>();
|
||||
// _currentRequestID++;
|
||||
// final id = _currentRequestID.toString();
|
||||
// _addTask(id: id, completer: completer);
|
||||
//
|
||||
// _socket?.write(
|
||||
// _buildJsonRequestString(
|
||||
// method: method,
|
||||
// id: id,
|
||||
// params: params,
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// return completer.future;
|
||||
// }
|
||||
//
|
||||
// Future<dynamic> _callWithTimeout({
|
||||
// required String method,
|
||||
// List<dynamic> params = const [],
|
||||
// Duration timeout = const Duration(seconds: 2),
|
||||
// }) async {
|
||||
// final completer = Completer<dynamic>();
|
||||
// _currentRequestID++;
|
||||
// final id = _currentRequestID.toString();
|
||||
// _addTask(id: id, completer: completer);
|
||||
//
|
||||
// _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;
|
||||
// }
|
||||
//
|
||||
// ElectrumXSubscription _subscribe({
|
||||
// required String taskId,
|
||||
// required String method,
|
||||
// List<dynamic> params = const [],
|
||||
// }) {
|
||||
// // try {
|
||||
// final subscription = ElectrumXSubscription();
|
||||
// _addSubscriptionTask(id: taskId, subscription: subscription);
|
||||
// _currentRequestID++;
|
||||
// _socket?.write(
|
||||
// _buildJsonRequestString(
|
||||
// method: method,
|
||||
// id: taskId,
|
||||
// params: params,
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// return subscription;
|
||||
// // } catch (e, s) {
|
||||
// // Logging.instance.log("SubscribableElectrumXClient _subscribe: $e\n$s", level: LogLevel.Error);
|
||||
// // return null;
|
||||
// // }
|
||||
// }
|
||||
//
|
||||
// /// Ping the server to ensure it is responding
|
||||
// ///
|
||||
// /// Returns true if ping succeeded
|
||||
// Future<bool> ping() async {
|
||||
// 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(
|
||||
// taskId: '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(
|
||||
// taskId: "blockchain.headers.subscribe",
|
||||
// method: "blockchain.headers.subscribe",
|
||||
// params: [],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
/*
|
||||
* 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:stackwallet/utilities/logger.dart';
|
||||
|
||||
class ElectrumXSubscription with ChangeNotifier {
|
||||
dynamic _response;
|
||||
dynamic get response => _response;
|
||||
set response(dynamic newData) {
|
||||
_response = newData;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
late final bool _useSSL;
|
||||
late final Duration _connectionTimeout;
|
||||
late final Duration _keepAlive;
|
||||
|
||||
bool get isConnected => _isConnected;
|
||||
bool get useSSL => _useSSL;
|
||||
|
||||
void Function(bool)? onConnectionStatusChanged;
|
||||
|
||||
SubscribableElectrumXClient({
|
||||
bool useSSL = true,
|
||||
this.onConnectionStatusChanged,
|
||||
Duration connectionTimeout = const Duration(seconds: 5),
|
||||
Duration keepAlive = const Duration(seconds: 10),
|
||||
}) {
|
||||
_useSSL = useSSL;
|
||||
_connectionTimeout = connectionTimeout;
|
||||
_keepAlive = keepAlive;
|
||||
}
|
||||
|
||||
Future<void> connect({required String host, required int port}) async {
|
||||
try {
|
||||
await _socket?.close();
|
||||
} catch (_) {}
|
||||
|
||||
if (_useSSL) {
|
||||
_socket = await SecureSocket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: _connectionTimeout,
|
||||
onBadCertificate: (_) => true,
|
||||
);
|
||||
} else {
|
||||
_socket = await Socket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: _connectionTimeout,
|
||||
);
|
||||
}
|
||||
_updateConnectionStatus(true);
|
||||
|
||||
_socket!.listen(
|
||||
_dataHandler,
|
||||
onError: _errorHandler,
|
||||
onDone: _doneHandler,
|
||||
cancelOnError: true,
|
||||
);
|
||||
|
||||
_aliveTimer?.cancel();
|
||||
_aliveTimer = Timer.periodic(
|
||||
_keepAlive,
|
||||
(_) async => _updateConnectionStatus(await ping()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> disconnect() async {
|
||||
_aliveTimer?.cancel();
|
||||
await _socket?.close();
|
||||
onConnectionStatusChanged = null;
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
void _updateConnectionStatus(bool connectionStatus) {
|
||||
if (_isConnected != connectionStatus && onConnectionStatusChanged != null) {
|
||||
onConnectionStatusChanged!(connectionStatus);
|
||||
}
|
||||
_isConnected = connectionStatus;
|
||||
}
|
||||
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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?.response = params.last;
|
||||
break;
|
||||
case "blockchain.headers.subscribe":
|
||||
final params = response["params"];
|
||||
const taskId = "blockchain.headers.subscribe";
|
||||
|
||||
_tasks[taskId]?.subscription?.response = params.first;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _errorHandler(Object error, StackTrace trace) {
|
||||
_updateConnectionStatus(false);
|
||||
Logging.instance.log(
|
||||
"SubscribableElectrumXClient called _errorHandler with: $error\n$trace",
|
||||
level: LogLevel.Info);
|
||||
}
|
||||
|
||||
void _doneHandler() {
|
||||
_updateConnectionStatus(false);
|
||||
Logging.instance.log("SubscribableElectrumXClient called _doneHandler",
|
||||
level: LogLevel.Info);
|
||||
}
|
||||
|
||||
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?.response = data;
|
||||
}
|
||||
}
|
||||
|
||||
void _addTask({
|
||||
required String id,
|
||||
required Completer<dynamic> completer,
|
||||
}) {
|
||||
_tasks[id] = SocketTask(completer: completer, subscription: null);
|
||||
}
|
||||
|
||||
void _addSubscriptionTask({
|
||||
required String id,
|
||||
required ElectrumXSubscription subscription,
|
||||
}) {
|
||||
_tasks[id] = SocketTask(completer: null, subscription: subscription);
|
||||
}
|
||||
|
||||
Future<dynamic> _call({
|
||||
required String method,
|
||||
List<dynamic> params = const [],
|
||||
}) async {
|
||||
final completer = Completer<dynamic>();
|
||||
_currentRequestID++;
|
||||
final id = _currentRequestID.toString();
|
||||
_addTask(id: id, completer: completer);
|
||||
|
||||
_socket?.write(
|
||||
_buildJsonRequestString(
|
||||
method: method,
|
||||
id: id,
|
||||
params: params,
|
||||
),
|
||||
);
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<dynamic> _callWithTimeout({
|
||||
required String method,
|
||||
List<dynamic> params = const [],
|
||||
Duration timeout = const Duration(seconds: 2),
|
||||
}) async {
|
||||
final completer = Completer<dynamic>();
|
||||
_currentRequestID++;
|
||||
final id = _currentRequestID.toString();
|
||||
_addTask(id: id, completer: completer);
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
ElectrumXSubscription _subscribe({
|
||||
required String taskId,
|
||||
required String method,
|
||||
List<dynamic> params = const [],
|
||||
}) {
|
||||
// try {
|
||||
final subscription = ElectrumXSubscription();
|
||||
_addSubscriptionTask(id: taskId, subscription: subscription);
|
||||
_currentRequestID++;
|
||||
_socket?.write(
|
||||
_buildJsonRequestString(
|
||||
method: method,
|
||||
id: taskId,
|
||||
params: params,
|
||||
),
|
||||
);
|
||||
|
||||
return subscription;
|
||||
// } catch (e, s) {
|
||||
// Logging.instance.log("SubscribableElectrumXClient _subscribe: $e\n$s", level: LogLevel.Error);
|
||||
// return null;
|
||||
// }
|
||||
}
|
||||
|
||||
/// Ping the server to ensure it is responding
|
||||
///
|
||||
/// Returns true if ping succeeded
|
||||
Future<bool> ping() async {
|
||||
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(
|
||||
taskId: '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(
|
||||
taskId: "blockchain.headers.subscribe",
|
||||
method: "blockchain.headers.subscribe",
|
||||
params: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue