Skip to content

CW-519 Enable built-in Tor #1950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
71d1b98
tor wip
MrCyjaneK Jan 9, 2025
3604eb7
Enable tor on iOS
MrCyjaneK Jan 13, 2025
dfdc92e
Prevent app lag when node is exceptionally slow (usually over tor)
MrCyjaneK Jan 14, 2025
e564cfd
fix: logic in daemonBlockchainHeight refresh
MrCyjaneK Jan 15, 2025
21d6ae6
Pin ledger_flutter_plus dependency to fix builds
MrCyjaneK Jan 15, 2025
eee8cd2
bump arti version
MrCyjaneK Jan 15, 2025
0b8a39d
wip
MrCyjaneK Jan 16, 2025
5649177
add single httpclient
MrCyjaneK Jan 16, 2025
ecb59e6
Route everything I was able to catch trough the built-in tor node
MrCyjaneK Jan 16, 2025
9475b39
Enable proxy for http.Client [run tests]
MrCyjaneK Jan 17, 2025
a0744d7
add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests]
MrCyjaneK Feb 3, 2025
767b3a1
remove log pollution, cleanup [skip slack]
MrCyjaneK Feb 3, 2025
4e813ab
fix tests not working in latest main [skip slack] [run tests]
MrCyjaneK Feb 3, 2025
c048689
remove cw_wownero import
MrCyjaneK Apr 1, 2025
499d6fc
fix build issues
MrCyjaneK Apr 1, 2025
253daf7
migrate all remaining calls to use ProxyWrapper
MrCyjaneK Apr 3, 2025
0ad0591
fix tor background sync (will work on test builds after #2142 is merg…
MrCyjaneK Apr 3, 2025
a9de1a7
wip [skip ci]
MrCyjaneK Apr 3, 2025
b12ac72
relicense to GPLv3 add socks5 license, build fixes
MrCyjaneK Apr 23, 2025
6cac4a8
use ProxyWrapper instead of http in robinhood
MrCyjaneK Apr 25, 2025
e2ac4c7
Revert "relicense to GPLv3"
MrCyjaneK Apr 25, 2025
35a9349
feat(cw_bitcoin): support socks proxy and CakeTor
MrCyjaneK Apr 25, 2025
80dbb6f
fix(tor): migrate OCP and EVM over to ProxyWrapper()
MrCyjaneK Apr 25, 2025
d3656da
chore: cleanup
MrCyjaneK Apr 30, 2025
64bff6d
fix: tor switch properly dismisses fullscreen loading dialog
MrCyjaneK Apr 30, 2025
64c1073
fix(tor): status check for xmr/wow/zano
MrCyjaneK May 1, 2025
ab570cf
fix(tor): onramper request fix
MrCyjaneK May 18, 2025
504d67f
fix(api): ServicesResponse is now being cached and doesn't fetch data…
MrCyjaneK May 22, 2025
e6bcf63
[skip ci] remove print
MrCyjaneK May 22, 2025
570a0fe
address comments from review
MrCyjaneK May 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/no_http_imports.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: No http imports

on: [pull_request]

jobs:
PR_test_build:
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v4
- name: Check for http package usage
if: github.event_name == 'pull_request'
run: |
GIT_GREP_OUT="$(git grep package:http | (grep .dart: || test $? = 1) | (grep -v proxy_wrapper.dart || test $? = 1) | (grep -v very_insecure_http_do_not_use || test $? = 1) || true)"
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT"
echo "There are .dart files which use http imports"
echo "Using http package breaks proxy integration"
echo "Please use ProxyWrapper.getHttpClient() from package:cw_core/utils/proxy_wrapper.dart"
exit 1

2 changes: 1 addition & 1 deletion .github/workflows/no_print_in_dart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ jobs:
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT"
echo "There are .dart files which use print() statements"
echo "Please use printV from package: cw_core/utils/print_verbose.dart"
echo "Please use printV from package:cw_core/utils/print_verbose.dart"
exit 1
18 changes: 6 additions & 12 deletions cw_bitcoin/lib/electrum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';

Expand Down Expand Up @@ -42,7 +44,7 @@ class ElectrumClient {
static const aliveTimerDuration = Duration(seconds: 4);

bool get isConnected => _isConnected;
Socket? socket;
ProxySocket? socket;
void Function(ConnectionStatus)? onConnectionStatusChange;
int _id;
final Map<String, SocketTask> _tasks;
Expand Down Expand Up @@ -72,18 +74,11 @@ class ElectrumClient {
} catch (_) {}
socket = null;

final ssl = !(useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum")));
try {
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
socket = await Socket.connect(host, port, timeout: connectionTimeout);
} else {
socket = await SecureSocket.connect(
host,
port,
timeout: connectionTimeout,
onBadCertificate: (_) => true,
);
}
socket = await ProxyWrapper().getSocksSocket(ssl, host, port, connectionTimeout: connectionTimeout);
} catch (e) {
printV("connect: $e");
if (e is HandshakeException) {
useSSL = !(useSSL ?? false);
}
Expand All @@ -105,7 +100,6 @@ class ElectrumClient {

// use ping to determine actual connection status since we could've just not timed out yet:
// _setConnectionStatus(ConnectionStatus.connected);

socket!.listen(
(Uint8List event) {
try {
Expand Down
47 changes: 24 additions & 23 deletions cw_bitcoin/lib/electrum_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import 'dart:io';
import 'dart:isolate';

import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
Expand Down Expand Up @@ -49,7 +49,6 @@ import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart';
import 'package:hex/hex.dart';
import 'package:http/http.dart' as http;

part 'electrum_wallet.g.dart';

Expand Down Expand Up @@ -493,11 +492,12 @@ abstract class ElectrumWalletBase
Future<void> updateFeeRates() async {
if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) {
try {
final response = await http
.get(Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended"))
.timeout(Duration(seconds: 5));

final result = json.decode(response.body) as Map<String, dynamic>;
final req = await ProxyWrapper().getHttpClient()
.getUrl(Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended"))
.timeout(Duration(seconds: 15));
final response = await req.close();
final stringData = await response.transform(utf8.decoder).join();
final result = json.decode(stringData) as Map<String, dynamic>;
final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
int fastFee = (result['fastestFee'] as num?)?.toInt() ?? 0;
Expand Down Expand Up @@ -1880,24 +1880,25 @@ abstract class ElectrumWalletBase

if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) {
try {
final blockHash = await http.get(
Uri.parse(
"https://mempool.cakewallet.com/api/v1/block-height/$height",
),
);
final req = await ProxyWrapper().getHttpClient()
.getUrl(Uri.parse("https://mempool.cakewallet.com/api/v1/block-height/$height"))
.timeout(Duration(seconds: 15));
final blockHash = await req.close();
final stringData = await blockHash.transform(utf8.decoder).join();

if (blockHash.statusCode == 200 &&
blockHash.body.isNotEmpty &&
jsonDecode(blockHash.body) != null) {
final blockResponse = await http.get(
Uri.parse(
"https://mempool.cakewallet.com/api/v1/block/${blockHash.body}",
),
);
if (blockResponse.statusCode == 200 &&
blockResponse.body.isNotEmpty &&
jsonDecode(blockResponse.body)['timestamp'] != null) {
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
stringData.isNotEmpty &&
jsonDecode(stringData) != null) {
final blockResponseReq = await ProxyWrapper().getHttpClient()
.getUrl(Uri.parse("https://mempool.cakewallet.com/api/v1/block/${stringData}"))
.timeout(Duration(seconds: 15));
final blockResponseRes = await blockResponseReq.close();
final blockResponse = await blockResponseRes.transform(utf8.decoder).join();

if (blockResponseRes == 200 &&
blockResponse.isNotEmpty &&
jsonDecode(blockResponse)['timestamp'] != null) {
time = int.parse(jsonDecode(blockResponse)['timestamp'].toString());
}
}
} catch (_) {}
Expand Down
22 changes: 10 additions & 12 deletions cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
import 'package:cw_bitcoin/psbt/signer.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:http/http.dart' as http;
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:payjoin_flutter/bitcoin_ffi.dart';
import 'package:payjoin_flutter/common.dart';
import 'package:payjoin_flutter/receive.dart';
import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
import 'package:http/http.dart' as very_insecure_http_do_not_use; // for eerrors

enum PayjoinReceiverRequestTypes {
processOriginalTx,
Expand All @@ -27,7 +28,7 @@ class PayjoinReceiverWorker {
final pendingRequests = <String, Completer<dynamic>>{};

PayjoinReceiverWorker._(this.sendPort);

static final client = ProxyWrapper().getHttpIOClient();
static Future<void> run(List<Object> args) async {
await pj.core.init();

Expand All @@ -41,11 +42,10 @@ class PayjoinReceiverWorker {
receivePort.listen(worker.handleMessage);

try {
final httpClient = http.Client();
final receiver = Receiver.fromJson(receiverJson);

final uncheckedProposal =
await worker.receiveUncheckedProposal(httpClient, receiver);
await worker.receiveUncheckedProposal(receiver);

final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast();
sendPort.send({
Expand All @@ -56,14 +56,14 @@ class PayjoinReceiverWorker {
final payjoinProposal = await worker.processPayjoinProposal(
uncheckedProposal,
);
final psbt = await worker.sendFinalProposal(httpClient, payjoinProposal);
final psbt = await worker.sendFinalProposal(payjoinProposal);
sendPort.send({
'type': PayjoinReceiverRequestTypes.proposalSent,
'psbt': psbt,
});
} catch (e) {
if (e is HttpException ||
(e is http.ClientException &&
(e is very_insecure_http_do_not_use.ClientException &&
e.message.contains("Software caused connection abort"))) {
sendPort.send(PayjoinSessionError.recoverable(e.toString()));
} else {
Expand Down Expand Up @@ -97,15 +97,14 @@ class PayjoinReceiverWorker {
return completer.future;
}

Future<UncheckedProposal> receiveUncheckedProposal(
http.Client httpClient, Receiver session) async {
Future<UncheckedProposal> receiveUncheckedProposal(Receiver session) async {
while (true) {
printV("Polling for Proposal (${session.id()})");
final extractReq = await session.extractReq();
final request = extractReq.$1;

final url = Uri.parse(request.url.asString());
final httpRequest = await httpClient.post(url,
final httpRequest = await client.post(url,
headers: {'Content-Type': request.contentType}, body: request.body);

final proposal = await session.processRes(
Expand All @@ -114,13 +113,12 @@ class PayjoinReceiverWorker {
}
}

Future<String> sendFinalProposal(
http.Client httpClient, PayjoinProposal finalProposal) async {
Future<String> sendFinalProposal(PayjoinProposal finalProposal) async {
final req = await finalProposal.extractV2Req();
final proposalReq = req.$1;
final proposalCtx = req.$2;

final request = await httpClient.post(
final request = await client.post(
Uri.parse(proposalReq.url.asString()),
headers: {"Content-Type": proposalReq.contentType},
body: proposalReq.body,
Expand Down
20 changes: 10 additions & 10 deletions cw_bitcoin/lib/payjoin/payjoin_send_worker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'dart:isolate';
import 'package:cw_bitcoin/payjoin/manager.dart';
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:http/http.dart' as http;
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:payjoin_flutter/common.dart';
import 'package:payjoin_flutter/send.dart';
import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
Expand Down Expand Up @@ -42,19 +42,19 @@ class PayjoinSenderWorker {
sendPort.send(e);
}
}
final client = ProxyWrapper().getHttpIOClient();

/// Run a payjoin sender (V2 protocol first, fallback to V1).
Future<String> runSender(Sender sender) async {
final httpClient = http.Client();

try {
return await _runSenderV2(sender, httpClient);
return await _runSenderV2(sender);
} catch (e) {
printV(e);
if (e is PayjoinException &&
// TODO condition on error type instead of message content
e.message?.contains('parse receiver public key') == true) {
return await _runSenderV1(sender, httpClient);
return await _runSenderV1(sender);
} else if (e is HttpException) {
printV(e);
throw Exception(PayjoinSessionError.recoverable(e.toString()));
Expand All @@ -65,13 +65,13 @@ class PayjoinSenderWorker {
}

/// Attempt to send payjoin using the V2 of the protocol.
Future<String> _runSenderV2(Sender sender, http.Client httpClient) async {
Future<String> _runSenderV2(Sender sender) async {
try {
final postRequest = await sender.extractV2(
ohttpProxyUrl: await PayjoinManager.randomOhttpRelayUrl(),
);

final postResult = await _postRequest(httpClient, postRequest.$1);
final postResult = await _postRequest(postRequest.$1);
final getContext =
await postRequest.$2.processResponse(response: postResult);

Expand All @@ -83,7 +83,7 @@ class PayjoinSenderWorker {
final getRequest = await getContext.extractReq(
ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(),
);
final getRes = await _postRequest(httpClient, getRequest.$1);
final getRes = await _postRequest(getRequest.$1);
final proposalPsbt = await getContext.processResponse(
response: getRes,
ohttpCtx: getRequest.$2,
Expand All @@ -97,10 +97,10 @@ class PayjoinSenderWorker {
}

/// Attempt to send payjoin using the V1 of the protocol.
Future<String> _runSenderV1(Sender sender, http.Client httpClient) async {
Future<String> _runSenderV1(Sender sender) async {
try {
final postRequest = await sender.extractV1();
final response = await _postRequest(httpClient, postRequest.$1);
final response = await _postRequest(postRequest.$1);

sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted});

Expand All @@ -110,7 +110,7 @@ class PayjoinSenderWorker {
}
}

Future<List<int>> _postRequest(http.Client client, Request req) async {
Future<List<int>> _postRequest(Request req) async {
final httpRequest = await client.post(Uri.parse(req.url.asString()),
headers: {'Content-Type': req.contentType}, body: req.body);

Expand Down
32 changes: 25 additions & 7 deletions cw_bitcoin/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -935,11 +935,21 @@ packages:
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
path: "."
ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3
url: "https://github.com/cake-tech/socks_dart.git"
source: git
version: "1.0.4"
socks_socket:
dependency: "direct main"
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description:
Expand Down Expand Up @@ -1033,10 +1043,18 @@ packages:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
version: "1.0.1"
tor:
dependency: transitive
description:
name: tor
sha256: eeed80e5c912a1806c2f81825c12e84f4dc5a0b50aebedea59e3a8ba53df3142
url: "https://pub.dev"
source: hosted
version: "0.0.8"
tuple:
dependency: transitive
description:
Expand Down
4 changes: 4 additions & 0 deletions cw_bitcoin/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ dependencies:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-litecoin
socks_socket:
git:
url: https://github.com/sneurlax/socks_socket
ref: e6232c53c1595469931ababa878759a067c02e94

dev_dependencies:
flutter_test:
Expand Down
Loading
Loading