Skip to content

Commit e6ccc9c

Browse files
authored
Merge pull request #1782 from SatoshiPortal/fix/mempool-server
custom mempool server fixes [has DB changes]
2 parents bfed9b8 + 4062429 commit e6ccc9c

40 files changed

Lines changed: 1240 additions & 181 deletions
Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import 'package:bb_mobile/core/mempool/domain/entities/mempool_server.dart';
2+
import 'package:bb_mobile/core/mempool/domain/value_objects/mempool_server_status.dart';
23

34
class MempoolServerDto {
45
final String url;
56
final bool isCustom;
67
final bool isTestnet;
78
final bool isLiquid;
9+
final bool enableSsl;
10+
final MempoolServerStatus status;
811

912
MempoolServerDto({
1013
required this.url,
1114
required this.isCustom,
1215
required this.isTestnet,
1316
required this.isLiquid,
17+
this.enableSsl = true,
18+
this.status = MempoolServerStatus.unknown,
1419
});
1520

1621
factory MempoolServerDto.fromEntity(MempoolServer entity) {
@@ -19,10 +24,29 @@ class MempoolServerDto {
1924
isCustom: entity.isCustom,
2025
isTestnet: entity.isTestnet,
2126
isLiquid: entity.isLiquid,
27+
enableSsl: entity.enableSsl,
2228
);
2329
}
2430

25-
String get fullUrl => 'https://$url';
31+
String get fullUrl => enableSsl ? 'https://$url' : 'http://$url';
32+
33+
MempoolServerDto copyWith({
34+
String? url,
35+
bool? isCustom,
36+
bool? isTestnet,
37+
bool? isLiquid,
38+
bool? enableSsl,
39+
MempoolServerStatus? status,
40+
}) {
41+
return MempoolServerDto(
42+
url: url ?? this.url,
43+
isCustom: isCustom ?? this.isCustom,
44+
isTestnet: isTestnet ?? this.isTestnet,
45+
isLiquid: isLiquid ?? this.isLiquid,
46+
enableSsl: enableSsl ?? this.enableSsl,
47+
status: status ?? this.status,
48+
);
49+
}
2650

2751
@override
2852
bool operator ==(Object other) =>
@@ -32,16 +56,20 @@ class MempoolServerDto {
3256
url == other.url &&
3357
isCustom == other.isCustom &&
3458
isTestnet == other.isTestnet &&
35-
isLiquid == other.isLiquid;
59+
isLiquid == other.isLiquid &&
60+
enableSsl == other.enableSsl &&
61+
status == other.status;
3662

3763
@override
3864
int get hashCode =>
3965
url.hashCode ^
4066
isCustom.hashCode ^
4167
isTestnet.hashCode ^
42-
isLiquid.hashCode;
68+
isLiquid.hashCode ^
69+
enableSsl.hashCode ^
70+
status.hashCode;
4371

4472
@override
4573
String toString() =>
46-
'MempoolServerDto(url: $url, isCustom: $isCustom, isTestnet: $isTestnet, isLiquid: $isLiquid)';
74+
'MempoolServerDto(url: $url, isCustom: $isCustom, isTestnet: $isTestnet, isLiquid: $isLiquid, enableSsl: $enableSsl, status: $status)';
4775
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
class SetCustomMempoolServerRequest {
22
final String url;
33
final bool isLiquid;
4+
final bool enableSsl;
45

56
SetCustomMempoolServerRequest({
67
required this.url,
78
required this.isLiquid,
9+
this.enableSsl = true,
810
});
911
}

lib/core/mempool/application/usecases/set_custom_mempool_server_usecase.dart

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:bb_mobile/core/mempool/application/dtos/requests/set_custom_mempool_server_request.dart';
22
import 'package:bb_mobile/core/mempool/domain/entities/mempool_server.dart';
3+
import 'package:bb_mobile/core/mempool/domain/errors/mempool_server_exception.dart';
34
import 'package:bb_mobile/core/mempool/domain/ports/mempool_server_validator_port.dart';
45
import 'package:bb_mobile/core/mempool/domain/repositories/mempool_server_repository.dart';
56
import 'package:bb_mobile/core/mempool/domain/value_objects/mempool_server_network.dart';
@@ -8,20 +9,36 @@ import 'package:bb_mobile/core/mempool/domain/ports/environment_port.dart';
89
import 'package:bb_mobile/core/settings/domain/settings_entity.dart';
910
import 'package:bb_mobile/core/utils/logger.dart';
1011

12+
enum SetCustomMempoolServerError {
13+
sameAsDefault,
14+
validationFailed,
15+
saveFailed,
16+
unexpected,
17+
}
18+
1119
class SetCustomMempoolServerResult {
1220
final bool isValid;
13-
final String? errorMessage;
21+
final SetCustomMempoolServerError? errorType;
22+
final MempoolValidationErrorType? validationErrorType;
1423

15-
SetCustomMempoolServerResult({required this.isValid, this.errorMessage});
24+
SetCustomMempoolServerResult({
25+
required this.isValid,
26+
this.errorType,
27+
this.validationErrorType,
28+
});
1629

1730
factory SetCustomMempoolServerResult.success() {
1831
return SetCustomMempoolServerResult(isValid: true);
1932
}
2033

21-
factory SetCustomMempoolServerResult.failure(String errorMessage) {
34+
factory SetCustomMempoolServerResult.failure(
35+
SetCustomMempoolServerError errorType, {
36+
MempoolValidationErrorType? validationErrorType,
37+
}) {
2238
return SetCustomMempoolServerResult(
2339
isValid: false,
24-
errorMessage: errorMessage,
40+
errorType: errorType,
41+
validationErrorType: validationErrorType,
2542
);
2643
}
2744
}
@@ -51,14 +68,20 @@ class SetCustomMempoolServerUsecase {
5168
isLiquid: request.isLiquid,
5269
);
5370

54-
final customUrl = NormalizedMempoolUrl(request.url);
71+
final customUrl = NormalizedMempoolUrl(
72+
request.url,
73+
enableSsl: request.enableSsl,
74+
);
5575

5676
final defaultServerResult = await _fetchDefaultServerSafely(network);
5777
if (defaultServerResult != null) {
58-
final defaultUrl = NormalizedMempoolUrl(defaultServerResult.url);
78+
final defaultUrl = NormalizedMempoolUrl(
79+
defaultServerResult.url,
80+
enableSsl: defaultServerResult.enableSsl,
81+
);
5982
if (customUrl == defaultUrl) {
6083
return SetCustomMempoolServerResult.failure(
61-
'This URL is the same as the default server. Please use a different URL.',
84+
SetCustomMempoolServerError.sameAsDefault,
6285
);
6386
}
6487
}
@@ -68,16 +91,22 @@ class SetCustomMempoolServerUsecase {
6891
final isValid = await _validator.validateServer(
6992
url: request.url,
7093
network: network,
94+
enableSsl: request.enableSsl,
7195
);
7296

7397
if (!isValid) {
7498
return SetCustomMempoolServerResult.failure(
75-
'Server validation failed: Unable to connect or invalid response',
99+
SetCustomMempoolServerError.validationFailed,
76100
);
77101
}
102+
} on MempoolServerValidationException catch (e) {
103+
return SetCustomMempoolServerResult.failure(
104+
SetCustomMempoolServerError.validationFailed,
105+
validationErrorType: e.errorType,
106+
);
78107
} catch (e) {
79108
return SetCustomMempoolServerResult.failure(
80-
'Validation error: ${e.toString()}',
109+
SetCustomMempoolServerError.unexpected,
81110
);
82111
}
83112
}
@@ -86,14 +115,16 @@ class SetCustomMempoolServerUsecase {
86115
final server = MempoolServer.createCustom(
87116
url: request.url,
88117
network: network,
118+
enableSsl: request.enableSsl,
89119
);
90120

91121
await _serverRepository.save(server);
92122

93123
return SetCustomMempoolServerResult.success();
94124
} catch (e) {
125+
log.warning('Failed to save mempool server: $e');
95126
return SetCustomMempoolServerResult.failure(
96-
'Failed to save server: ${e.toString()}',
127+
SetCustomMempoolServerError.saveFailed,
97128
);
98129
}
99130
}
@@ -105,9 +136,7 @@ class SetCustomMempoolServerUsecase {
105136
return await _serverRepository.fetchDefaultServer(network);
106137
} catch (e) {
107138
// log the error for debugging but don't throw.
108-
log.warning(
109-
'Could not fetch default mempool server for comparison: $e',
110-
);
139+
log.warning('Could not fetch default mempool server for comparison: $e');
111140
return null;
112141
}
113142
}
Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,78 @@
11
import 'package:bb_mobile/core/mempool/domain/errors/mempool_server_exception.dart';
22
import 'package:bb_mobile/core/mempool/domain/value_objects/mempool_server_network.dart';
3+
import 'package:bb_mobile/core/utils/mempool_url_parser.dart';
34

45
class MempoolServer {
56
final String _url;
67
final MempoolServerNetwork _network;
78
final bool _isCustom;
9+
final bool _enableSsl;
810

911
MempoolServer._({
1012
required String url,
1113
required MempoolServerNetwork network,
1214
required bool isCustom,
15+
required bool enableSsl,
1316
}) : _url = url,
1417
_network = network,
15-
_isCustom = isCustom;
18+
_isCustom = isCustom,
19+
_enableSsl = enableSsl;
1620

1721
factory MempoolServer.createCustom({
1822
required String url,
1923
required MempoolServerNetwork network,
24+
bool enableSsl = true,
2025
}) {
2126
final cleanedUrl = _validateAndCleanUrl(url);
22-
return MempoolServer._(url: cleanedUrl, network: network, isCustom: true);
27+
return MempoolServer._(
28+
url: cleanedUrl,
29+
network: network,
30+
isCustom: true,
31+
enableSsl: enableSsl,
32+
);
2333
}
2434

2535
factory MempoolServer.existing({
2636
required String url,
2737
required MempoolServerNetwork network,
2838
required bool isCustom,
39+
bool enableSsl = true,
2940
}) {
30-
return MempoolServer._(url: url, network: network, isCustom: isCustom);
41+
return MempoolServer._(
42+
url: url,
43+
network: network,
44+
isCustom: isCustom,
45+
enableSsl: enableSsl,
46+
);
3147
}
3248

3349
static String _validateAndCleanUrl(String url) {
34-
if (url.isEmpty) {
35-
throw InvalidMempoolUrlException('URL cannot be empty');
50+
try {
51+
final result = MempoolUrlParser.parse(url);
52+
return result.cleanUrl;
53+
} on MempoolUrlValidationError catch (e) {
54+
switch (e) {
55+
case MempoolUrlValidationError.empty:
56+
throw InvalidMempoolUrlException('URL cannot be empty');
57+
case MempoolUrlValidationError.hasPath:
58+
throw InvalidMempoolUrlException(
59+
'URL should not contain paths. Enter only the domain or IP address',
60+
);
61+
case MempoolUrlValidationError.invalidDomain:
62+
throw InvalidMempoolUrlException(
63+
'Invalid URL format: must be a valid domain',
64+
);
65+
case MempoolUrlValidationError.invalidFormat:
66+
throw InvalidMempoolUrlException('Invalid URL format');
67+
}
3668
}
37-
38-
// Remove protocol if present
39-
String cleanedUrl = url.replaceFirst(RegExp(r'^https?://'), '');
40-
41-
// Remove trailing slash
42-
cleanedUrl = cleanedUrl.replaceFirst(RegExp(r'/$'), '');
43-
44-
if (cleanedUrl.isEmpty) {
45-
throw InvalidMempoolUrlException('Invalid URL format');
46-
}
47-
48-
// Basic domain validation - should contain at least one dot or be localhost
49-
if (!cleanedUrl.contains('.') && !cleanedUrl.startsWith('localhost')) {
50-
throw InvalidMempoolUrlException(
51-
'Invalid URL format: must be a valid domain',
52-
);
53-
}
54-
55-
return cleanedUrl;
5669
}
5770

5871
String get url => _url;
5972
MempoolServerNetwork get network => _network;
6073
bool get isCustom => _isCustom;
61-
String get fullUrl => 'https://$_url';
74+
bool get enableSsl => _enableSsl;
75+
String get fullUrl => _enableSsl ? 'https://$_url' : 'http://$_url';
6276

6377
bool get isTestnet => _network.isTestnet;
6478
bool get isLiquid => _network.isLiquid;
@@ -70,12 +84,17 @@ class MempoolServer {
7084
runtimeType == other.runtimeType &&
7185
_url == other._url &&
7286
_network == other._network &&
73-
_isCustom == other._isCustom;
87+
_isCustom == other._isCustom &&
88+
_enableSsl == other._enableSsl;
7489

7590
@override
76-
int get hashCode => _url.hashCode ^ _network.hashCode ^ _isCustom.hashCode;
91+
int get hashCode =>
92+
_url.hashCode ^
93+
_network.hashCode ^
94+
_isCustom.hashCode ^
95+
_enableSsl.hashCode;
7796

7897
@override
7998
String toString() =>
80-
'MempoolServer(url: $_url, network: $_network, isCustom: $_isCustom)';
99+
'MempoolServer(url: $_url, network: $_network, isCustom: $_isCustom, enableSsl: $_enableSsl)';
81100
}

lib/core/mempool/domain/errors/mempool_server_exception.dart

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,25 @@ class InvalidMempoolUrlException implements Exception {
77
String toString() => 'InvalidMempoolUrlException: $message';
88
}
99

10+
enum MempoolValidationErrorType {
11+
connectionTimeout,
12+
hostNotFound,
13+
torNotRunning,
14+
connectionError,
15+
notMempoolServer,
16+
serverUnavailable,
17+
serverError,
18+
invalidResponse,
19+
unexpected,
20+
}
21+
1022
class MempoolServerValidationException implements Exception {
11-
final String message;
23+
final MempoolValidationErrorType errorType;
1224
final Object? cause;
1325

14-
MempoolServerValidationException(this.message, [this.cause]);
26+
MempoolServerValidationException(this.errorType, [this.cause]);
1527

1628
@override
1729
String toString() =>
18-
'MempoolServerValidationException: $message${cause != null ? ' (cause: $cause)' : ''}';
30+
'MempoolServerValidationException: $errorType${cause != null ? ' (cause: $cause)' : ''}';
1931
}

lib/core/mempool/domain/ports/mempool_server_validator_port.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ abstract class MempoolServerValidatorPort {
44
Future<bool> validateServer({
55
required String url,
66
required MempoolServerNetwork network,
7+
bool enableSsl = true,
78
});
89
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
enum MempoolServerStatus {
2+
online,
3+
offline,
4+
checking,
5+
unknown;
6+
7+
bool get isOnline => this == MempoolServerStatus.online;
8+
bool get isOffline => this == MempoolServerStatus.offline;
9+
bool get isChecking => this == MempoolServerStatus.checking;
10+
bool get isUnknown => this == MempoolServerStatus.unknown;
11+
}

0 commit comments

Comments
 (0)