WIP proxied sockets

This commit is contained in:
sneurlax 2023-08-08 16:01:41 -05:00
parent ec5ae60a61
commit 281cd98390
3 changed files with 199 additions and 40 deletions

View file

@ -15,6 +15,7 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:stackwallet/electrumx_rpc/rpc.dart'; import 'package:stackwallet/electrumx_rpc/rpc.dart';
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart'; import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
import 'package:stackwallet/networking/tor_service.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/prefs.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -71,6 +72,8 @@ class ElectrumX {
final Duration connectionTimeoutForSpecialCaseJsonRPCClients; final Duration connectionTimeoutForSpecialCaseJsonRPCClients;
({String host, int port})? proxyInfo;
ElectrumX({ ElectrumX({
required String host, required String host,
required int port, required int port,
@ -80,6 +83,7 @@ class ElectrumX {
JsonRPC? client, JsonRPC? client,
this.connectionTimeoutForSpecialCaseJsonRPCClients = this.connectionTimeoutForSpecialCaseJsonRPCClients =
const Duration(seconds: 60), const Duration(seconds: 60),
({String host, int port})? proxyInfo,
}) { }) {
_prefs = prefs; _prefs = prefs;
_host = host; _host = host;
@ -92,14 +96,38 @@ class ElectrumX {
required ElectrumXNode node, required ElectrumXNode node,
required Prefs prefs, required Prefs prefs,
required List<ElectrumXNode> failovers, required List<ElectrumXNode> failovers,
}) => ({String host, int port})? proxyInfo,
ElectrumX( }) {
if (Prefs.instance.useTor) {
if (proxyInfo == null) {
// TODO await tor / make sure it's running
proxyInfo = (
host: InternetAddress.loopbackIPv4.address,
port: TorService.sharedInstance.port
);
Logging.instance.log(
"ElectrumX.batchRequest(): no tor proxy info, read $proxyInfo",
level: LogLevel.Warning);
}
return ElectrumX(
host: node.address, host: node.address,
port: node.port, port: node.port,
useSSL: node.useSSL, useSSL: node.useSSL,
prefs: prefs, prefs: prefs,
failovers: failovers, failovers: failovers,
proxyInfo: proxyInfo,
); );
} else {
return ElectrumX(
host: node.address,
port: node.port,
useSSL: node.useSSL,
prefs: prefs,
failovers: failovers,
proxyInfo: null,
);
}
}
Future<bool> _allow() async { Future<bool> _allow() async {
if (_prefs.wifiOnly) { if (_prefs.wifiOnly) {
@ -121,12 +149,24 @@ class ElectrumX {
throw WifiOnlyException(); throw WifiOnlyException();
} }
if (Prefs.instance.useTor) {
if (proxyInfo == null) {
// TODO await tor / make sure Tor is running
proxyInfo = (
host: InternetAddress.loopbackIPv4.address,
port: TorService.sharedInstance.port
);
Logging.instance.log(
"ElectrumX.batchRequest(): no tor proxy info, read $proxyInfo",
level: LogLevel.Warning);
}
if (currentFailoverIndex == -1) { if (currentFailoverIndex == -1) {
_rpcClient ??= JsonRPC( _rpcClient ??= JsonRPC(
host: host, host: host,
port: port, port: port,
useSSL: useSSL, useSSL: useSSL,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients, connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
proxyInfo: proxyInfo,
); );
} else { } else {
_rpcClient = JsonRPC( _rpcClient = JsonRPC(
@ -134,8 +174,28 @@ class ElectrumX {
port: failovers![currentFailoverIndex].port, port: failovers![currentFailoverIndex].port,
useSSL: failovers![currentFailoverIndex].useSSL, useSSL: failovers![currentFailoverIndex].useSSL,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients, connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
proxyInfo: proxyInfo,
); );
} }
} else {
if (currentFailoverIndex == -1) {
_rpcClient ??= JsonRPC(
host: host,
port: port,
useSSL: useSSL,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
proxyInfo: null,
);
} else {
_rpcClient = JsonRPC(
host: failovers![currentFailoverIndex].address,
port: failovers![currentFailoverIndex].port,
useSSL: failovers![currentFailoverIndex].useSSL,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
proxyInfo: null,
);
}
}
try { try {
final requestId = requestID ?? const Uuid().v1(); final requestId = requestID ?? const Uuid().v1();
@ -221,12 +281,25 @@ class ElectrumX {
throw WifiOnlyException(); throw WifiOnlyException();
} }
if (Prefs.instance.useTor) {
// TODO await tor / make sure Tor is initialized
if (proxyInfo == null) {
proxyInfo = (
host: InternetAddress.loopbackIPv4.address,
port: TorService.sharedInstance.port
);
Logging.instance.log(
"ElectrumX.batchRequest(): no tor proxy info, read $proxyInfo",
level: LogLevel.Warning);
}
if (currentFailoverIndex == -1) { if (currentFailoverIndex == -1) {
_rpcClient ??= JsonRPC( _rpcClient ??= JsonRPC(
host: host, host: host,
port: port, port: port,
useSSL: useSSL, useSSL: useSSL,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients, connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
proxyInfo: proxyInfo,
); );
} else { } else {
_rpcClient = JsonRPC( _rpcClient = JsonRPC(
@ -234,8 +307,28 @@ class ElectrumX {
port: failovers![currentFailoverIndex].port, port: failovers![currentFailoverIndex].port,
useSSL: failovers![currentFailoverIndex].useSSL, useSSL: failovers![currentFailoverIndex].useSSL,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients, connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
proxyInfo: proxyInfo,
); );
} }
} else {
if (currentFailoverIndex == -1) {
_rpcClient ??= JsonRPC(
host: host,
port: port,
useSSL: useSSL,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
proxyInfo: null,
);
} else {
_rpcClient = JsonRPC(
host: failovers![currentFailoverIndex].address,
port: failovers![currentFailoverIndex].port,
useSSL: failovers![currentFailoverIndex].useSSL,
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
proxyInfo: null,
);
}
}
try { try {
final List<String> requestStrings = []; final List<String> requestStrings = [];

View file

@ -14,7 +14,11 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mutex/mutex.dart'; import 'package:mutex/mutex.dart';
import 'package:socks5_proxy/socks.dart';
import 'package:socks5_proxy/src/client/socks_client.dart';
import 'package:stackwallet/networking/tor_service.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
// Json RPC class to handle connecting to electrumx servers // Json RPC class to handle connecting to electrumx servers
class JsonRPC { class JsonRPC {
@ -23,11 +27,13 @@ class JsonRPC {
required this.port, required this.port,
this.useSSL = false, this.useSSL = false,
this.connectionTimeout = const Duration(seconds: 60), this.connectionTimeout = const Duration(seconds: 60),
required ({String host, int port})? proxyInfo,
}); });
final bool useSSL; final bool useSSL;
final String host; final String host;
final int port; final int port;
final Duration connectionTimeout; final Duration connectionTimeout;
({String host, int port})? proxyInfo;
final _requestMutex = Mutex(); final _requestMutex = Mutex();
final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue(); final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue();
@ -152,6 +158,62 @@ class JsonRPC {
); );
} }
if (Prefs.instance.useTor) {
if (proxyInfo == null) {
// TODO await tor / make sure it's running
proxyInfo = (
host: InternetAddress.loopbackIPv4.address,
port: TorService.sharedInstance.port
);
Logging.instance.log(
"ElectrumX.connect(): no tor proxy info, read $proxyInfo",
level: LogLevel.Warning);
}
// TODO connect to proxy socket...
// https://github.com/LacticWhale/socks_dart/blob/master/lib/src/client/socks_client.dart#L50C46-L50C56
// TODO implement ssl over tor
// if (useSSL) {
// _socket = await SecureSocket.connect(
// host,
// port,
// timeout: connectionTimeout,
// onBadCertificate: (_) => true,
// ); // TODO do not automatically trust bad certificates
// final _client = SocksSocket.protected(_socket, type);
// } else {
// _socket = await Socket.connect(
// proxyInfo!.host,
// proxyInfo!.port,
// timeout: connectionTimeout,
// );
// final _client = SocksSocket.protected(
// _socket!, SocksConnectionType.connect
// );
final InternetAddress _host =
await InternetAddress.lookup(host).then((value) => value.first);
var _socket = await SocksSocket.initialize(
[
ProxySettings(
InternetAddress.loopbackIPv4,
proxyInfo!.port,
)
],
_host,
port,
SocksConnectionType.connect,
);
if (_socket == null) {
Logging.instance.log(
"JsonRPC.connect(): failed to connect to $host over tor proxy at $proxyInfo",
level: LogLevel.Error);
throw Exception("JsonRPC.connect(): failed to connect to tor proxy");
} else {
Logging.instance.log(
"JsonRPC.connect(): connected to $host over tor proxy at $proxyInfo",
level: LogLevel.Info);
}
} else {
if (useSSL) { if (useSSL) {
_socket = await SecureSocket.connect( _socket = await SecureSocket.connect(
host, host,
@ -166,6 +228,7 @@ class JsonRPC {
timeout: connectionTimeout, timeout: connectionTimeout,
); );
} }
}
_subscription = _socket!.listen( _subscription = _socket!.listen(
_dataHandler, _dataHandler,

View file

@ -11,6 +11,7 @@ void main() {
port: DefaultNodes.bitcoin.port, port: DefaultNodes.bitcoin.port,
useSSL: true, useSSL: true,
connectionTimeout: const Duration(seconds: 40), connectionTimeout: const Duration(seconds: 40),
proxyInfo: null, // TODO test for proxyInfo
); );
const jsonRequestString = const jsonRequestString =
@ -27,7 +28,8 @@ void main() {
final jsonRPC = JsonRPC( final jsonRPC = JsonRPC(
host: "some.bad.address.thingdsfsdfsdaf", host: "some.bad.address.thingdsfsdfsdaf",
port: 3000, port: 3000,
connectionTimeout: Duration(seconds: 10), connectionTimeout: const Duration(seconds: 10),
proxyInfo: null,
); );
const jsonRequestString = const jsonRequestString =
@ -47,6 +49,7 @@ void main() {
port: 3000, port: 3000,
useSSL: false, useSSL: false,
connectionTimeout: const Duration(seconds: 1), connectionTimeout: const Duration(seconds: 1),
proxyInfo: null,
); );
const jsonRequestString = const jsonRequestString =