decred: Add sync.

This commit is contained in:
JoeGruff 2024-01-16 20:19:02 +09:00
parent 2b808ad50a
commit 0328f9afb4
7 changed files with 248 additions and 20 deletions

View file

@ -287,8 +287,20 @@ class Node extends HiveObject with Keyable {
return false;
}
}
}
Future<bool> requestDecredNode() async {
final decredMainnetPort = 9108;
if (uri.host == "" && uri.port == decredMainnetPort) {
// Just show default port as ok. The wallet will connect to a list of known
// nodes automatically.
return true;
}
try {
final socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
socket.destroy();
return true;
} catch (_) {
return false;
}
}
}

View file

@ -19,11 +19,10 @@ final dcrwalletApi = libdcrwallet(DynamicLibrary.open(libraryName));
/// a wallet.
void initLibdcrwallet(String logDir) {
final cLogDir = logDir.toCString();
final res = executePayloadFn(
executePayloadFn(
fn: () => dcrwalletApi.initialize(cLogDir),
ptrsToFree: [cLogDir],
);
print(res.payload);
}
/// createWalletAsync calls the libdcrwallet's createWallet function
@ -46,11 +45,10 @@ void createWalletSync(Map<String, String> args) {
final password = args["password"]!.toCString();
final network = "testnet".toCString();
final res = executePayloadFn(
executePayloadFn(
fn: () => dcrwalletApi.createWallet(name, dataDir, network, password),
ptrsToFree: [name, dataDir, network, password],
);
print(res.payload);
}
/// loadWalletAsync calls the libdcrwallet's loadWallet function asynchronously.
@ -67,15 +65,35 @@ void loadWalletSync(Map<String, String> args) {
final name = args["name"]!.toCString();
final dataDir = args["dataDir"]!.toCString();
final network = "testnet".toCString();
final res = executePayloadFn(
executePayloadFn(
fn: () => dcrwalletApi.loadWallet(name, dataDir, network),
ptrsToFree: [name, dataDir, network],
);
print(res.payload);
}
Future<void> startSyncAsync({required String name, required String peers}) {
final args = <String, String>{
"name": name,
"peers": peers,
};
return compute(startSync, args);
}
void startSync(Map<String, String> args) {
final name = args["name"]!.toCString();
final peers = args["peers"]!.toCString();
executePayloadFn(
fn: () => dcrwalletApi.syncWallet(name, peers),
ptrsToFree: [name, peers],
);
}
void closeWallet(String walletName) {
// TODO.
final name = walletName.toCString();
executePayloadFn(
fn: () => dcrwalletApi.closeWallet(name),
ptrsToFree: [name],
);
}
Future<void> changeWalletPassword(
@ -110,6 +128,15 @@ String? currentReceiveAddress(String walletName) {
return res.payload;
}
String syncStatus(String walletName) {
final cName = walletName.toCString();
final res = executePayloadFn(
fn: () => dcrwalletApi.syncWalletStatus(cName),
ptrsToFree: [cName],
);
return res.payload;
}
Map balance(String walletName) {
final cName = walletName.toCString();
final res = executePayloadFn(

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_decred/pending_transaction.dart';
@ -38,9 +40,10 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance,
// password is currently only used for seed display, but would likely also be
// required to sign inputs when creating transactions.
final String _password;
bool connecting = false;
String persistantPeer = "";
Timer? syncTimer;
// TODO: Set up a way to change the balance and sync status when dcrlibwallet
// changes. Long polling probably?
@override
@observable
SyncStatus syncStatus;
@ -65,19 +68,138 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance,
Future<void> init() async {
updateBalance();
// TODO: update other wallet properties such as syncStatus, walletAddresses
// and transactionHistory with data from libdcrwallet.
}
void performBackgroundTasks() {
if (!checkSync()) {
return;
}
updateBalance();
}
bool checkSync() {
final syncStatusJSON = libdcrwallet.syncStatus(walletInfo.name);
final decoded = json.decode(syncStatusJSON);
final syncStatusCode = decoded["syncstatuscode"] ?? 0;
final syncStatusStr = decoded["syncstatus"] ?? "";
final targetHeight = decoded["targetheight"] ?? 1;
final numPeers = decoded["numpeers"] ?? 0;
// final cFiltersHeight = decoded["cfiltersheight"] ?? 0;
final headersHeight = decoded["headersheight"] ?? 0;
final rescanHeight = decoded["rescanheight"] ?? 0;
if (numPeers == 0) {
syncStatus = NotConnectedSyncStatus();
return false;
}
// Sync codes:
// NotStarted = 0
// FetchingCFilters = 1
// FetchingHeaders = 2
// DiscoveringAddrs = 3
// Rescanning = 4
// Complete = 5
if (syncStatusCode > 4) {
syncStatus = SyncedSyncStatus();
return true;
}
if (syncStatusCode == 0) {
syncStatus = ConnectedSyncStatus();
return false;
}
if (syncStatusCode == 1) {
syncStatus = SyncingSyncStatus(targetHeight, 0.0);
return false;
}
if (syncStatusCode == 2) {
final headersProg = headersHeight / targetHeight;
// Only allow headers progress to go up half way.
syncStatus =
SyncingSyncStatus(targetHeight - headersHeight, headersProg / 2);
return false;
}
// TODO: This step takes a while so should really get more info to the UI
// that we are discovering addresses.
if (syncStatusCode == 3) {
// Hover at half.
syncStatus = SyncingSyncStatus(0, .5);
return false;
}
if (syncStatusCode == 4) {
// Start at 75%.
final rescanProg = rescanHeight / targetHeight / 4;
syncStatus =
SyncingSyncStatus(targetHeight - rescanHeight, .75 + rescanProg);
return false;
}
return false;
}
@action
@override
Future<void> connectToNode({required Node node}) async {
//throw UnimplementedError();
if (connecting) {
throw "decred already connecting";
}
connecting = true;
String addr = "";
if (node.uri.host != "") {
addr = node.uri.host;
if (node.uri.port != "") {
addr += ":" + node.uri.port.toString();
}
}
if (addr != persistantPeer) {
if (syncTimer != null) {
syncTimer!.cancel();
syncTimer = null;
}
persistantPeer = addr;
libdcrwallet.closeWallet(walletInfo.name);
libdcrwallet.loadWalletSync({
"name": walletInfo.name,
"dataDir": walletInfo.dirPath,
});
}
await this._startSync();
connecting = false;
}
@action
@override
Future<void> startSync() async {
// TODO: call libdcrwallet.spvSync() and update syncStatus.
if (connecting) {
throw "decred already connecting";
}
connecting = true;
await this._startSync();
connecting = false;
}
Future<void> _startSync() async {
if (syncTimer != null) {
return;
}
try {
syncStatus = ConnectingSyncStatus();
libdcrwallet.startSyncAsync(
name: walletInfo.name,
peers: persistantPeer,
);
syncTimer = Timer.periodic(
Duration(seconds: 5), (Timer t) => performBackgroundTasks());
} catch (e) {
print(e.toString());
syncStatus = FailedSyncStatus();
}
}
@override
@ -134,6 +256,10 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance,
@override
void close() {
if (syncTimer != null) {
syncTimer!.cancel();
syncTimer = null;
}
libdcrwallet.closeWallet(walletInfo.name);
}

View file

@ -45,6 +45,7 @@ const tronDefaultNodeUri = 'api.trongrid.io';
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
const moneroWorldNodeUri = '.moneroworld.com';
const decredDefaultUri = ":9108";
Future<void> defaultSettingsMigration(
{required int version,
@ -95,6 +96,7 @@ Future<void> defaultSettingsMigration(
PreferencesKey.currentBalanceDisplayModeKey, BalanceDisplayMode.availableBalance.raw);
await sharedPreferences.setBool('save_recipient_address', true);
await resetToDefault(nodes);
await setDefaultDecredNodeKey(sharedPreferences, nodes);
await changeMoneroCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeBitcoinCurrentElectrumServerToDefault(
@ -647,6 +649,11 @@ Node? getNanoDefaultNode({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
}
Node? getDecredDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == decredDefaultUri) ??
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.decred));
}
Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
@ -812,6 +819,18 @@ Future<void> rewriteSecureStoragePin({required SecureStorage secureStorage}) asy
);
}
// If "node_list.resetToDefault" is called the old node.key will still be set in
// preferences. Set it to whatever it is now.
//
// TODO: There really isn't any reason to have a default node for decred, find
// a different way to handle this.
Future<void> setDefaultDecredNodeKey(
SharedPreferences sharedPreferences, Box<Node> nodeSource) async {
final node = nodeSource.values.firstWhere((node) => node.type == WalletType.decred);
await sharedPreferences.setInt(
PreferencesKey.currentDecredNodeIdKey, node.key as int);
}
Future<void> changeBitcoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes,
@ -1149,6 +1168,7 @@ Future<void> checkCurrentNodes(
final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
final currentBitcoinCashNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
@ -1168,6 +1188,8 @@ Future<void> checkCurrentNodes(
nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentDecredNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentDecredNodeId);
final currentNanoPowNodeServer =
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
final currentBitcoinCashNodeServer =
@ -1261,6 +1283,14 @@ Future<void> checkCurrentNodes(
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int);
}
if (currentDecredNodeServer == null) {
final decredMainnetPort = ":9108";
final node = Node(uri: decredDefaultUri, type: WalletType.decred);
await nodeSource.add(node);
await sharedPreferences.setInt(
PreferencesKey.currentDecredNodeIdKey, node.key as int);
}
}
Future<void> resetBitcoinElectrumServer(

View file

@ -1,6 +1,7 @@
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import "package:yaml/yaml.dart";
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
@ -200,6 +201,12 @@ Future<List<Node>> loadDefaultWowneroNodes() async {
return nodes;
}
Future<List<Node>> loadDefaultDecredNodes() async {
final decredMainnetPort = ":9108";
final node = Node(uri: decredMainnetPort, type: WalletType.decred);
return <Node>[node];
}
Future<void> resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
@ -211,6 +218,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final polygonNodes = await loadDefaultPolygonNodes();
final solanaNodes = await loadDefaultSolanaNodes();
final tronNodes = await loadDefaultTronNodes();
final decredNodes = await loadDefaultDecredNodes();
final nodes = moneroNodes +
bitcoinElectrumServerList +
@ -220,7 +228,9 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
bitcoinCashElectrumServerList +
nanoNodes +
polygonNodes +
solanaNodes + tronNodes;
solanaNodes +
tronNodes +
decredNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);

View file

@ -816,11 +816,6 @@ abstract class SettingsStoreBase with Store {
Node getCurrentNode(WalletType walletType) {
final node = nodes[walletType];
// TODO: Implement connecting to a user's preferred node.
if (walletType == WalletType.decred) {
return Node();
}
if (node == null) {
throw Exception('No node found for wallet type: ${walletType.toString()}');
}
@ -1007,6 +1002,7 @@ abstract class SettingsStoreBase with Store {
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
final moneroNode = nodeSource.get(nodeId);
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
@ -1015,6 +1011,7 @@ abstract class SettingsStoreBase with Store {
final polygonNode = nodeSource.get(polygonNodeId);
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId);
final decredNode = nodeSource.get(decredNodeId);
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
final solanaNode = nodeSource.get(solanaNodeId);
final tronNode = nodeSource.get(tronNodeId);
@ -1100,6 +1097,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.wownero] = wowneroNode;
}
if (decredNode != null) {
nodes[WalletType.decred] = decredNode;
}
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0);
});
@ -1334,6 +1335,11 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.bitcoinCash] = bitcoinCash!.deserializeBitcoinCashTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!);
}
if (decred != null &&
sharedPreferences.getInt(PreferencesKey.decredTransactionPriority) != null) {
priority[WalletType.decred] = decred!.deserializeDecredTransactionPriority(
sharedPreferences.getInt(PreferencesKey.decredTransactionPriority)!);
}
final generateSubaddresses =
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
@ -1442,6 +1448,7 @@ abstract class SettingsStoreBase with Store {
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
final moneroNode = nodeSource.get(nodeId);
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
@ -1453,6 +1460,7 @@ abstract class SettingsStoreBase with Store {
final solanaNode = nodeSource.get(solanaNodeId);
final tronNode = nodeSource.get(tronNodeId);
final wowneroNode = nodeSource.get(wowneroNodeId);
final decredNode = nodeSource.get(decredNodeId);
if (moneroNode != null) {
nodes[WalletType.monero] = moneroNode;
}
@ -1497,6 +1505,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.wownero] = wowneroNode;
}
if (decredNode != null) {
nodes[WalletType.decred] = decredNode;
}
// MIGRATED:
useTOTP2FA = await SecureKey.getBool(
@ -1633,6 +1645,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.wownero:
await _sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int);
break;
case WalletType.decred:
await _sharedPreferences.setInt(PreferencesKey.currentDecredNodeIdKey, node.key as int);
break;
default:
break;
}

View file

@ -8,6 +8,8 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/node.dart';
import 'package:cake_wallet/entities/node_list.dart';
import 'package:cake_wallet/entities/default_settings_migration.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/di.dart';
import 'package:cw_core/wallet_type.dart';
part 'node_list_view_model.g.dart';
@ -47,6 +49,9 @@ abstract class NodeListViewModelBase with Store {
Future<void> reset() async {
await resetToDefault(_nodeSource);
final decredNode = getDecredDefaultNode(nodes: _nodeSource)!;
final sharedPrefs = getIt.get<SharedPreferences>();
await setDefaultDecredNodeKey(sharedPrefs, _nodeSource);
Node node;
@ -88,6 +93,9 @@ abstract class NodeListViewModelBase with Store {
case WalletType.wownero:
node = getWowneroDefaultNode(nodes: _nodeSource);
break;
case WalletType.decred:
node = decredNode;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
}