From ac05166e956ba09e242144a31a96670aea3f382d Mon Sep 17 00:00:00 2001 From: Omar Date: Tue, 26 May 2026 17:04:36 +0200 Subject: [PATCH] Rebase node-sqlite-migration onto dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed migration of node Hive→SQLite, default-node handling, isPow/proxy support, arbitrum node updates, and related fixes. Replays node-sqlite-migration on top of current dev without the amount-refactor merge that the original branch carried. Co-Authored-By: Claude Opus 4.7 (1M context) --- assets/arbitrum_node_list.yml | 7 +- assets/base_node_list.yml | 8 +- assets/bitcoin_cash_electrum_server_list.yml | 7 +- assets/bitcoin_electrum_server_list.yml | 7 + assets/bsc_node_list.yml | 7 + assets/decred_node_list.yml | 6 +- assets/dogecoin_electrum_server_list.yml | 7 +- assets/ethereum_server_list.yml | 7 +- assets/haven_node_list.yml | 2 +- assets/lightning_deposit_help.svg | 99 ---- assets/litecoin_electrum_server_list.yml | 7 + assets/nano_node_list.yml | 21 +- assets/node_list.yml | 18 +- assets/polygon_node_list.yml | 10 +- assets/solana_node_list.yml | 14 +- assets/tron_node_list.yml | 14 +- assets/wownero_node_list.yml | 4 +- assets/zano_node_list.yml | 7 +- assets/zcash_node_list.yml | 20 +- cw_bitcoin/lib/electrum_wallet.dart | 5 +- cw_core/lib/db/sqlite.dart | 28 +- cw_core/lib/node.dart | 204 ++++++-- cw_core/lib/node_legacy.dart | 237 +++++++++ cw_core/lib/node_list.dart | 188 +++++++ lib/core/node_switching_service.dart | 16 +- lib/di.dart | 45 +- lib/entities/default_settings_migration.dart | 491 +++++------------- lib/entities/node_check.dart | 69 +++ lib/entities/node_list.dart | 148 ------ lib/main.dart | 23 +- .../widgets/send_page/send_confirm_sheet.dart | 1 + .../nodes/node_create_or_edit_page.dart | 9 +- .../nodes/pow_node_create_or_edit_page.dart | 2 +- lib/src/screens/nodes/widgets/node_form.dart | 127 ++--- .../screens/settings/manage_nodes_page.dart | 4 +- lib/store/app_store.dart | 3 - lib/store/node_list_store.dart | 34 -- lib/store/settings_store.dart | 133 +++-- lib/utils/exception_handler.dart | 21 +- .../node_create_or_edit_view_model.dart | 82 +-- .../node_list/node_list_view_model.dart | 37 +- .../node_list/pow_node_list_view_model.dart | 20 +- .../restore_from_backup_view_model.dart | 2 +- res/values/strings_ar.arb | 2 +- res/values/strings_bg.arb | 2 +- res/values/strings_cs.arb | 2 +- res/values/strings_de.arb | 2 +- res/values/strings_es.arb | 2 +- res/values/strings_fa.arb | 2 +- res/values/strings_fr.arb | 2 +- res/values/strings_gn.arb | 2 +- res/values/strings_ha.arb | 2 +- res/values/strings_hi.arb | 4 +- res/values/strings_hr.arb | 2 +- res/values/strings_hy.arb | 2 +- res/values/strings_id.arb | 2 +- res/values/strings_it.arb | 2 +- res/values/strings_ja.arb | 2 +- res/values/strings_ko.arb | 2 +- res/values/strings_my.arb | 2 +- res/values/strings_nl.arb | 2 +- res/values/strings_pl.arb | 2 +- res/values/strings_pt.arb | 2 +- res/values/strings_ru.arb | 2 +- res/values/strings_th.arb | 2 +- res/values/strings_tl.arb | 2 +- res/values/strings_tr.arb | 2 +- res/values/strings_uk.arb | 2 +- res/values/strings_ur.arb | 2 +- res/values/strings_vi.arb | 2 +- res/values/strings_yo.arb | 2 +- res/values/strings_zh.arb | 2 +- 72 files changed, 1205 insertions(+), 1056 deletions(-) delete mode 100644 assets/lightning_deposit_help.svg create mode 100644 cw_core/lib/node_legacy.dart create mode 100644 cw_core/lib/node_list.dart create mode 100644 lib/entities/node_check.dart diff --git a/assets/arbitrum_node_list.yml b/assets/arbitrum_node_list.yml index 63894f18a..019a5838d 100644 --- a/assets/arbitrum_node_list.yml +++ b/assets/arbitrum_node_list.yml @@ -1,12 +1,15 @@ - uri: arbitrum.nownodes.io - is_default: true useSSL: true isEnabledForAutoSwitching: true + label: "NOWNodes" - uri: arbitrum.drpc.org useSSL: true + label: "dRPC" - uri: arbitrum-one-rpc.publicnode.com useSSL: true - isEnabledForAutoSwitching: true \ No newline at end of file + isDefault: true + isEnabledForAutoSwitching: true + label: "PublicNode" \ No newline at end of file diff --git a/assets/base_node_list.yml b/assets/base_node_list.yml index 990e972a4..053882639 100644 --- a/assets/base_node_list.yml +++ b/assets/base_node_list.yml @@ -1,15 +1,19 @@ - uri: base.nownodes.io + isDefault: true useSSL: true isEnabledForAutoSwitching: true + label: "NOWNodes" - uri: base.llamarpc.com useSSL: true + label: "LlamaNodes" - uri: base-rpc.publicnode.com - is_default: true useSSL: true isEnabledForAutoSwitching: true + label: "PublicNode" - uri: 1rpc.io/base - useSSL: true \ No newline at end of file + useSSL: true + label: "1RPC" \ No newline at end of file diff --git a/assets/bitcoin_cash_electrum_server_list.yml b/assets/bitcoin_cash_electrum_server_list.yml index 770bdbc05..b8c4e17df 100644 --- a/assets/bitcoin_cash_electrum_server_list.yml +++ b/assets/bitcoin_cash_electrum_server_list.yml @@ -1,12 +1,11 @@ - uri: bitcoincash.stackwallet.com:50002 - is_default: true + isDefault: true useSSL: true isEnabledForAutoSwitching: true + label: "Stack Wallet" - uri: bch.aftrek.org:50002 useSSL: true isEnabledForAutoSwitching: true -- - uri: node.minisatoshi.cash:50002 - useSSL: true + label: "Aftrek" diff --git a/assets/bitcoin_electrum_server_list.yml b/assets/bitcoin_electrum_server_list.yml index c3a92e948..4346d1d31 100644 --- a/assets/bitcoin_electrum_server_list.yml +++ b/assets/bitcoin_electrum_server_list.yml @@ -2,14 +2,21 @@ uri: btc-electrum.cakewallet.com:50002 useSSL: true isDefault: true + isOfficial: true isEnabledForAutoSwitching: true + label: "Cake Wallet Electrum" - uri: electrs.cakewallet.com:50001 + isOfficial: true + label: "Cake Wallet Electrs" - uri: fulcrum.sethforprivacy.com:50002 useSSL: true isEnabledForAutoSwitching: true + label: "Seth For Privacy" - uri: electrum.cakewallet.com:50002 + isOfficial: true useSSL: true isEnabledForAutoSwitching: true + label: "Cake Wallet (Legacy)" diff --git a/assets/bsc_node_list.yml b/assets/bsc_node_list.yml index 20e0cdcca..4403b6b01 100644 --- a/assets/bsc_node_list.yml +++ b/assets/bsc_node_list.yml @@ -3,26 +3,33 @@ useSSL: true isDefault: true isEnabledForAutoSwitching: true + label: "BNBChain" - uri: bsc-dataseed2.bnbchain.org useSSL: true isEnabledForAutoSwitching: true + label: "BNBChain #2" - uri: bsc-dataseed.nariox.org useSSL: true isEnabledForAutoSwitching: true + label: "Nariox" - uri: bsc-dataseed.defibit.io useSSL: true isEnabledForAutoSwitching: true + label: "DeFibit" - uri: bsc-dataseed.ninicoin.io useSSL: true isEnabledForAutoSwitching: true + label: "NiniCoin" - uri: bsc.nodereal.io useSSL: true isEnabledForAutoSwitching: true + label: "NodeReal" - uri: bsc-dataseed-public.bnbchain.org useSSL: true + label: "BNBChain Public" \ No newline at end of file diff --git a/assets/decred_node_list.yml b/assets/decred_node_list.yml index 9f5938e8c..7121867e0 100644 --- a/assets/decred_node_list.yml +++ b/assets/decred_node_list.yml @@ -1,8 +1,10 @@ - uri: default-spv-nodes - is_default: true + isDefault: true isEnabledForAutoSwitching: true + label: "Decred" - uri: dcrd.sethforprivacy.com:9108 useSSL: true - isEnabledForAutoSwitching: true \ No newline at end of file + isEnabledForAutoSwitching: true + label: "Seth For Privacy" \ No newline at end of file diff --git a/assets/dogecoin_electrum_server_list.yml b/assets/dogecoin_electrum_server_list.yml index 087b0af2b..010535be3 100644 --- a/assets/dogecoin_electrum_server_list.yml +++ b/assets/dogecoin_electrum_server_list.yml @@ -1,12 +1,15 @@ - uri: dogecoin.stackwallet.com:50022 - is_default: true + isDefault: true useSSL: true isEnabledForAutoSwitching: true + label: "Stack Wallet" - uri: doge.aftrek.org:50002 useSSL: true isEnabledForAutoSwitching: true + label: "Aftrek" - uri: electrum1.cipig.net:20060 - useSSL: true \ No newline at end of file + useSSL: true + label: "Cipig" \ No newline at end of file diff --git a/assets/ethereum_server_list.yml b/assets/ethereum_server_list.yml index 0ba098fc5..08c2f4ae5 100644 --- a/assets/ethereum_server_list.yml +++ b/assets/ethereum_server_list.yml @@ -3,18 +3,21 @@ useSSL: true isDefault: true isEnabledForAutoSwitching: true + label: "PublicNode" - uri: eth.llamarpc.com useSSL: true + label: "LlamaNodes" - uri: rpc.flashbots.net useSSL: true + label: "Flashbots" - uri: eth-mainnet.public.blastapi.io useSSL: true + label: "Blast" - uri: eth.nownodes.io useSSL: true isEnabledForAutoSwitching: true -- - uri: ethereum.publicnode.com \ No newline at end of file + label: "NOWNodes" \ No newline at end of file diff --git a/assets/haven_node_list.yml b/assets/haven_node_list.yml index 27d9ee611..2dfa644b7 100644 --- a/assets/haven_node_list.yml +++ b/assets/haven_node_list.yml @@ -1,4 +1,4 @@ - uri: nodes.havenprotocol.org:443 useSSL: true - is_default: true \ No newline at end of file + isDefault: true \ No newline at end of file diff --git a/assets/lightning_deposit_help.svg b/assets/lightning_deposit_help.svg deleted file mode 100644 index 5cecc0bab..000000000 --- a/assets/lightning_deposit_help.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/litecoin_electrum_server_list.yml b/assets/litecoin_electrum_server_list.yml index 18b982aa1..b2c559910 100644 --- a/assets/litecoin_electrum_server_list.yml +++ b/assets/litecoin_electrum_server_list.yml @@ -2,20 +2,27 @@ uri: ltc-electrum.cakewallet.com:50002 useSSL: true isDefault: true + isOfficial: true isEnabledForAutoSwitching: true + label: "Cake Wallet" - uri: litecoin.stackwallet.com:20063 useSSL: true isEnabledForAutoSwitching: true + label: "Stack Wallet" - uri: electrum-ltc.bysh.me:50002 useSSL: true + label: "Bysh" - uri: lightweight.fiatfaucet.com:50002 useSSL: true + label: "Fiat Faucet" - uri: electrum.ltc.xurious.com:50002 useSSL: true + label: "Xurious" - uri: backup.electrum-ltc.org:443 useSSL: true + label: "Electrum-LTC" diff --git a/assets/nano_node_list.yml b/assets/nano_node_list.yml index aa1d8e3c9..1f153a290 100644 --- a/assets/nano_node_list.yml +++ b/assets/nano_node_list.yml @@ -1,34 +1,37 @@ +- + uri: rpc.nano.to + isDefault: true + useSSL: true + isEnabledForAutoSwitching: true + label: "Nano.to" - uri: nano.nownodes.io useSSL: true isEnabledForAutoSwitching: true -- - uri: rpc.nano.to - is_default: true - useSSL: true - isEnabledForAutoSwitching: true + label: "NOWNodes" - uri: node.nautilus.io path: /api useSSL: true isEnabledForAutoSwitching: true + label: "Nautilus" - uri: app.natrium.io path: /api useSSL: true + label: "Natrium" - uri: rainstorm.city path: /api useSSL: true + label: "Rainstorm City" - uri: node.somenano.com path: /proxy useSSL: true + label: "SomeNano" - uri: nanoslo.0x.no path: /proxy useSSL: true -- - uri: www.bitrequest.app - port: 8020 - useSSL: true \ No newline at end of file + label: "0x.no" \ No newline at end of file diff --git a/assets/node_list.yml b/assets/node_list.yml index 568bffb2c..a1568319a 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -1,34 +1,36 @@ - + label: "Cake Wallet" uri: xmr-node.cakewallet.com:18081 - is_default: true + isDefault: true + isOfficial: true trusted: true useSSL: true isEnabledForAutoSwitching: true - + label: "Cake Wallet (Onion)" uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081 - is_default: false - + label: "Seth For Privacy" uri: node.sethforprivacy.com:443 useSSL: true - is_default: false isEnabledForAutoSwitching: true - + label: "MoneroDevs" uri: node.monerodevs.org:18089 useSSL: true - is_default: false - + label: "MoneroDevs #2" uri: node2.monerodevs.org:18089 useSSL: true - is_default: false - + label: "MoneroDevs #3" uri: node3.monerodevs.org:18089 useSSL: true - is_default: false - + label: "Feather Wallet" uri: selsta1.featherwallet.net:18081 useSSL: true - is_default: false - + label: "StormyCloud" uri: xmr.stormycloud.org:18089 useSSL: true - is_default: false diff --git a/assets/polygon_node_list.yml b/assets/polygon_node_list.yml index 822a8b4f6..9760dd7a3 100644 --- a/assets/polygon_node_list.yml +++ b/assets/polygon_node_list.yml @@ -1,14 +1,14 @@ -- - uri: polygon-rpc.com - uri: polygon-bor-rpc.publicnode.com useSSL: true isDefault: true isEnabledForAutoSwitching: true + label: "polygon-bor-rpc.publicnode.com" - - uri: polygon.llamarpc.com - useSSL: true + uri: polygon-rpc.com + label: "Ankr" - uri: matic.nownodes.io useSSL: true - isEnabledForAutoSwitching: true \ No newline at end of file + isEnabledForAutoSwitching: true + label: "NOWNodes" \ No newline at end of file diff --git a/assets/solana_node_list.yml b/assets/solana_node_list.yml index e1d7971c6..e2ad7a272 100644 --- a/assets/solana_node_list.yml +++ b/assets/solana_node_list.yml @@ -1,16 +1,20 @@ +- + uri: solana-mainnet.core.chainstack.com + useSSL: true + isDefault: true + isEnabledForAutoSwitching: true + label: "Chainstack" - uri: rpc.ankr.com useSSL: true + label: "Ankr" - uri: api.mainnet-beta.solana.com:443 useSSL: true isEnabledForAutoSwitching: true + label: "Solana Beta" - uri: solana-rpc.publicnode.com:443 useSSL: true isEnabledForAutoSwitching: true -- - uri: solana-mainnet.core.chainstack.com - useSSL: true - is_default: true - isEnabledForAutoSwitching: true \ No newline at end of file + label: "PublicNode" \ No newline at end of file diff --git a/assets/tron_node_list.yml b/assets/tron_node_list.yml index 595bba971..3837ec4ee 100644 --- a/assets/tron_node_list.yml +++ b/assets/tron_node_list.yml @@ -1,13 +1,15 @@ -- - uri: tron-rpc.publicnode.com:443 - is_default: false - useSSL: true - uri: api.trongrid.io - is_default: true + isDefault: true useSSL: true isEnabledForAutoSwitching: true + label: "TronGrid" +- + uri: tron-rpc.publicnode.com:443 + useSSL: true + label: "PublicNode" - uri: trx.nownodes.io useSSL: true - isEnabledForAutoSwitching: true \ No newline at end of file + isEnabledForAutoSwitching: true + label: "NOWNodes" \ No newline at end of file diff --git a/assets/wownero_node_list.yml b/assets/wownero_node_list.yml index 893f82984..5356fa5d5 100644 --- a/assets/wownero_node_list.yml +++ b/assets/wownero_node_list.yml @@ -1,14 +1,12 @@ - uri: node3.monerodevs.org:34568 - is_default: true + isDefault: true useSSL: false isEnabledForAutoSwitching: true - uri: node2.monerodevs.org:34568 - is_default: false useSSL: false isEnabledForAutoSwitching: true - uri: node.monerodevs.org:34568 - is_default: false useSSL: false diff --git a/assets/zano_node_list.yml b/assets/zano_node_list.yml index 17bba5f44..93923e6bd 100644 --- a/assets/zano_node_list.yml +++ b/assets/zano_node_list.yml @@ -1,9 +1,12 @@ - uri: 37.27.100.59:10500 - is_default: true + isDefault: true useSSL: false isEnabledForAutoSwitching: true + label: "Zano" - uri: zano.cakewallet.com:11211 useSSL: false - isEnabledForAutoSwitching: true \ No newline at end of file + isOfficial: true + isEnabledForAutoSwitching: true + label: "Cake Wallet" diff --git a/assets/zcash_node_list.yml b/assets/zcash_node_list.yml index 88db5c54c..afdd49599 100644 --- a/assets/zcash_node_list.yml +++ b/assets/zcash_node_list.yml @@ -1,12 +1,20 @@ -- uri: zec.rocks:443 - is_default: false +- + uri: zec-node.cakewallet.com:443 + isDefault: true + isOfficial: true useSSL: true isEnabledForAutoSwitching: true -- uri: zec-node.cakewallet.com:443 - is_default: true + label: "Cake Wallet" +- + uri: zec.rocks:443 useSSL: true isEnabledForAutoSwitching: true -- uri: 2c4whzg26j6hgjh22rxynj3oig4gm22ga7x74weclujywxye23v3u5id.onion:9067 + label: "Zec.rocks" +- + uri: 2c4whzg26j6hgjh22rxynj3oig4gm22ga7x74weclujywxye23v3u5id.onion:9067 useSSL: false -- uri: bvp2l442g5ogma7rywzahm7eqyhpp3g26n3gvvxeioqn5csio2ir6myd.onion:9067 + label: "Cake Wallet (Tor)" +- + uri: bvp2l442g5ogma7rywzahm7eqyhpp3g26n3gvvxeioqn5csio2ir6myd.onion:9067 useSSL: false + label: "Cake Wallet (Tor #2)" \ No newline at end of file diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 243a88b26..ec5492116 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -790,9 +790,10 @@ abstract class ElectrumWalletBase if (server.toLowerCase().contains('electrs')) { node!.isElectrs = true; - if (node!.isInBox) { + // TODO figure out why condition was needed + // if (node!.isInBox) { node!.save(); - } + // } return node!.isElectrs!; } } diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart index 593b1b14f..eea479a80 100644 --- a/cw_core/lib/db/sqlite.dart +++ b/cw_core/lib/db/sqlite.dart @@ -41,7 +41,7 @@ Future initDb({String? pathOverride}) async { } } await db?.close(); - db = await openDatabase(dbFile.path, version: 7, + db = await openDatabase(dbFile.path, version: 8, onUpgrade: (Database db, int oldVersion, int newVersion) async { printV("migrating: $oldVersion, $newVersion"); if (oldVersion <= 1) { @@ -104,6 +104,9 @@ CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings ( definition: 'TEXT', ); } + if(oldVersion <= 7) { + await _createNodeTable(db); + } }, onCreate: (Database db, int version) async { await db.execute( @@ -202,6 +205,7 @@ CREATE TABLE BalanceCardStyleSettings ( ); '''); await _createBridgeTransferTable(db); + await _createNodeTable(db); await _createTradeTable(db); } ); @@ -330,4 +334,26 @@ CREATE TABLE IF NOT EXISTS BridgeTransfer ( CREATE INDEX IF NOT EXISTS idx_bridgetransfer_wallet_id ON BridgeTransfer(wallet_id); '''); +} + +Future _createNodeTable(Database db) async { + db.execute(""" +CREATE TABLE Node ( +NodeId INTEGER PRIMARY KEY, +uri TEXT NOT NULL, +path TEXT, +login TEXT, +label TEXT, +password TEXT, +isPow INTEGER NOT NULL, +useSSL INTEGER, +typeRaw INTEGER NOT NULL, +trusted INTEGER NOT NULL, +socksProxyAddress TEXT, +isEnabledForAutoSwitching BOOLEAN DEFAULT FALSE, +isOfficial BOOLEAN DEFAULT FALSE, +isBuiltin BOOLEAN DEFAULT FALSE, +isDefault BOOLEAN DEFAULT FALSE +); + """); } \ No newline at end of file diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 6ed3964a2..b3478ed4a 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -1,33 +1,62 @@ +import 'dart:convert'; import 'dart:io'; -import 'package:cw_core/keyable.dart'; +import 'dart:math' as math; + +import 'package:blockchain_utils/hex/hex.dart'; +import 'package:cw_core/node_list.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:cw_core/utils/print_verbose.dart'; -import 'dart:convert'; -import 'package:hive/hive.dart'; -import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/wallet_type.dart'; -import 'dart:math' as math; -import 'package:convert/convert.dart'; - import 'package:crypto/crypto.dart'; +import 'package:sqflite/sqflite.dart'; -part 'node.g.dart'; +import 'db/sqlite.dart'; Uri createUriFromElectrumAddress(String address, String path) => Uri.tryParse('tcp://$address$path')!; -@HiveType(typeId: Node.typeId) -class Node extends HiveObject with Keyable { +Future validateBuiltinNodes() async { + // ensures nodes stored as builtin in the db correspond to the nodes in the .yml files + + final builtinFromDb = await Node.getAllBuiltin(); + final builtinFromList = await loadAllDefaultNodes(); + + for (final listNode in builtinFromList) { + // preserve proxy settings from user + try { + final matchedDbNode = builtinFromDb.firstWhere( + (dbNode) => dbNode.uri == listNode.uri + ); + listNode.socksProxyAddress = matchedDbNode.socksProxyAddress; + } catch(e) {} + } + + final dbSet = builtinFromDb.toSet(); + final listSet = builtinFromList.toSet(); + + final nodesToDelete = dbSet.difference(listSet); + final nodesToAdd = listSet.difference(dbSet); + + for (final node in nodesToDelete) await node.delete(); + for (final node in nodesToAdd) await node.save(); +} + +class Node { Node({ + this.id = 0, this.label, this.login, this.password, this.useSSL, + this.isPow = false, this.trusted = false, this.socksProxyAddress, this.path = '', this.isEnabledForAutoSwitching = false, + this.isOfficial = false, + this.isBuiltin = false, + this.isDefault = false, String? uri, WalletType? type, }) { @@ -50,62 +79,144 @@ class Node extends HiveObject with Keyable { trusted: $trusted, socksProxyAddress: $socksProxyAddress, isEnabledForAutoSwitching: $isEnabledForAutoSwitching, + isOfficial: $isOfficial, + isBuiltin: $isBuiltin, + isDefault: $isDefault })"""; } Node.fromMap(Map map) - : uriRaw = map['uri'] as String? ?? '', + : id = (map[selfIdColumn] ?? 0) as int, + uriRaw = map['uri'] as String? ?? '', path = map['path'] as String? ?? '', login = map['login'] as String?, label = map['label'] as String?, password = map['password'] as String?, - useSSL = map['useSSL'] as bool?, - trusted = map['trusted'] as bool? ?? false, - socksProxyAddress = map['socksProxyPort'] as String?, - isEnabledForAutoSwitching = map['isEnabledForAutoSwitching'] as bool? ?? false; + isPow = (map["isPow"] != null && map['isPow'] != 0) as bool? ?? false, + useSSL = (map['useSSL'] != 0) as bool?, + typeRaw = (map["typeRaw"] ?? 0) as int, + trusted = (map['trusted'] != 0) as bool? ?? false, + socksProxyAddress = map['socksProxyAddress'] as String?, + isEnabledForAutoSwitching = (map['isEnabledForAutoSwitching'] != 0) as bool? ?? false, + isOfficial = (map['isOfficial'] != 0) as bool? ?? false, + isBuiltin = (map['isBuiltin'] != 0) as bool? ?? false, + isDefault = (map['isDefault'] != 0) as bool? ?? false; - static const typeId = NODE_TYPE_ID; - static const boxName = 'Nodes'; + Map toMap() { + return { + selfIdColumn: id, + 'uri': uriRaw, + 'path': path, + 'login': login, + "label": label, + 'password': password, + "isPow": isPow, + 'useSSL': useSSL, + "typeRaw": typeRaw, + 'trusted': trusted, + 'socksProxyAddress': socksProxyAddress, + 'isEnabledForAutoSwitching': isEnabledForAutoSwitching, + "isOfficial": isOfficial, + "isBuiltin": isBuiltin, + "isDefault": isDefault + }; + } - @HiveField(0, defaultValue: '') + Future delete() async { + return await db!.delete(tableName, where: '${selfIdColumn} = ?', whereArgs: [id]); + } + + static Future deleteAll() async { + return await db!.delete(tableName, where: "isPow = ?", whereArgs: [false]); + } + + static Future deleteAllPow() async { + return await db!.delete(tableName, where: "isPow = ?", whereArgs: [true]); + } + + Future save() async { + + final json = toMap(); + if (json[selfIdColumn] == 0) { + json[selfIdColumn] = null; + } + id = await db!.insert(tableName, json, conflictAlgorithm: ConflictAlgorithm.replace); + return id; + } + + + static Future> selectList(String where, List whereArgs, {String? orderBy}) async { + if(orderBy == null) { + orderBy = selfIdColumn; + } + final list = await db!.query( + tableName, + where: where.isNotEmpty ? where : "1 = 1", + whereArgs: whereArgs.isNotEmpty ? whereArgs : null, + orderBy: orderBy, + ); + printV("selectList: $list"); + return List.generate(list.length, (index) => Node.fromMap(list[index])); + } + + + static Future> getAll() async { + return selectList("isPow = ?", [false]); + } + + static Future> getAllBuiltin() async { + return selectList("isPow = ? AND isBuiltin = ?", [false, true]); + } + + static Future> getAllPowBuiltin() async { + return selectList("isPow = ? AND isBuiltin = ?", [true, true]); + } + + static Future> getAllForWalletType(WalletType type) async { + return selectList("typeRaw = ? AND isPow = ?", [serializeToInt(type), false]); + } + + static Future getDefaultForWalletType(WalletType type) async { + return (await selectList("typeRaw = ? AND isPow = ? AND isDefault = ?", [serializeToInt(type), false, true])).firstOrNull; + } + + static Future getDefaultPowForWalletType(WalletType type) async { + return (await selectList("typeRaw = ? AND isPow = ? AND isDefault = ?", [serializeToInt(type), true, true])).firstOrNull; + } + + static Future> getAllForWalletTypePow(WalletType type) async { + return selectList("typeRaw = ? AND isPow = ?", [serializeToInt(type), true]); + } + + static Future> getAllPow() async { + return selectList("isPow = ?", [true]); + } + + + int id; late String uriRaw; - - @HiveField(1) String? login; - - @HiveField(2) String? password; - - @HiveField(3, defaultValue: 0) late int typeRaw; - - @HiveField(4) bool? useSSL; - - @HiveField(5, defaultValue: false) bool trusted; - - @HiveField(6) + bool isPow; String? socksProxyAddress; - - @HiveField(7, defaultValue: '') String? path; - - @HiveField(8) bool? isElectrs; - - @HiveField(9) bool? supportsSilentPayments; - - @HiveField(10) bool? supportsMweb; - - @HiveField(11, defaultValue: false) bool isEnabledForAutoSwitching; + bool isOfficial; + bool isBuiltin; + bool isDefault; - @HiveField(12, defaultValue: '') String? label; + static String get tableName => "Node"; + static String get selfIdColumn => "${tableName}Id"; + + bool get isSSL => useSSL ?? false; bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty; @@ -167,18 +278,11 @@ class Node extends HiveObject with Keyable { socksProxyAddress.hashCode ^ path.hashCode; - @override - dynamic get keyIndex { - _keyIndex ??= key; - return _keyIndex; - } WalletType get type => deserializeFromInt(typeRaw); set type(WalletType type) => typeRaw = serializeToInt(type); - dynamic _keyIndex; - Future requestNode() async { try { switch (type) { @@ -228,7 +332,7 @@ class Node extends HiveObject with Keyable { body: jsonBody, ); - + final resBody = json.decode(response.body) as Map; return resBody['result']['height'] != null; diff --git a/cw_core/lib/node_legacy.dart b/cw_core/lib/node_legacy.dart new file mode 100644 index 000000000..3c39b6f90 --- /dev/null +++ b/cw_core/lib/node_legacy.dart @@ -0,0 +1,237 @@ +import 'dart:io'; +import 'package:cw_core/keyable.dart'; +import 'package:cw_core/node_list.dart'; +import 'package:cw_core/utils/proxy_socket/abstract.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'dart:convert'; +import 'package:hive/hive.dart'; +import 'package:cw_core/hive_type_ids.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'dart:math' as math; +import 'package:convert/convert.dart'; + +import 'package:crypto/crypto.dart'; + +import 'cake_hive.dart'; +import 'node.dart' as node_new; + +part "node_legacy.g.dart"; + +Future performNodeHiveMigration() async { + if(!CakeHive.isAdapterRegistered(Node.typeId)) { + CakeHive.registerAdapter(NodeAdapter()); + } + + final nodeBox = await CakeHive.openBox(Node.boxName); + final powNodeBox = await CakeHive.openBox(Node.boxName+"pow"); + final builtinNodes = await loadAllDefaultNodes(); + final builtinPowNodes = await loadDefaultNanoPowNodes(); + await Node.migrateAllToSqlite(nodeBox, powNodeBox, builtinNodes, builtinPowNodes); + + +} + +Uri createUriFromElectrumAddress(String address, String path) => + Uri.tryParse('tcp://$address$path')!; + +@HiveType(typeId: Node.typeId) +class Node extends HiveObject with Keyable { + Node({ + this.label, + this.login, + this.password, + this.useSSL, + this.trusted = false, + this.socksProxyAddress, + this.path = '', + this.isEnabledForAutoSwitching = false, + String? uri, + WalletType? type, + }) { + if (uri != null) { + uriRaw = uri; + } + if (type != null) { + this.type = type; + } + } + + static Future migrateAllToSqlite(Box nodeBox, Box powNodeBox, + List defaultNodes, List defaultPowNodes) async { + final list = nodeBox.values.toList(); + final powList = powNodeBox.values.toList(); + for (final node in list) { + if(defaultNodes.any((item)=>item.uri == node.uri)) { + // default nodes will be added by validateBuiltinNodes(), no need to migrate them. + await node.delete(); + continue; + } + await node.migrateToSqlite( + isPow: false, + isBuiltin: false, + isOfficial: false); + await node.delete(); + } + for (final node in powList) { + if(defaultPowNodes.any((item)=>item.uri == node.uri)) { + await node.delete(); + continue; + } + await node.migrateToSqlite( + isPow: true, + isBuiltin: false, + isOfficial: false); + await node.delete(); + } + } + + Future migrateToSqlite({required bool isPow, required bool isBuiltin, required bool isOfficial}) async { + final newNode = node_new.Node( + id: key as int, + login: login, + label: label, + password: password, + type: type, + useSSL: useSSL, + trusted: trusted, + socksProxyAddress: socksProxyAddress, + isPow: isPow, + path: path, + uri: uriRaw, + isEnabledForAutoSwitching: isEnabledForAutoSwitching, + isOfficial: isOfficial, + isBuiltin: isBuiltin, + ); + await newNode.save(); + } + + + Node.fromMap(Map map) + : uriRaw = map['uri'] as String? ?? '', + path = map['path'] as String? ?? '', + login = map['login'] as String?, + label = map['label'] as String?, + password = map['password'] as String?, + useSSL = map['useSSL'] as bool?, + trusted = map['trusted'] as bool? ?? false, + socksProxyAddress = map['socksProxyPort'] as String?, + isEnabledForAutoSwitching = map['isEnabledForAutoSwitching'] as bool? ?? false; + + static const typeId = NODE_TYPE_ID; + static const boxName = 'Nodes'; + + @HiveField(0, defaultValue: '') + late String uriRaw; + + @HiveField(1) + String? login; + + @HiveField(2) + String? password; + + @HiveField(3, defaultValue: 0) + late int typeRaw; + + @HiveField(4) + bool? useSSL; + + @HiveField(5, defaultValue: false) + bool trusted; + + @HiveField(6) + String? socksProxyAddress; + + @HiveField(7, defaultValue: '') + String? path; + + @HiveField(8) + bool? isElectrs; + + @HiveField(9) + bool? supportsSilentPayments; + + @HiveField(10) + bool? supportsMweb; + + @HiveField(11, defaultValue: false) + bool isEnabledForAutoSwitching; + + @HiveField(12, defaultValue: '') + String? label; + + bool get isSSL => useSSL ?? false; + + bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty; + + Uri get uri { + switch (type) { + case WalletType.monero: + case WalletType.zcash: + case WalletType.haven: + case WalletType.wownero: + return Uri.http(uriRaw, ''); + case WalletType.bitcoin: + case WalletType.litecoin: + case WalletType.bitcoinCash: + case WalletType.dogecoin: + return createUriFromElectrumAddress(uriRaw, path!); + case WalletType.nano: + case WalletType.banano: + case WalletType.ethereum: + case WalletType.polygon: + case WalletType.base: + case WalletType.bsc: + case WalletType.arbitrum: + case WalletType.solana: + case WalletType.tron: + case WalletType.zano: + case WalletType.decred: + return Uri.parse( + "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") || path!.isEmpty ? path : "/$path"}"); + case WalletType.none: + throw Exception('Unexpected type ${type.toString()} for Node uri'); + } + } + + bool get isValidProxyAddress => socksProxyAddress?.contains(':') ?? false; + + @override + bool operator ==(other) => + other is Node && + (other.uriRaw == uriRaw && + other.login == login && + other.label == label && + other.password == password && + other.typeRaw == typeRaw && + other.useSSL == useSSL && + other.trusted == trusted && + other.socksProxyAddress == socksProxyAddress && + other.path == path); + + @override + int get hashCode => + uriRaw.hashCode ^ + login.hashCode ^ + label.hashCode ^ + password.hashCode ^ + typeRaw.hashCode ^ + useSSL.hashCode ^ + trusted.hashCode ^ + socksProxyAddress.hashCode ^ + path.hashCode; + + @override + dynamic get keyIndex { + _keyIndex ??= key; + return _keyIndex; + } + + WalletType get type => deserializeFromInt(typeRaw); + + set type(WalletType type) => typeRaw = serializeToInt(type); + + dynamic _keyIndex; + + +} \ No newline at end of file diff --git a/cw_core/lib/node_list.dart b/cw_core/lib/node_list.dart new file mode 100644 index 000000000..e46f0eee4 --- /dev/null +++ b/cw_core/lib/node_list.dart @@ -0,0 +1,188 @@ +import 'package:flutter/services.dart'; +import 'package:hive/hive.dart'; +import "package:yaml/yaml.dart"; +import 'package:cw_core/node.dart'; +import 'package:cw_core/wallet_type.dart'; + +Future> loadDefaultNodes(WalletType type) async { + String path; + switch (type) { + case WalletType.monero: + path = 'assets/node_list.yml'; + break; + case WalletType.bitcoin: + path = 'assets/bitcoin_electrum_server_list.yml'; + break; + case WalletType.litecoin: + path = 'assets/litecoin_electrum_server_list.yml'; + break; + case WalletType.haven: + path = 'assets/haven_node_list.yml'; + break; + // TODO: (refactoring) each wallet would have its path, so `wallet.nodePath` would be decided based on chain id in Evm wallet + case WalletType.ethereum: + path = 'assets/ethereum_server_list.yml'; + break; + case WalletType.nano: + path = 'assets/nano_node_list.yml'; + break; + case WalletType.bitcoinCash: + path = 'assets/bitcoin_cash_electrum_server_list.yml'; + break; + case WalletType.polygon: + path = 'assets/polygon_node_list.yml'; + break; + case WalletType.solana: + path = 'assets/solana_node_list.yml'; + break; + case WalletType.tron: + path = 'assets/tron_node_list.yml'; + break; + case WalletType.wownero: + path = 'assets/wownero_node_list.yml'; + break; + case WalletType.zano: + path = 'assets/zano_node_list.yml'; + break; + case WalletType.decred: + path = 'assets/decred_node_list.yml'; + break; + case WalletType.dogecoin: + path = 'assets/dogecoin_electrum_server_list.yml'; + break; + case WalletType.base: + path = 'assets/base_node_list.yml'; + break; + case WalletType.arbitrum: + path = 'assets/arbitrum_node_list.yml'; + break; + case WalletType.zcash: + path = 'assets/zcash_node_list.yml'; + break; + case WalletType.bsc: + path = 'assets/bsc_node_list.yml'; + break; + case WalletType.banano: + case WalletType.none: + path = ''; + break; + } + + final nodesRaw = await rootBundle.loadString(path); + final loadedNodes = loadYaml(nodesRaw) as YamlList; + final nodes = []; + + for (final raw in loadedNodes) { + if (raw is Map) { + final map = Map.from(raw); + map["isBuiltin"] = 1; + map["isOfficial"] = ((map["isOfficial"] as bool? ?? false) ? 1 : 0); + map["isDefault"] = ((map["isDefault"] as bool? ?? false) ? 1 : 0); + final node = Node.fromMap(Map.from(map)); + node.type = type; + nodes.add(node); + } + } + + return nodes; +} + +Future> loadDefaultNanoPowNodes() async { + final powNodesRaw = await rootBundle.loadString('assets/nano_pow_node_list.yml'); + final loadedPowNodes = loadYaml(powNodesRaw) as YamlList; + final nodes = []; + + for (final raw in loadedPowNodes) { + if (raw is Map) { + final node = Node.fromMap(Map.from(raw)); + node.isPow = true; + node.type = WalletType.nano; + nodes.add(node); + } + } + + return nodes; +} + +Future> loadAllDefaultNodes() async { + final moneroNodes = await loadDefaultNodes(WalletType.monero); + final bitcoinElectrumServerList = await loadDefaultNodes(WalletType.bitcoin); + final litecoinElectrumServerList = await loadDefaultNodes(WalletType.litecoin); + final bitcoinCashElectrumServerList = await loadDefaultNodes(WalletType.bitcoinCash); + final havenNodes = await loadDefaultNodes(WalletType.haven); + final ethereumNodes = await loadDefaultNodes(WalletType.ethereum); + final nanoNodes = await loadDefaultNodes(WalletType.nano); + final polygonNodes = await loadDefaultNodes(WalletType.polygon); + final solanaNodes = await loadDefaultNodes(WalletType.solana); + final tronNodes = await loadDefaultNodes(WalletType.tron); + final decredNodes = await loadDefaultNodes(WalletType.decred); + final zanoNodes = await loadDefaultNodes(WalletType.zano); + final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin); + final baseNodes = await loadDefaultNodes(WalletType.base); + final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum); + final zcashNodes = await loadDefaultNodes(WalletType.zcash); + final bscNodes = await loadDefaultNodes(WalletType.bsc); + + return moneroNodes + + bitcoinElectrumServerList + + litecoinElectrumServerList + + havenNodes + + ethereumNodes + + bitcoinCashElectrumServerList + + nanoNodes + + polygonNodes + + solanaNodes + + tronNodes + + zanoNodes + + decredNodes + + dogecoinElectrumServerList + + baseNodes + + arbitrumNodes + + zcashNodes + + bscNodes; +} + +Future resetToDefault() async { + + final nodes = await loadAllDefaultNodes(); + + final currentNodes = await Node.getAll(); + + // after reset, nodes should retain the id to correspond with the ones in shared prefs. + // alternative would be nuking the shared prefs on reset + for (final node in nodes) { + try { + final curr = currentNodes.firstWhere( + (item) => item.uri == node.uri && item.typeRaw == node.typeRaw, + ); + node.id = curr.id; + } catch(e) {} + + } + + await Node.deleteAll(); + for(final node in nodes) { + await node.save(); + } +} + +Future resetPowToDefault() async { + final nanoPowNodes = await loadDefaultNanoPowNodes(); + await Node.deleteAllPow(); + for(final node in nanoPowNodes) { + node.save(); + } +} + +Future getDefaultNodeFromFiles(WalletType type, {bool isPow = false}) async { + final nodes = isPow ? await loadDefaultNanoPowNodes() : await loadDefaultNodes(type); + try { + return nodes.firstWhere((item)=>item.isDefault); + } catch(e) { + // ideally the yamls would always have a default, but this is better than nothing + return nodes.first; + } +} + + + diff --git a/lib/core/node_switching_service.dart b/lib/core/node_switching_service.dart index c17ec2e47..d7908eebe 100644 --- a/lib/core/node_switching_service.dart +++ b/lib/core/node_switching_service.dart @@ -14,7 +14,6 @@ class NodeSwitchingService { NodeSwitchingService({ required this.appStore, required this.settingsStore, - required this.nodeSource, }); static const int _healthCheckIntervalSeconds = 30; @@ -38,7 +37,6 @@ class NodeSwitchingService { final AppStore appStore; final SettingsStore settingsStore; - final Box nodeSource; final Map> _usedNodeKeys = {}; @@ -118,13 +116,13 @@ class NodeSwitchingService { WalletType walletType, ) async { for (final node in nodes) { - if (!_usedNodeKeys[walletType]!.contains(node.key)) { + if (!_usedNodeKeys[walletType]!.contains(node.id)) { final isActive = await node.requestNode(); if (isActive) { return node; } else { printV('Node ${node.uriRaw} is not active. Marking as used.'); - _usedNodeKeys[walletType]!.add(node.key); + _usedNodeKeys[walletType]!.add(node.id); } } } @@ -157,7 +155,7 @@ class NodeSwitchingService { final currentNode = settingsStore.getCurrentNode(nodeWalletType, chainId: chainId); // Get all trusted nodes for this wallet type - final trustedNodes = nodeSource.values + final trustedNodes = (await Node.getAll()) .where((node) => node.type == nodeWalletType && node.isEnabledForAutoSwitching) .toList(); @@ -171,8 +169,8 @@ class NodeSwitchingService { _usedNodeKeys.putIfAbsent(nodeWalletType, () => []); // Add current node to used list if not already there - if (!_usedNodeKeys[nodeWalletType]!.contains(currentNode.key)) { - _usedNodeKeys[nodeWalletType]!.add(currentNode.key); + if (!_usedNodeKeys[nodeWalletType]!.contains(currentNode.id)) { + _usedNodeKeys[nodeWalletType]!.add(currentNode.id); } // Try to find an active unused node @@ -199,8 +197,8 @@ class NodeSwitchingService { } // Ensure the selected node is marked as used - if (!_usedNodeKeys[nodeWalletType]!.contains(nextNode.key)) { - _usedNodeKeys[nodeWalletType]!.add(nextNode.key); + if (!_usedNodeKeys[nodeWalletType]!.contains(nextNode.id)) { + _usedNodeKeys[nodeWalletType]!.add(nextNode.id); } printV( diff --git a/lib/di.dart b/lib/di.dart index 50a7cefcf..aa42887d7 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -326,8 +326,8 @@ import 'src/screens/buy/buy_sell_page.dart'; final getIt = GetIt.instance; var _isSetupFinished = false; -late Box _nodeSource; -late Box _powNodeSource; +// late Box _nodeSource; +// late Box _powNodeSource; late Box _contactSource; late Box