Merge pull request #382 from cypherstack/fix/epic-create

Fix epic wallet creation epicbox config error by adding and using an EpicBoxConfigModel and an EpicBoxServerModel
This commit is contained in:
Diego Salazar 2023-03-03 08:55:06 -07:00 committed by GitHub
commit aadbeff488
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 415 additions and 52 deletions

View file

@ -0,0 +1,118 @@
import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:stackwallet/models/epicbox_server_model.dart';
part 'type_adaptors/epicbox_config_model.g.dart';
// @HiveType(typeId: 72)
class EpicBoxConfigModel {
// @HiveField(1)
final String host;
// @HiveField(2)
final int? port;
// @HiveField(3)
final bool? protocolInsecure;
// @HiveField(4)
final int? addressIndex;
// // @HiveField(5)
// final String? id;
// // @HiveField(6)
// final String? name;
EpicBoxConfigModel({
required this.host,
this.port,
this.protocolInsecure,
this.addressIndex,
// this.id,
// this.name,
});
EpicBoxConfigModel copyWith({
int? port,
bool? protocolInsecure,
int? addressIndex,
// String? id,
// String? name,
}) {
return EpicBoxConfigModel(
host: host,
port: this.port ?? 443,
protocolInsecure: this.protocolInsecure ?? false,
addressIndex: this.addressIndex ?? 0,
// id: id ?? this.id,
// name: name ?? this.name,
);
}
Map<String, dynamic> toMap() {
Map<String, dynamic> map = {};
map['epicbox_domain'] = host;
map['epicbox_port'] = port;
map['epicbox_protocol_insecure'] = protocolInsecure;
map['epicbox_address_index'] = addressIndex;
// map['id'] = id;
// map['name'] = name;
return map;
}
Map<String, dynamic> toJson() {
return {
'epicbox_domain': host,
'epicbox_port': port,
'epicbox_protocol_insecure': protocolInsecure,
'epicbox_address_index': addressIndex,
// 'id': id,
// 'name': name,
};
}
@override
String toString() {
return json.encode(toJson());
}
static EpicBoxConfigModel fromString(String epicBoxConfigString) {
dynamic _epicBox = json.decode(epicBoxConfigString);
// handle old epicbox config formats
final oldDomain = _epicBox["domain"] ?? "empty";
if (oldDomain != "empty") {
_epicBox['epicbox_domain'] = _epicBox['domain'];
}
final oldPort = _epicBox["port"] ?? "empty";
if (oldPort != "empty") {
_epicBox['epicbox_port'] = _epicBox['port'];
}
final oldProtocolInsecure = _epicBox["protocol_insecur"] ?? "empty";
if (oldProtocolInsecure != "empty") {
_epicBox['epicbox_protocol_insecure'] = _epicBox['protocol_insecur'];
}
final oldAddressIndex = _epicBox["address_index"] ?? "empty";
if (oldAddressIndex != "empty") {
_epicBox['epicbox_address_index'] = _epicBox['address_index'];
}
return EpicBoxConfigModel(
host: _epicBox['epicbox_domain'] as String,
port: _epicBox['epicbox_port'] as int,
protocolInsecure: _epicBox['epicbox_protocol_insecure'] as bool,
addressIndex: _epicBox['epicbox_address_index'] as int,
// name: fields[5] as String,
// id: fields[6] as String,
);
}
static EpicBoxConfigModel fromServer(EpicBoxServerModel server,
{bool? protocolInsecure, int? addressIndex}) {
return EpicBoxConfigModel(
host: server.host,
port: server.port ?? 443,
protocolInsecure: protocolInsecure ?? false,
addressIndex: addressIndex ?? 0,
// name: fields[5] as String,
// id: fields[6] as String,
);
}
}

View file

@ -0,0 +1,83 @@
import 'package:hive/hive.dart';
part 'type_adaptors/epicbox_server_model.g.dart';
// @HiveType(typeId: 71)
class EpicBoxServerModel {
// @HiveField(0)
final String id;
// @HiveField(1)
final String host;
// @HiveField(2)
final int? port;
// @HiveField(3)
final String name;
// @HiveField(4)
final bool? useSSL;
// @HiveField(5)
final bool? enabled;
// @HiveField(6)
final bool? isFailover;
// @HiveField(7)
final bool? isDown;
EpicBoxServerModel({
required this.id,
required this.host,
this.port,
required this.name,
this.useSSL,
this.enabled,
this.isFailover,
this.isDown,
});
EpicBoxServerModel copyWith({
String? host,
int? port,
String? name,
bool? useSSL,
bool? enabled,
bool? isFailover,
bool? isDown,
}) {
return EpicBoxServerModel(
id: id,
host: host ?? this.host,
port: port ?? this.port,
name: name ?? this.name,
useSSL: useSSL ?? this.useSSL,
enabled: enabled ?? this.enabled,
isFailover: isFailover ?? this.isFailover,
isDown: isDown ?? this.isDown,
);
}
Map<String, dynamic> toMap() {
Map<String, dynamic> map = {};
map['id'] = id;
map['host'] = host;
map['port'] = port;
map['name'] = name;
map['useSSL'] = useSSL;
map['enabled'] = enabled;
map['isFailover'] = isFailover;
map['isDown'] = isDown;
return map;
}
bool get isDefault => id.startsWith("default_");
Map<String, dynamic> toJson() {
return {
'id': id,
'host': host,
'port': port,
'name': name,
'useSSL': useSSL,
'enabled': enabled,
'isFailover': isFailover,
'isDown': isDown,
};
}
}

View file

@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of '../epicbox_config_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class EpicBoxConfigModelAdapter extends TypeAdapter<EpicBoxConfigModel> {
@override
final int typeId = 72;
@override
EpicBoxConfigModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return EpicBoxConfigModel(
host: fields[1] as String,
port: fields[2] as int,
protocolInsecure: fields[3] as bool,
addressIndex: fields[4] as int,
// name: fields[5] as String,
// id: fields[6] as String,
);
}
@override
void write(BinaryWriter writer, EpicBoxConfigModel obj) {
writer
..writeByte(4)
..writeByte(0)
..write(obj.host)
..writeByte(1)
..write(obj.port)
..writeByte(2)
..write(obj.protocolInsecure)
..writeByte(3)
..write(obj.addressIndex);
// ..writeByte(4)
// ..write(obj.id)
// ..writeByte(5)
// ..write(obj.name)
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is EpicBoxConfigModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View file

@ -0,0 +1,62 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of '../epicbox_server_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class EpicBoxServerModelAdapter extends TypeAdapter<EpicBoxServerModel> {
@override
final int typeId = 71;
@override
EpicBoxServerModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return EpicBoxServerModel(
host: fields[1] as String,
port: fields[2] as int,
name: fields[3] as String,
id: fields[0] as String,
useSSL: fields[4] as bool,
enabled: fields[5] as bool,
isFailover: fields[6] as bool,
isDown: fields[7] as bool,
);
}
@override
void write(BinaryWriter writer, EpicBoxServerModel obj) {
writer
..writeByte(8)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.host)
..writeByte(2)
..write(obj.port)
..writeByte(3)
..write(obj.name)
..writeByte(4)
..write(obj.useSSL)
..writeByte(5)
..write(obj.enabled)
..writeByte(6)
..write(obj.isFailover)
..writeByte(7)
..write(obj.isDown);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is EpicBoxServerModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View file

@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:event_bus/event_bus.dart'; import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/epicbox_config_model.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/address_book_views/address_book_view.dart';
import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart';
@ -356,10 +356,9 @@ class _EpiBoxInfoFormState extends ConsumerState<EpicBoxInfoForm> {
.getManager(widget.walletId) .getManager(widget.walletId)
.wallet as EpicCashWallet; .wallet as EpicCashWallet;
wallet.getEpicBoxConfig().then((value) { wallet.getEpicBoxConfig().then((EpicBoxConfigModel epicBoxConfig) {
final config = jsonDecode(value); hostController.text = epicBoxConfig.host;
hostController.text = config["domain"] as String; portController.text = "${epicBoxConfig.port ?? 443}";
portController.text = (config["port"] as int).toString();
}); });
super.initState(); super.initState();
} }

View file

@ -11,6 +11,7 @@ import 'package:mutex/mutex.dart';
import 'package:stack_wallet_backup/generate_password.dart'; import 'package:stack_wallet_backup/generate_password.dart';
import 'package:stackwallet/db/main_db.dart'; import 'package:stackwallet/db/main_db.dart';
import 'package:stackwallet/models/balance.dart'; import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/epicbox_config_model.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart';
@ -27,6 +28,7 @@ import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/default_epicboxes.dart';
import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
@ -447,7 +449,7 @@ class EpicCashWallet extends CoinServiceAPI
Future<String> confirmSend({required Map<String, dynamic> txData}) async { Future<String> confirmSend({required Map<String, dynamic> txData}) async {
try { try {
final wallet = await _secureStore.read(key: '${_walletId}_wallet'); final wallet = await _secureStore.read(key: '${_walletId}_wallet');
final epicboxConfig = await getEpicBoxConfig(); EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
// TODO determine whether it is worth sending change to a change address. // TODO determine whether it is worth sending change to a change address.
dynamic message; dynamic message;
@ -456,10 +458,8 @@ class EpicCashWallet extends CoinServiceAPI
if (!receiverAddress.startsWith("http://") || if (!receiverAddress.startsWith("http://") ||
!receiverAddress.startsWith("https://")) { !receiverAddress.startsWith("https://")) {
final decoded = json.decode(epicboxConfig);
bool isEpicboxConnected = await testEpicboxServer( bool isEpicboxConnected = await testEpicboxServer(
decoded["epicbox_domain"] as String, epicboxConfig.host, epicboxConfig.port ?? 443);
decoded["epicbox_port"] as int);
if (!isEpicboxConnected) { if (!isEpicboxConnected) {
throw Exception("Failed to send TX : Unable to reach epicbox server"); throw Exception("Failed to send TX : Unable to reach epicbox server");
} }
@ -496,7 +496,7 @@ class EpicCashWallet extends CoinServiceAPI
"amount": txData['recipientAmt'], "amount": txData['recipientAmt'],
"address": txData['addresss'], "address": txData['addresss'],
"secretKeyIndex": 0, "secretKeyIndex": 0,
"epicboxConfig": epicboxConfig!, "epicboxConfig": epicboxConfig,
"minimumConfirmations": MINIMUM_CONFIRMATIONS, "minimumConfirmations": MINIMUM_CONFIRMATIONS,
}, name: walletName); }, name: walletName);
@ -558,13 +558,13 @@ class EpicCashWallet extends CoinServiceAPI
if (address == null) { if (address == null) {
final wallet = await _secureStore.read(key: '${_walletId}_wallet'); final wallet = await _secureStore.read(key: '${_walletId}_wallet');
final epicboxConfig = await getEpicBoxConfig(); EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
String? walletAddress; String? walletAddress;
await m.protect(() async { await m.protect(() async {
walletAddress = await compute( walletAddress = await compute(
_initGetAddressInfoWrapper, _initGetAddressInfoWrapper,
Tuple3(wallet!, index, epicboxConfig!), Tuple3(wallet!, index, epicboxConfig.toString()),
); );
}); });
Logging.instance Logging.instance
@ -718,12 +718,13 @@ class EpicCashWallet extends CoinServiceAPI
int index = 0; int index = 0;
Logging.instance.log("This index is $index", level: LogLevel.Info); Logging.instance.log("This index is $index", level: LogLevel.Info);
final epicboxConfig = await getEpicBoxConfig(); EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
String? walletAddress; String? walletAddress;
await m.protect(() async { await m.protect(() async {
walletAddress = await compute( walletAddress = await compute(
_initGetAddressInfoWrapper, _initGetAddressInfoWrapper,
Tuple3(wallet!, index, epicboxConfig!), Tuple3(wallet!, index, epicboxConfig.toString()),
); );
}); });
Logging.instance Logging.instance
@ -761,14 +762,14 @@ class EpicCashWallet extends CoinServiceAPI
final String password = generatePassword(); final String password = generatePassword();
String stringConfig = await getConfig(); String stringConfig = await getConfig();
String epicboxConfig = await getEpicBoxConfig(); EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
await _secureStore.write( await _secureStore.write(
key: '${_walletId}_mnemonic', value: mnemonicString); key: '${_walletId}_mnemonic', value: mnemonicString);
await _secureStore.write(key: '${_walletId}_config', value: stringConfig); await _secureStore.write(key: '${_walletId}_config', value: stringConfig);
await _secureStore.write(key: '${_walletId}_password', value: password); await _secureStore.write(key: '${_walletId}_password', value: password);
await _secureStore.write( await _secureStore.write(
key: '${_walletId}_epicboxConfig', value: epicboxConfig); key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString());
String name = _walletId; String name = _walletId;
@ -984,6 +985,7 @@ class EpicCashWallet extends CoinServiceAPI
} }
Future<bool> testEpicboxServer(String host, int port) async { Future<bool> testEpicboxServer(String host, int port) async {
// TODO use an EpicBoxServerModel as the only param
final websocketConnectionUri = 'wss://$host:$port'; final websocketConnectionUri = 'wss://$host:$port';
const connectionOptions = SocketConnectionOptions( const connectionOptions = SocketConnectionOptions(
pingIntervalMs: 3000, pingIntervalMs: 3000,
@ -1026,33 +1028,49 @@ class EpicCashWallet extends CoinServiceAPI
return isConnected; return isConnected;
} }
Future<String> getEpicBoxConfig() async { Future<EpicBoxConfigModel> getEpicBoxConfig() async {
EpicBoxConfigModel? _epicBoxConfig;
// read epicbox config from secure store
String? storedConfig = String? storedConfig =
await _secureStore.read(key: '${_walletId}_epicboxConfig'); await _secureStore.read(key: '${_walletId}_epicboxConfig');
// we should move to storing the primary server model like we do with nodes, and build the config from that (see epic-mobile)
// EpicBoxServerModel? _epicBox = epicBox ??
// DB.instance.get<EpicBoxServerModel>(
// boxName: DB.boxNamePrimaryEpicBox, key: 'primary');
// Logging.instance.log(
// "Read primary Epic Box config: ${jsonEncode(_epicBox)}",
// level: LogLevel.Info);
if (storedConfig == null) { if (storedConfig == null) {
storedConfig = DefaultNodes.defaultEpicBoxConfig; // if no config stored, use the default epicbox server as config
_epicBoxConfig =
EpicBoxConfigModel.fromServer(DefaultEpicBoxes.defaultEpicBoxServer);
} else { } else {
dynamic decoded = json.decode(storedConfig!); // if a config is stored, test it
final domain = decoded["domain"] ?? "empty";
if (domain != "empty") { _epicBoxConfig = EpicBoxConfigModel.fromString(
//If we have the old invalid config, use the new default one storedConfig); // fromString handles checking old config formats
// new storage format stores domain under "epicbox_domain", old storage format used "domain"
storedConfig = DefaultNodes.defaultEpicBoxConfig;
} }
}
final decoded = json.decode(storedConfig);
//Check Epicbox is up before returning it
bool isEpicboxConnected = await testEpicboxServer( bool isEpicboxConnected = await testEpicboxServer(
decoded["epicbox_domain"] as String, decoded["epicbox_port"] as int); _epicBoxConfig.host, _epicBoxConfig.port ?? 443);
if (!isEpicboxConnected) { if (!isEpicboxConnected) {
//Default Epicbox is not connected, Defaulting to Europe // default Epicbox is not connected, default to Europe
storedConfig = json.encode(DefaultNodes.epicBoxConfigEUR); _epicBoxConfig = EpicBoxConfigModel.fromServer(DefaultEpicBoxes.europe);
// TODO test this connection before returning it, iterating through the list of default Epic Box servers
// example of selecting another random server from the default list
// alternative servers: copy list of all default EB servers but remove the default default
// List<EpicBoxServerModel> alternativeServers = DefaultEpicBoxes.all;
// alternativeServers.removeWhere((opt) => opt.name == DefaultEpicBoxes.defaultEpicBoxServer.name);
// alternativeServers.shuffle(); // randomize which server is used
// _epicBoxConfig = EpicBoxConfigModel.fromServer(alternativeServers.first);
// TODO test this connection before returning it
} }
return storedConfig; return _epicBoxConfig;
} }
Future<String> getRealConfig() async { Future<String> getRealConfig() async {
@ -1162,14 +1180,14 @@ class EpicCashWallet extends CoinServiceAPI
final String password = generatePassword(); final String password = generatePassword();
String stringConfig = await getConfig(); String stringConfig = await getConfig();
String epicboxConfig = await getEpicBoxConfig(); EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
final String name = _walletName.trim(); final String name = _walletName.trim();
await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic);
await _secureStore.write(key: '${_walletId}_config', value: stringConfig); await _secureStore.write(key: '${_walletId}_config', value: stringConfig);
await _secureStore.write(key: '${_walletId}_password', value: password); await _secureStore.write(key: '${_walletId}_password', value: password);
await _secureStore.write( await _secureStore.write(
key: '${_walletId}_epicboxConfig', value: epicboxConfig); key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString());
await compute( await compute(
_recoverWrapper, _recoverWrapper,
@ -1370,14 +1388,14 @@ class EpicCashWallet extends CoinServiceAPI
Future<void> listenForSlates() async { Future<void> listenForSlates() async {
final wallet = await _secureStore.read(key: '${_walletId}_wallet'); final wallet = await _secureStore.read(key: '${_walletId}_wallet');
final epicboxConfig = await getEpicBoxConfig(); EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
await m.protect(() async { await m.protect(() async {
Logging.instance.log("CALLING LISTEN FOR SLATES", level: LogLevel.Info); Logging.instance.log("CALLING LISTEN FOR SLATES", level: LogLevel.Info);
ReceivePort receivePort = await getIsolate({ ReceivePort receivePort = await getIsolate({
"function": "listenForSlates", "function": "listenForSlates",
"wallet": wallet, "wallet": wallet,
"epicboxConfig": epicboxConfig, "epicboxConfig": epicboxConfig.toString(),
}, name: walletName); }, name: walletName);
var result = await receivePort.first; var result = await receivePort.first;

View file

@ -0,0 +1,43 @@
import 'package:stackwallet/models/epicbox_server_model.dart';
abstract class DefaultEpicBoxes {
static const String defaultName = "Default";
static List<EpicBoxServerModel> get all => [americas, asia, europe];
static List<String> get defaultIds => ['americas', 'asia', 'europe'];
static EpicBoxServerModel get americas => EpicBoxServerModel(
host: 'epicbox.epic.tech',
port: 443,
name: 'Americas',
id: 'americas',
useSSL: true,
enabled: true,
isFailover: true,
isDown: false,
);
static EpicBoxServerModel get asia => EpicBoxServerModel(
host: 'epicbox.hyperbig.com',
port: 443,
name: 'Asia',
id: 'asia',
useSSL: true,
enabled: true,
isFailover: true,
isDown: false,
);
static EpicBoxServerModel get europe => EpicBoxServerModel(
host: 'epicbox.fastepic.eu',
port: 443,
name: 'Europe',
id: 'europe',
useSSL: true,
enabled: true,
isFailover: true,
isDown: false,
);
static final defaultEpicBoxServer = americas;
}

View file

@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -255,18 +253,4 @@ abstract class DefaultNodes {
return dogecoinTestnet; return dogecoinTestnet;
} }
} }
static final String defaultEpicBoxConfig = jsonEncode({
"epicbox_domain": "epicbox.epic.tech",
"epicbox_port": 443,
"epicbox_protocol_unsecure": false,
"epicbox_address_index": 0,
});
static final String epicBoxConfigEUR = jsonEncode({
"epicbox_domain": "epicbox.fastepic.eu",
"epicbox_port": 443,
"epicbox_protocol_unsecure": false,
"epicbox_address_index": 0,
});
} }