mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-15 16:12:16 +00:00
Merge branch 'tor' into tor-presocks
This commit is contained in:
commit
75750e2bd1
10 changed files with 722 additions and 72 deletions
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
|
@ -157,7 +158,6 @@ class CachedElectrumX {
|
|||
|
||||
Future<List<String>> getUsedCoinSerials({
|
||||
required Coin coin,
|
||||
int startNumber = 0,
|
||||
}) async {
|
||||
try {
|
||||
final box = await DB.instance.getUsedSerialsCacheBox(coin: coin);
|
||||
|
@ -168,7 +168,7 @@ class CachedElectrumX {
|
|||
_list == null ? {} : List<String>.from(_list).toSet();
|
||||
|
||||
final startNumber =
|
||||
cachedSerials.length - 10; // 10 being some arbitrary buffer
|
||||
max(0, cachedSerials.length - 100); // 100 being some arbitrary buffer
|
||||
|
||||
final serials = await electrumXClient.getUsedCoinSerials(
|
||||
startNumber: startNumber,
|
||||
|
|
|
@ -15,6 +15,7 @@ import 'package:connectivity_plus/connectivity_plus.dart';
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/rpc.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/prefs.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
@ -71,6 +72,8 @@ class ElectrumX {
|
|||
|
||||
final Duration connectionTimeoutForSpecialCaseJsonRPCClients;
|
||||
|
||||
({String host, int port})? proxyInfo;
|
||||
|
||||
ElectrumX({
|
||||
required String host,
|
||||
required int port,
|
||||
|
@ -80,6 +83,7 @@ class ElectrumX {
|
|||
JsonRPC? client,
|
||||
this.connectionTimeoutForSpecialCaseJsonRPCClients =
|
||||
const Duration(seconds: 60),
|
||||
({String host, int port})? proxyInfo,
|
||||
}) {
|
||||
_prefs = prefs;
|
||||
_host = host;
|
||||
|
@ -92,14 +96,38 @@ class ElectrumX {
|
|||
required ElectrumXNode node,
|
||||
required Prefs prefs,
|
||||
required List<ElectrumXNode> failovers,
|
||||
}) =>
|
||||
ElectrumX(
|
||||
({String host, int port})? proxyInfo,
|
||||
}) {
|
||||
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.from(): no tor proxy info, read $proxyInfo",
|
||||
level: LogLevel.Warning);
|
||||
}
|
||||
return ElectrumX(
|
||||
host: node.address,
|
||||
port: node.port,
|
||||
useSSL: node.useSSL,
|
||||
prefs: prefs,
|
||||
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 {
|
||||
if (_prefs.wifiOnly) {
|
||||
|
@ -121,20 +149,52 @@ class ElectrumX {
|
|||
throw WifiOnlyException();
|
||||
}
|
||||
|
||||
if (currentFailoverIndex == -1) {
|
||||
_rpcClient ??= JsonRPC(
|
||||
host: host,
|
||||
port: port,
|
||||
useSSL: useSSL,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
);
|
||||
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.request(): no tor proxy info, read $proxyInfo",
|
||||
level: LogLevel.Warning);
|
||||
}
|
||||
if (currentFailoverIndex == -1) {
|
||||
_rpcClient ??= JsonRPC(
|
||||
host: host,
|
||||
port: port,
|
||||
useSSL: useSSL,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
} else {
|
||||
_rpcClient ??= JsonRPC(
|
||||
host: failovers![currentFailoverIndex].address,
|
||||
port: failovers![currentFailoverIndex].port,
|
||||
useSSL: failovers![currentFailoverIndex].useSSL,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_rpcClient = JsonRPC(
|
||||
host: failovers![currentFailoverIndex].address,
|
||||
port: failovers![currentFailoverIndex].port,
|
||||
useSSL: failovers![currentFailoverIndex].useSSL,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
);
|
||||
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 {
|
||||
|
@ -153,7 +213,8 @@ class ElectrumX {
|
|||
);
|
||||
|
||||
if (response.exception != null) {
|
||||
throw response.exception!;
|
||||
throw response.exception!
|
||||
as Object; // TODO properly check that .exception is an Object
|
||||
}
|
||||
|
||||
if (response.data is Map && response.data["error"] != null) {
|
||||
|
@ -221,20 +282,53 @@ class ElectrumX {
|
|||
throw WifiOnlyException();
|
||||
}
|
||||
|
||||
if (currentFailoverIndex == -1) {
|
||||
_rpcClient ??= JsonRPC(
|
||||
host: host,
|
||||
port: port,
|
||||
useSSL: useSSL,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
);
|
||||
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) {
|
||||
_rpcClient ??= JsonRPC(
|
||||
host: host,
|
||||
port: port,
|
||||
useSSL: useSSL,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
} else {
|
||||
_rpcClient = JsonRPC(
|
||||
host: failovers![currentFailoverIndex].address,
|
||||
port: failovers![currentFailoverIndex].port,
|
||||
useSSL: failovers![currentFailoverIndex].useSSL,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_rpcClient = JsonRPC(
|
||||
host: failovers![currentFailoverIndex].address,
|
||||
port: failovers![currentFailoverIndex].port,
|
||||
useSSL: failovers![currentFailoverIndex].useSSL,
|
||||
connectionTimeout: connectionTimeoutForSpecialCaseJsonRPCClients,
|
||||
);
|
||||
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 {
|
||||
|
@ -260,7 +354,8 @@ class ElectrumX {
|
|||
(await _rpcClient!.request(request, requestTimeout));
|
||||
|
||||
if (jsonRpcResponse.exception != null) {
|
||||
throw jsonRpcResponse.exception!;
|
||||
throw jsonRpcResponse.exception!
|
||||
as Object; // TODO properly check that .exception is an Object
|
||||
}
|
||||
|
||||
final response = jsonRpcResponse.data as List;
|
||||
|
|
|
@ -14,7 +14,10 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:stackwallet/networking/socks5.dart';
|
||||
import 'package:stackwallet/networking/tor_service.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
|
||||
// Json RPC class to handle connecting to electrumx servers
|
||||
class JsonRPC {
|
||||
|
@ -23,16 +26,20 @@ class JsonRPC {
|
|||
required this.port,
|
||||
this.useSSL = false,
|
||||
this.connectionTimeout = const Duration(seconds: 60),
|
||||
required ({String host, int port})? proxyInfo,
|
||||
});
|
||||
final bool useSSL;
|
||||
final String host;
|
||||
final int port;
|
||||
final Duration connectionTimeout;
|
||||
({String host, int port})? proxyInfo;
|
||||
|
||||
final _requestMutex = Mutex();
|
||||
final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue();
|
||||
Socket? _socket;
|
||||
SOCKSSocket? _socksSocket;
|
||||
StreamSubscription<Uint8List>? _subscription;
|
||||
StreamSubscription<Uint8List>? get subscription => _subscription;
|
||||
|
||||
void _dataHandler(List<int> data) {
|
||||
_requestQueue.nextIncompleteReq.then((req) {
|
||||
|
@ -75,7 +82,13 @@ class JsonRPC {
|
|||
_requestQueue.nextIncompleteReq.then((req) {
|
||||
if (req != null) {
|
||||
// \r\n required by electrumx server
|
||||
_socket!.write('${req.jsonRequest}\r\n');
|
||||
if (_socket != null) {
|
||||
_socket!.write('${req.jsonRequest}\r\n');
|
||||
}
|
||||
if (_socksSocket != null) {
|
||||
print(33333333);
|
||||
_socksSocket!.write('${req.jsonRequest}\r\n');
|
||||
}
|
||||
|
||||
// TODO different timeout length?
|
||||
req.initiateTimeout(
|
||||
|
@ -92,12 +105,22 @@ class JsonRPC {
|
|||
Duration requestTimeout,
|
||||
) async {
|
||||
await _requestMutex.protect(() async {
|
||||
if (_socket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC request: opening socket $host:$port",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await connect();
|
||||
if (!Prefs.instance.useTor) {
|
||||
if (_socket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC request: opening socket $host:$port",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await connect();
|
||||
}
|
||||
} else {
|
||||
if (_socksSocket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC request: opening SOCKS socket to $host:$port",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await connect();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -137,6 +160,9 @@ class JsonRPC {
|
|||
_subscription = null;
|
||||
_socket?.destroy();
|
||||
_socket = null;
|
||||
unawaited(_socksSocket?.close(keepOpen: false));
|
||||
// TODO check that it's ok to not await this
|
||||
_socksSocket = null;
|
||||
|
||||
// clean up remaining queue
|
||||
await _requestQueue.completeRemainingWithError(
|
||||
|
@ -146,33 +172,92 @@ class JsonRPC {
|
|||
}
|
||||
|
||||
Future<void> connect() async {
|
||||
if (_socket != null) {
|
||||
throw Exception(
|
||||
"JsonRPC attempted to connect to an already existing socket!",
|
||||
);
|
||||
}
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
if (useSSL) {
|
||||
_socket = await SecureSocket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: connectionTimeout,
|
||||
onBadCertificate: (_) => true,
|
||||
); // TODO do not automatically trust bad certificates
|
||||
_subscription = _socket!.listen(
|
||||
_dataHandler,
|
||||
onError: _errorHandler,
|
||||
onDone: _doneHandler,
|
||||
cancelOnError: true,
|
||||
);
|
||||
} else {
|
||||
_socket = await Socket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: connectionTimeout,
|
||||
);
|
||||
}
|
||||
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...
|
||||
|
||||
_subscription = _socket!.listen(
|
||||
_dataHandler,
|
||||
onError: _errorHandler,
|
||||
onDone: _doneHandler,
|
||||
cancelOnError: true,
|
||||
);
|
||||
// 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 {
|
||||
final sock = await RawSocket.connect(
|
||||
InternetAddress.loopbackIPv4, proxyInfo!.port);
|
||||
_socksSocket = SOCKSSocket(sock);
|
||||
|
||||
try {
|
||||
Logging.instance.log(
|
||||
"JsonRPC.connect(): connecting to $host:$port over SOCKS socket at $proxyInfo...",
|
||||
level: LogLevel.Info);
|
||||
if (!isIpAddress(host)) {
|
||||
await _socksSocket!.connect("$host:$port");
|
||||
} else {
|
||||
await _socksSocket!.connectIp(InternetAddress(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 Exception(
|
||||
"JsonRPC.connect(): failed to connect to tor proxy, $e");
|
||||
}
|
||||
|
||||
// _subscription = _socksSocket!.socket.listen(
|
||||
// _dataHandler,
|
||||
// onError: _errorHandler,
|
||||
// onDone: _doneHandler,
|
||||
// cancelOnError: true,
|
||||
// ) as StreamSubscription<Uint8List>?;
|
||||
|
||||
_socksSocket!.subscription.onData((RawSocketEvent event) {
|
||||
/// [RawSocketEvent] messages are here
|
||||
/// read from here..
|
||||
if (event == RawSocketEvent.read) {
|
||||
final data = sock.read(sock.available());
|
||||
print(11111);
|
||||
print(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,3 +380,14 @@ class JsonRPCResponse {
|
|||
|
||||
JsonRPCResponse({this.data, this.exception});
|
||||
}
|
||||
|
||||
bool isIpAddress(String host) {
|
||||
try {
|
||||
// if the string can be parsed into an InternetAddress, it's an IP.
|
||||
InternetAddress(host);
|
||||
return true;
|
||||
} catch (e) {
|
||||
// if parsing fails, it's not an IP.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
399
lib/networking/socks5.dart
Normal file
399
lib/networking/socks5.dart
Normal file
|
@ -0,0 +1,399 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// https://tools.ietf.org/html/rfc1928
|
||||
/// https://tools.ietf.org/html/rfc1929
|
||||
///
|
||||
|
||||
const SOCKSVersion = 0x05;
|
||||
const RFC1929Version = 0x01;
|
||||
|
||||
enum AuthMethods {
|
||||
NoAuth(0x00),
|
||||
GSSApi(0x01),
|
||||
UsernamePassword(0x02),
|
||||
NoAcceptableMethods(0xFF);
|
||||
|
||||
final int rawValue;
|
||||
|
||||
const AuthMethods(this.rawValue);
|
||||
|
||||
factory AuthMethods.fromValue(int value) {
|
||||
for (final v in values) {
|
||||
if (v.rawValue == value) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw UnsupportedError("Invalid AuthMethods value");
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "$runtimeType.$name";
|
||||
}
|
||||
|
||||
enum SOCKSState {
|
||||
Starting(0x00),
|
||||
Auth(0x01),
|
||||
RequestReady(0x02),
|
||||
Connected(0x03),
|
||||
AuthStarted(0x04);
|
||||
|
||||
final int rawValue;
|
||||
|
||||
const SOCKSState(this.rawValue);
|
||||
|
||||
factory SOCKSState.fromValue(int value) {
|
||||
for (final v in values) {
|
||||
if (v.rawValue == value) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw UnsupportedError("Invalid SOCKSState value");
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "$runtimeType.$name";
|
||||
}
|
||||
|
||||
enum SOCKSAddressType {
|
||||
IPv4(0x01),
|
||||
Domain(0x03),
|
||||
IPv6(0x04);
|
||||
|
||||
final int rawValue;
|
||||
|
||||
const SOCKSAddressType(this.rawValue);
|
||||
|
||||
factory SOCKSAddressType.fromValue(int value) {
|
||||
for (final v in values) {
|
||||
if (v.rawValue == value) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw UnsupportedError("Invalid SOCKSAddressType value");
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "$runtimeType.$name";
|
||||
}
|
||||
|
||||
enum SOCKSCommand {
|
||||
Connect(0x01),
|
||||
Bind(0x02),
|
||||
UDPAssociate(0x03);
|
||||
|
||||
final int rawValue;
|
||||
|
||||
const SOCKSCommand(this.rawValue);
|
||||
|
||||
factory SOCKSCommand.fromValue(int value) {
|
||||
for (final v in values) {
|
||||
if (v.rawValue == value) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw UnsupportedError("Invalid SOCKSCommand value");
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "$runtimeType.$name";
|
||||
}
|
||||
|
||||
enum SOCKSReply {
|
||||
Success(0x00),
|
||||
GeneralFailure(0x01),
|
||||
ConnectionNotAllowedByRuleSet(0x02),
|
||||
NetworkUnreachable(0x03),
|
||||
HostUnreachable(0x04),
|
||||
ConnectionRefused(0x05),
|
||||
TTLExpired(0x06),
|
||||
CommandNotSupported(0x07),
|
||||
AddressTypeNotSupported(0x08);
|
||||
|
||||
final int rawValue;
|
||||
|
||||
const SOCKSReply(this.rawValue);
|
||||
|
||||
factory SOCKSReply.fromValue(int value) {
|
||||
for (final v in values) {
|
||||
if (v.rawValue == value) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw UnsupportedError("Invalid SOCKSReply value");
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "$runtimeType.$name";
|
||||
}
|
||||
|
||||
class SOCKSRequest {
|
||||
final int version = SOCKSVersion;
|
||||
final SOCKSCommand command;
|
||||
final SOCKSAddressType addressType;
|
||||
final Uint8List address;
|
||||
final int port;
|
||||
|
||||
String getAddressString() {
|
||||
switch (addressType) {
|
||||
case SOCKSAddressType.Domain:
|
||||
return const AsciiDecoder().convert(address);
|
||||
case SOCKSAddressType.IPv4:
|
||||
return address.join(".");
|
||||
case SOCKSAddressType.IPv6:
|
||||
final List<String> ret = [];
|
||||
for (int x = 0; x < address.length; x += 2) {
|
||||
ret.add("${address[x].toRadixString(16).padLeft(2, "0")}"
|
||||
"${address[x + 1].toRadixString(16).padLeft(2, "0")}");
|
||||
}
|
||||
return ret.join(":");
|
||||
}
|
||||
}
|
||||
|
||||
SOCKSRequest({
|
||||
required this.command,
|
||||
required this.addressType,
|
||||
required this.address,
|
||||
required this.port,
|
||||
});
|
||||
}
|
||||
|
||||
class SOCKSSocket {
|
||||
late List<AuthMethods> _auth;
|
||||
late RawSocket _sock;
|
||||
SOCKSRequest? _request;
|
||||
|
||||
StreamSubscription<RawSocketEvent>? _sockSub;
|
||||
StreamSubscription<RawSocketEvent>? get subscription => _sockSub;
|
||||
|
||||
SOCKSState _state = SOCKSState.Starting;
|
||||
final StreamController<SOCKSState> _stateStream =
|
||||
StreamController<SOCKSState>();
|
||||
SOCKSState get state => _state;
|
||||
Stream<SOCKSState> get stateStream => _stateStream.stream;
|
||||
|
||||
/// For username:password auth
|
||||
final String? username;
|
||||
final String? password;
|
||||
|
||||
/// Waits for state to change to [SOCKSState.Connected]
|
||||
/// If the connection request returns an error from the
|
||||
/// socks server it will be thrown as an exception in the stream
|
||||
///
|
||||
///
|
||||
Future<SOCKSState> get _waitForConnect =>
|
||||
stateStream.firstWhere((a) => a == SOCKSState.Connected);
|
||||
|
||||
SOCKSSocket(
|
||||
RawSocket socket, {
|
||||
List<AuthMethods> auth = const [AuthMethods.NoAuth],
|
||||
this.username,
|
||||
this.password,
|
||||
}) {
|
||||
_sock = socket;
|
||||
_auth = auth;
|
||||
_setState(SOCKSState.Starting);
|
||||
}
|
||||
|
||||
void _setState(SOCKSState ns) {
|
||||
_state = ns;
|
||||
_stateStream.add(ns);
|
||||
}
|
||||
|
||||
/// Issue connect command to proxy
|
||||
///
|
||||
Future<void> connect(String domain) async {
|
||||
final ds = domain.split(':');
|
||||
assert(ds.length == 2, "Domain must contain port, example.com:80");
|
||||
|
||||
_request = SOCKSRequest(
|
||||
command: SOCKSCommand.Connect,
|
||||
addressType: SOCKSAddressType.Domain,
|
||||
address: AsciiEncoder().convert(ds[0]).sublist(0, ds[0].length),
|
||||
port: int.tryParse(ds[1]) ?? 80,
|
||||
);
|
||||
await _start();
|
||||
await _waitForConnect;
|
||||
}
|
||||
|
||||
Future<void> connectIp(InternetAddress ip, int port) async {
|
||||
_request = SOCKSRequest(
|
||||
command: SOCKSCommand.Connect,
|
||||
addressType: ip.type == InternetAddressType.IPv4
|
||||
? SOCKSAddressType.IPv4
|
||||
: SOCKSAddressType.IPv6,
|
||||
address: ip.rawAddress,
|
||||
port: port,
|
||||
);
|
||||
await _start();
|
||||
await _waitForConnect;
|
||||
}
|
||||
|
||||
Future<void> close({bool keepOpen = true}) async {
|
||||
await _stateStream.close();
|
||||
if (!keepOpen) {
|
||||
await _sock.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _start() async {
|
||||
// send auth methods
|
||||
_setState(SOCKSState.Auth);
|
||||
//print(">> Version: 5, AuthMethods: $_auth");
|
||||
_sock.write([
|
||||
0x05,
|
||||
_auth.length,
|
||||
..._auth.map((v) => v.rawValue),
|
||||
]);
|
||||
|
||||
_sockSub = _sock.listen((RawSocketEvent ev) {
|
||||
switch (ev) {
|
||||
case RawSocketEvent.read:
|
||||
{
|
||||
final have = _sock.available();
|
||||
final data = _sock.read(have);
|
||||
if (data != null) {
|
||||
_handleRead(data);
|
||||
} else {
|
||||
print("========= sock read DATA is NULL");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RawSocketEvent.closed:
|
||||
{
|
||||
_sockSub?.cancel();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
print("AAAAAAAAAAAAA: unhandled raw socket event: $ev");
|
||||
// case RawSocketEvent.closed:
|
||||
// // TODO: Handle this case.
|
||||
// break;
|
||||
// case RawSocketEvent.read:
|
||||
// // TODO: Handle this case.
|
||||
// break;
|
||||
// case RawSocketEvent.readClosed:
|
||||
// // TODO: Handle this case.
|
||||
// break;
|
||||
// case RawSocketEvent.write:
|
||||
// // TODO: Handle this case.
|
||||
// break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _sendUsernamePassword(String uname, String password) {
|
||||
if (uname.length > 255 || password.length > 255) {
|
||||
throw "Username or Password is too long";
|
||||
}
|
||||
|
||||
final data = [
|
||||
RFC1929Version,
|
||||
uname.length,
|
||||
...const AsciiEncoder().convert(uname),
|
||||
password.length,
|
||||
...const AsciiEncoder().convert(password)
|
||||
];
|
||||
|
||||
//print(">> Sending $username:$password");
|
||||
_sock.write(data);
|
||||
}
|
||||
|
||||
void _handleRead(Uint8List data) async {
|
||||
if (state == SOCKSState.Auth) {
|
||||
if (data.length == 2) {
|
||||
final version = data[0];
|
||||
final auth = AuthMethods.fromValue(data[1]);
|
||||
|
||||
print("_handleRead << Version: $version, Auth: $auth");
|
||||
|
||||
switch (auth) {
|
||||
case AuthMethods.UsernamePassword:
|
||||
_setState(SOCKSState.AuthStarted);
|
||||
_sendUsernamePassword(username ?? '', password ?? '');
|
||||
break;
|
||||
case AuthMethods.NoAuth:
|
||||
_setState(SOCKSState.RequestReady);
|
||||
_writeRequest(_request!);
|
||||
|
||||
break;
|
||||
default:
|
||||
throw "No auth methods acceptable";
|
||||
}
|
||||
} else {
|
||||
throw "Expected 2 bytes";
|
||||
}
|
||||
} else if (_state == SOCKSState.AuthStarted) {
|
||||
if (_auth.contains(AuthMethods.UsernamePassword)) {
|
||||
final version = data[0];
|
||||
final status = data[1];
|
||||
|
||||
if (version != RFC1929Version || status != 0x00) {
|
||||
throw "Invalid username or password";
|
||||
} else {
|
||||
_setState(SOCKSState.RequestReady);
|
||||
_writeRequest(_request!);
|
||||
}
|
||||
}
|
||||
} else if (_state == SOCKSState.RequestReady) {
|
||||
if (data.length >= 10) {
|
||||
final version = data[0];
|
||||
final reply = SOCKSReply.fromValue(data[1]);
|
||||
//data[2] reserved
|
||||
final addrType = SOCKSAddressType.fromValue(data[3]);
|
||||
Uint8List addr;
|
||||
int port = 0;
|
||||
|
||||
switch (addrType) {
|
||||
case SOCKSAddressType.Domain:
|
||||
final len = data[4];
|
||||
addr = data.sublist(5, 5 + len);
|
||||
port = data[5 + len] << 8 | data[6 + len];
|
||||
break;
|
||||
case SOCKSAddressType.IPv4:
|
||||
addr = data.sublist(5, 9);
|
||||
port = data[9] << 8 | data[10];
|
||||
break;
|
||||
case SOCKSAddressType.IPv6:
|
||||
addr = data.sublist(5, 21);
|
||||
port = data[21] << 8 | data[22];
|
||||
break;
|
||||
}
|
||||
|
||||
print(
|
||||
"<< Version: $version, Reply: $reply, AddrType: $addrType, Addr: $addr, Port: $port");
|
||||
if (reply.rawValue == SOCKSReply.Success.rawValue) {
|
||||
_setState(SOCKSState.Connected);
|
||||
} else {
|
||||
throw reply;
|
||||
}
|
||||
} else {
|
||||
throw "Expected 10 bytes";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _writeRequest(SOCKSRequest req) {
|
||||
if (_state == SOCKSState.RequestReady) {
|
||||
final data = [
|
||||
req.version,
|
||||
req.command.rawValue,
|
||||
0x00,
|
||||
req.addressType.rawValue,
|
||||
if (req.addressType == SOCKSAddressType.Domain)
|
||||
req.address.lengthInBytes,
|
||||
...req.address,
|
||||
req.port >> 8,
|
||||
req.port & 0xF0,
|
||||
];
|
||||
|
||||
print(
|
||||
"_writeRequest >> Version: ${req.version}, Command: ${req.command}, AddrType: ${req.addressType}, Addr: ${req.getAddressString()}, Port: ${req.port}");
|
||||
_sock.write(data);
|
||||
} else {
|
||||
throw "Must be in RequestReady state, current state $_state";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import 'dart:async';
|
|||
import 'package:event_bus/event_bus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/models/epicbox_config_model.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/address_book_views/address_book_view.dart';
|
||||
|
@ -36,11 +37,14 @@ import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
|||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
/// [eventBus] should only be set during testing
|
||||
|
@ -306,6 +310,61 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return SettingsListButton(
|
||||
iconAssetName: Assets.svg.eye,
|
||||
title: "Clear electrumx cache",
|
||||
onPressed: () async {
|
||||
String? result;
|
||||
await showDialog<void>(
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title:
|
||||
"Are you sure you want to clear "
|
||||
"${coin.prettyName} electrumx cache?",
|
||||
onOkPressed: (value) {
|
||||
result = value;
|
||||
},
|
||||
leftButton: SecondaryButton(
|
||||
label: "Cancel",
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (result == "OK" && mounted) {
|
||||
await showLoading(
|
||||
whileFuture: Future.wait<void>(
|
||||
[
|
||||
Future.delayed(
|
||||
const Duration(
|
||||
milliseconds: 1500,
|
||||
),
|
||||
),
|
||||
DB.instance
|
||||
.clearSharedTransactionCache(
|
||||
coin: coin,
|
||||
),
|
||||
],
|
||||
),
|
||||
context: context,
|
||||
message: "Clearing cache...",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
if (coin == Coin.nano || coin == Coin.banano)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
|
|
|
@ -35,6 +35,7 @@ class TxIcon extends ConsumerWidget {
|
|||
|
||||
String _getAssetName(
|
||||
bool isCancelled, bool isReceived, bool isPending, IThemeAssets assets) {
|
||||
|
||||
if (!isReceived && transaction.subType == TransactionSubType.mint) {
|
||||
if (isCancelled) {
|
||||
return Assets.svg.anonymizeFailed;
|
||||
|
@ -47,7 +48,7 @@ class TxIcon extends ConsumerWidget {
|
|||
|
||||
if (isReceived) {
|
||||
if (isCancelled) {
|
||||
return assets.receive;
|
||||
return assets.receiveCancelled;
|
||||
}
|
||||
if (isPending) {
|
||||
return assets.receivePending;
|
||||
|
|
|
@ -358,8 +358,6 @@ class _TransactionDetailsViewState
|
|||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
print("THIS TRANSACTION IS $_transaction");
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
|
@ -1584,8 +1582,7 @@ class _TransactionDetailsViewState
|
|||
coin.requiredConfirmations,
|
||||
) ==
|
||||
false &&
|
||||
_transaction.isCancelled == false &&
|
||||
_transaction.type == TransactionType.outgoing)
|
||||
_transaction.isCancelled == false)
|
||||
? ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => Padding(
|
||||
|
|
|
@ -9,7 +9,7 @@ mkdir -p build
|
|||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/linux && ./build_monero_all.sh && ./build_sharedfile.sh ) &
|
||||
(cd ../../crypto_plugins/tor/scripts/linux && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libtor/scripts/linux && ./build_all.sh ) &
|
||||
|
||||
wait
|
||||
echo "Done building"
|
||||
|
|
|
@ -11,6 +11,7 @@ void main() {
|
|||
port: DefaultNodes.bitcoin.port,
|
||||
useSSL: true,
|
||||
connectionTimeout: const Duration(seconds: 40),
|
||||
proxyInfo: null, // TODO test for proxyInfo
|
||||
);
|
||||
|
||||
const jsonRequestString =
|
||||
|
@ -27,7 +28,8 @@ void main() {
|
|||
final jsonRPC = JsonRPC(
|
||||
host: "some.bad.address.thingdsfsdfsdaf",
|
||||
port: 3000,
|
||||
connectionTimeout: Duration(seconds: 10),
|
||||
connectionTimeout: const Duration(seconds: 10),
|
||||
proxyInfo: null,
|
||||
);
|
||||
|
||||
const jsonRequestString =
|
||||
|
@ -47,6 +49,7 @@ void main() {
|
|||
port: 3000,
|
||||
useSSL: false,
|
||||
connectionTimeout: const Duration(seconds: 1),
|
||||
proxyInfo: null,
|
||||
);
|
||||
|
||||
const jsonRequestString =
|
||||
|
|
|
@ -2286,8 +2286,8 @@ void main() {
|
|||
when(cachedClient.getAnonymitySet(
|
||||
groupId: "1", blockhash: "", coin: Coin.firo))
|
||||
.thenAnswer((_) async => GetAnonymitySetSampleData.data);
|
||||
when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo))
|
||||
.thenAnswer((_) async => List<String>.from(
|
||||
when(cachedClient.getUsedCoinSerials(coin: Coin.firo)).thenAnswer(
|
||||
(_) async => List<String>.from(
|
||||
GetUsedSerialsSampleData.serials['serials'] as List));
|
||||
|
||||
final firo = FiroWallet(
|
||||
|
|
Loading…
Reference in a new issue