Compare commits

...

48 Commits

Author SHA1 Message Date
lucretius 7dd665131e Merge pull request 'Merge upstream branch dev' (#1) from hush/SilentDragonLite:dev into dev 7 months ago
jahway603 6183c244e5 Merge pull request 'Ubuntu 18.04 is now EOL, so removing from README' (#131) from jahway603/SilentDragonLite:jah-doc into dev 7 months ago
jahway603 5114b76be2 Ubuntu 18.04 is now EOL, so removing from README 7 months ago
Duke 973ec2e823 Remove res/libsodium.a from git 7 months ago
duke eb7a083d41 Merge pull request 'mempool integration' (#126) from lucretius/SilentDragonLite:mempool-new into dev 7 months ago
Deniod 3962b42e30 mempool integration 7 months ago
duke aadb7e7c9e Merge pull request 'Add build instruction for Ubuntu 22.04' (#125) from onryo-build1 into dev 8 months ago
onryo 9a7e87fa1d Add build instruction for Ubuntu 22.04 8 months ago
onryo 2519acdd7a Merge pull request 'Add files to create deb package' (#123) from onryo/SilentDragonLite:dev into dev 1 year ago
onryo aa58e3a4ce Initial docs 1 year ago
onryo 50964ce8b7 Update 'contrib/debian/control' 1 year ago
onryo 2e23886d86 add compat 1 year ago
onryo d8ff0de15d add copyright 1 year ago
onryo b8c18aa38b add rules 1 year ago
onryo 1f31c40172 add changelog 1 year ago
onryo eca9c53ff7 add control file 1 year ago
Duke 828a912d85 Do not change servers when retrying sync RPC 1 year ago
Duke bfdda1f1af Try really hard to make sync RPC finish 1 year ago
Duke 5b33cb3638 Much DEBUG such wow 1 year ago
Duke a3987fca13 Add note for improvement 1 year ago
Duke d05d8271ec Add SDL dev docs 1 year ago
Duke 69cf815ed2 litelib_initialize_new returns a seed when it succeeds, not OK 1 year ago
Duke f46e1b4a57 Potentially fix #120 1 year ago
Duke 77ac1f99ae Try to fix #119 1 year ago
Duke 7364e21f99 Check for a non-empty response from litelib_execute 1 year ago
Duke 51fe4d6cde Look for non-empty responses instead of the string "OK" 1 year ago
Duke a080d0ca80 Update current server on info tab since it can change at run-time 1 year ago
Duke b84828604f Catch exceptions when parsing invalid json 1 year ago
Duke 7e54360b72 Try another server if we get an error when executing an RPC, possibly fixes #119 1 year ago
Duke 4116de6a69 Merge branch 'dev' of https://git.hush.is/hush/SilentDragonLite into dev 1 year ago
Duke 9befa3450f Try another server if current is down when saving wallet via a rescan 1 year ago
fekt 5508050fcc Update 'res/css/Dark.css' 1 year ago
Duke c7e0f0fae6 Initialize a new server connection from seedphrase when restoring from seed 1 year ago
Duke ad5b294d95 Make sure to init from phrase when trying a new server during restore in first time wizard 1 year ago
Duke 89bf5b0f7c Make sure to init from phrase when trying a new server during restore 1 year ago
Duke 51483843ac Report down server in errors of first time wizard 1 year ago
Duke 17fcb84a89 Report server in error when saving wallet as part of rescan 1 year ago
Duke f22f97463b derp 1 year ago
Duke f7787fe9e9 Try another server if current is down when restoring during a rescan; report down server in error 1 year ago
Duke 0b72d01f4a Add DEBUG macro for less typing 1 year ago
Duke 557e10e5e8 Try another server if current is down when restoring a wallet from seed or saving a wallet 1 year ago
Duke 3f8ae1f9d7 Try another server if current is down when creating new seed 1 year ago
Duke 3b6da338c9 Delete mobile app connector 1 year ago
Duke 430a7ab474 Delete websuckets 1 year ago
Duke 5d5447aced Improve error handling when restoring from seedphrase 1 year ago
Duke 1e6e77055b Catch exceptions from litelib_initialize_existing 1 year ago
Duke 6165733e03 Ignore exceptions in litelib_initialize_existing() in a few places 1 year ago
duke 6590a49bb5 Update 'doc/release-process.md' 1 year ago
  1. 2
      .travis.yml
  2. 15
      README.md
  3. 11
      contrib/debian/README.md
  4. 5
      contrib/debian/changelog
  5. 1
      contrib/debian/compat
  6. 13
      contrib/debian/control
  7. 5
      contrib/debian/copyright
  8. 20
      contrib/debian/rules
  9. 62
      doc/developer.md
  10. 11
      doc/release-process.md
  11. 2
      doc/win/DEVELOPING-Ubuntu-18-04.md
  12. 2
      lib/Cargo.lock
  13. 2
      lib/Cargo.toml
  14. 31
      lib/src/lib.rs
  15. 2
      res/css/Dark.css
  16. BIN
      res/libsodium.a
  17. 6
      silentdragon-lite.pro
  18. 282
      src/connection.cpp
  19. 1
      src/connection.h
  20. 70
      src/controller.cpp
  21. 6
      src/datamodel.h
  22. 149
      src/firsttimewizard.cpp
  23. 1
      src/main.cpp
  24. 130
      src/mainwindow.cpp
  25. 10
      src/mainwindow.h
  26. 41
      src/mainwindow.ui
  27. 16
      src/mobileappconnector.cpp
  28. 24
      src/mobileappconnector.h
  29. 214
      src/mobileappconnector.ui
  30. 1
      src/precompiled.h
  31. 2
      src/scripts/docker/Dockerfile
  32. 1
      src/sdl.h
  33. 14
      src/settings.cpp
  34. 2
      src/version.h
  35. 940
      src/websockets.cpp
  36. 179
      src/websockets.h

2
.travis.yml

@ -25,7 +25,7 @@ before_install:
- sudo add-apt-repository ppa:beineri/opt-qt-5.14.2-xenial -y
- sudo apt-get update -qq
- sudo apt-get install libsodium-dev pkg-config
- sudo apt-get install qt514base qt514websockets libgl1-mesa-dev
- sudo apt-get install qt514base libgl1-mesa-dev
- source /opt/qt514/bin/qt514-env.sh
- chmod +x res/libsodium/buildlibsodium.sh

15
README.md

@ -49,11 +49,20 @@ rustup -V
**Nothing below will work without the Linux "build-essential" package. Check that your system has it installed. If not, and you're using a Ubuntu/Debian distro, then you can install with `apt install build-essential`.**
##### Ubuntu 22.04:
```shell script
sudo apt-get -y install build-essential qtbase5-dev qt5-qmake qtcreator qttools5-dev-tools
```
##### Ubuntu 20.04:
```shell script
sudo apt-get -y install build-essential qt5-default qt5-qmake qtcreator qttools5-dev-tools
```
Compiling can take some time, so be patient and wait for it to finish. It will take potentially a long time for slower systems. Be Patient and please report compiler problems!
##### Ubuntu 18.04 and 20.04:
```shell script
sudo apt-get -y install build-essential qt5-default qt5-qmake libqt5websockets5-dev qtcreator qttools5-dev-tools
git clone https://git.hush.is/hush/SilentDragonLite
cd SilentDragonLite
./build.sh linguist
@ -63,7 +72,7 @@ cd SilentDragonLite
### Other notes
#### Install Torsocks (or any other Socks service for TOR) on Ubuntu 18.04
#### Install Torsocks (or any other Socks service for TOR) on Ubuntu
```shell script
sudo apt update
sudo apt install torsocks

11
contrib/debian/README.md

@ -0,0 +1,11 @@
Install build tools:
```
sudo apt install dh-make
```
To build the package from source run the following:
```
dpkg-buildpackage -rfakeroot -b -uc -us
```

5
contrib/debian/changelog

@ -0,0 +1,5 @@
silentdragonlite (1.5.3) stable; urgency=medium
* 1.5.3.1 release.
-- onryo <onryo@hush.land> Sat, 10 Jun 2023 15:20:30 +0200

1
contrib/debian/compat

@ -0,0 +1 @@
9

13
contrib/debian/control

@ -0,0 +1,13 @@
Source: silentdragonlite
Section: utils
Priority: optional
Maintainer: onryo <onryo@hush.land>
Standards-Version: 4.6.0
Homepage: https://hush.is
Vcs-Browser: https://git.hush.is/hush/SilentDragonLite
Vcs-Git: https://git.hush.is/hush/SilentDragonLite.git
Package: silentdragonlite
Architecture: amd64 arm64
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: SilentDragonLite is a lightwallet for HUSH which does not require you to download the full blockchain.

5
contrib/debian/copyright

@ -0,0 +1,5 @@
Files: *
Copyright: 2019-2023, The Hush developers
2018-2019, The Zcash developers
License: GPLv3
Comment: https://hush.is/developers

20
contrib/debian/rules

@ -0,0 +1,20 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable)
# output every command that modifies files on the build system.
#export DH_VERBOSE = 1
# see FEATURE AREAS in dpkg-buildflags(1)
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# see ENVIRONMENT in dpkg-buildflags(1)
# package maintainers to append CFLAGS
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# package maintainers to append LDFLAGS
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
%:
dh $@
# dh_make generated override targets
# This is example for Cmake (See https://bugs.debian.org/641051 )
#override_dh_auto_configure:
# dh_auto_configure -- # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

62
doc/developer.md

@ -0,0 +1,62 @@
# Developer Docs for SDL
Random stuff that is useful for devs.
# Checking return values from litelib
There are 3 functions written in Rust that live in lib/src/lib.rs :
* `litelib_initialize_new`
* create a new client/connection and brand new wallet
* `litelib_initialize_new_from_phrase`
* create a new client/connection from a seedphrase (restoring from seedphrase)
* `litelib_initialize_existing`
* create a new client/connection with an already existing wallet
The Rust code calls it a "LightClient" while the C++ of SDL calls it a "Connection".
When `litelib_initialize_existing` or `litelib_initialize_new_from_phrase` return successfully, they return the string "OK" (which is not JSON).
When `litelib_initialize_new` returns successfully it returns JSON that looks like :
```
{"seed":"seed","birthday":birthday}
```
where "seed" is a 24 word seed and birthday is an integer block height.
So when calling these 3 functions, which looks almost the same in the calling code, the code which checks if they worked will be different, depending on what each returns on success.
When checking the return value of `litelib_initialize_existing` or `litelib_initialize_new_from_phrase` it should look like :
```
QString reply = "";
try {
char* resp = litelib_initialize_new_from_phrase(...);
reply = litelib_process_response(resp);
} catch {
...
}
if (reply.isEmpty())) {
// litelib_initialize_new_from_phrase failed
...
}
```
Yes, `isEmpty()` is not a very strict check, we could actually check for valid-looking JSON (starts with a { and ends with a }) as well as making sure the keys "seed" and "birthday" exist. Please implement this.
When checking the return value of `litelib_initialize_new` it should look like :
```
QString reply = "";
try {
char* resp = litelib_initialize_new(...);
reply = litelib_process_response(resp);
} catch {
...
}
if (reply.toUpper().trimmed() != "OK") {
// litelib_initialize_new failed
...
}
```

11
doc/release-process.md

@ -55,4 +55,13 @@ To update the file run `cargo vendor` as was mentioned in https://git.hush.is/hu
## Release process
...
* Write release notes
* Choose a release name
* Make Gitea release from master branch
* You can either manually make a git tag or let Gitea do it, which is easier
* Make binaries
* Windows exe
* Windows msi
* Linux
* Mac
* Debian package

2
doc/win/DEVELOPING-Ubuntu-18-04.md

@ -56,7 +56,7 @@ mkdir ~/git && cd ~/git
git clone https://github.com/mxe/mxe.git
cd mxe
make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase qtwebsockets
make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase
```
# Build SilentDragonLite .exe

2
lib/Cargo.lock

@ -1849,7 +1849,7 @@ dependencies = [
[[package]]
name = "silentdragonlitelib"
version = "0.1.0"
source = "git+https://git.hush.is/hush/silentdragonlite-cli?rev=0181b16fd037f98c760e668bb6af8a41dd0d6267#0181b16fd037f98c760e668bb6af8a41dd0d6267"
source = "git+https://git.hush.is/lucretius/silentdragonlite-cli?rev=eff5dd7b6dbcfd7e4db6fdba423399337b590722#eff5dd7b6dbcfd7e4db6fdba423399337b590722"
dependencies = [
"base58",
"bellman",

2
lib/Cargo.toml

@ -12,4 +12,4 @@ crate-type = ["staticlib"]
libc = "0.2.58"
lazy_static = "1.4.0"
blake3 = "0.3.4"
silentdragonlitelib = { git = "https://git.hush.is/hush/silentdragonlite-cli", rev = "0181b16fd037f98c760e668bb6af8a41dd0d6267" }
silentdragonlitelib = { git = "https://git.hush.is/lucretius/silentdragonlite-cli", rev = "eff5dd7b6dbcfd7e4db6fdba423399337b590722" }

31
lib/src/lib.rs

@ -92,7 +92,15 @@ pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *
}
};
LIGHTCLIENT.lock().unwrap().replace(Some(Arc::new(lightclient)));
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => {println!("Starting Mempool")},
Err(e) => {
println!("Couldnt start mempool {}", e)
}
}
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
// Return the wallet's seed
let s_str = CString::new(seed).unwrap();
@ -135,7 +143,15 @@ pub extern fn litelib_initialize_new_from_phrase(dangerous: bool,server: *const
// Initialize logging
let _ = lightclient.init_logging();
LIGHTCLIENT.lock().unwrap().replace(Some(Arc::new(lightclient)));
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => {println!("Starting Mempool")},
Err(e) => {
println!("Couldnt start mempool {}",e)
}
}
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
let c_str = CString::new("OK").unwrap();
return c_str.into_raw();
@ -169,8 +185,17 @@ pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char
// Initialize logging
let _ = lightclient.init_logging();
let lc = Arc::new(lightclient);
match LightClient::start_mempool_monitor(lc.clone()) {
Ok(_) => {println!("Starting Mempool")},
Err(e) => {
println!("Couldnt start mempool {}",e)
}
}
LIGHTCLIENT.lock().unwrap().replace(Some(Arc::new(lightclient)));
LIGHTCLIENT.lock().unwrap().replace(Some(lc));
let c_str = CString::new("OK").unwrap();
return c_str.into_raw();

2
res/css/Dark.css

@ -25,7 +25,7 @@ QTabWidget QTabBar::tab:hover {
background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.25, stop: 0 #747577, stop: 1 #3E4244);
color:#fff;
border: 1px ridge #fff;
min-height: 20px
min-height: 20px;
}
QHeaderView { /* Table Header */

BIN
res/libsodium.a

Binary file not shown.

6
silentdragon-lite.pro

@ -13,7 +13,6 @@ CONFIG += precompile_header
PRECOMPILED_HEADER = src/precompiled.h
QT += widgets
QT += websockets
TARGET = SilentDragonLite
TEMPLATE = app
@ -59,8 +58,6 @@ SOURCES += \
src/addressbook.cpp \
src/logger.cpp \
src/addresscombo.cpp \
src/websockets.cpp \
src/mobileappconnector.cpp \
src/recurring.cpp \
src/requestdialog.cpp \
src/memoedit.cpp \
@ -104,8 +101,6 @@ HEADERS += \
src/addressbook.h \
src/logger.h \
src/addresscombo.h \
src/websockets.h \
src/mobileappconnector.h \
src/recurring.h \
src/requestdialog.h \
src/memoedit.h \
@ -143,7 +138,6 @@ FORMS += \
src/connection.ui \
src/addressbook.ui \
src/memodialog.ui \
src/mobileappconnector.ui \
src/createhushconfdialog.ui \
src/recurringdialog.ui \
src/requestContactDialog.ui \

282
src/connection.cpp

@ -9,6 +9,8 @@
#include "controller.h"
#include "../lib/silentdragonlitelib.h"
#include "precompiled.h"
#include <QThreadPool>
#include "sdl.h"
using json = nlohmann::json;
@ -32,7 +34,7 @@ ConnectionLoader::ConnectionLoader(MainWindow* main, Controller* rpc)
connD->setupUi(d);
auto theme = Settings::getInstance()->get_theme_name();
qDebug() << theme << "theme " << theme << " has loaded";
DEBUG("theme " << theme << " has loaded");
auto size = QSize(512,512);
if (theme == "Dark" || theme == "Midnight") {
@ -55,6 +57,7 @@ ConnectionLoader::ConnectionLoader(MainWindow* main, Controller* rpc)
ConnectionLoader::~ConnectionLoader()
{
DEBUG("destroying ConnectionLoader");
delete isSyncing;
delete connD;
delete d;
@ -62,6 +65,7 @@ ConnectionLoader::~ConnectionLoader()
void ConnectionLoader::loadConnection()
{
DEBUG("calling doAutoConnect");
QTimer::singleShot(1, [=]() { this->doAutoConnect(); });
if (!Settings::getInstance()->isHeadless())
d->exec();
@ -69,7 +73,22 @@ void ConnectionLoader::loadConnection()
void ConnectionLoader::loadProgress()
{
QTimer::singleShot(1, [=]() { this->ShowProgress(); });
bool failed = false;
QTimer::singleShot(1, [=]() mutable {
DEBUG("failed=" << failed);
// continually retry ShowProgress() until it succeeds
// by running without an exception
do {
try {
this->ShowProgress();
failed = false;
} catch (const std::exception& e) {
DEBUG("caught exception " << e.what() );
failed = true;
}
} while (failed);
});
if (!Settings::getInstance()->isHeadless())
d->exec();
}
@ -83,56 +102,62 @@ void ConnectionLoader::ShowProgress()
auto connection = makeConnection(config);
auto me = this;
qDebug() << __func__ << ": server=" << config->server << " connection=" << connection << " me=" << me;
DEBUG("server=" << config->server << " connection=" << connection << " me=" << me);
isSyncing = new QAtomicInteger<bool>();
isSyncing->store(true);
main->logger->write("isSyncing");
DEBUG("isSyncing");
// Do a sync after import
syncTimer = new QTimer(main);
main->logger->write("Beginning sync after import wif");
connection->doRPCWithDefaultErrorHandling("sync", "", [=](auto) {
DEBUG("Beginning sync after import wif");
connection->doRPC("sync", "", [=](auto) {
DEBUG("finished syncing");
isSyncing->store(false);
// Cancel the timer
syncTimer->deleteLater();
// When sync is done, set the connection
this->doRPCSetConnectionShield(connection);
}, [=](auto) {
DEBUG("sync rpc error! server=" << config->server);
});
// While it is syncing, we'll show the status updates while it is alive.
QObject::connect(syncTimer, &QTimer::timeout, [=]() {
// Check the sync status
DEBUG("Check the sync status");
if (isSyncing != nullptr && isSyncing->load()) {
// Get the sync status
DEBUG("Get the sync status");
try {
connection->doRPC("syncstatus", "", [=](json reply) {
if (isSyncing != nullptr && reply.find("synced_blocks") != reply.end())
{
qint64 synced = reply["synced_blocks"].get<json::number_unsigned_t>();
qint64 total = reply["total_blocks"].get<json::number_unsigned_t>();
me->showInformation(
"Syncing... " + QString::number(synced) + " / " + QString::number(total)
);
}
},
[=](QString err) {
qDebug() << "Sync error" << err;
});
}catch (...)
{
main->logger->write("catch sync progress reply");
}
}
});
connection->doRPC("syncstatus", "", [=](json reply) {
if (isSyncing != nullptr && reply.find("synced_blocks") != reply.end()) {
qint64 synced = reply["synced_blocks"].get<json::number_unsigned_t>();
qint64 total = reply["total_blocks"].get<json::number_unsigned_t>();
me->showInformation(
"Syncing... " + QString::number(synced) + " / " + QString::number(total)
);
}
}, [=](QString err) {
DEBUG("Sync error " << err);
// We may have gotten "Unexpected compression flag: 60"
// or some other error, so let's try another server
config->server = Settings::getRandomServer();
DEBUG("Changed server to " << config->server );
});
} catch (const std::exception& e) {
DEBUG("syncstatus exception: " << e.what() );
main->logger->write("catch sync progress reply");
syncTimer->setInterval(1* 1000);
syncTimer->start();
main->logger->write("Start sync timer");
// rethrow exception so loadProgress can catch
// it and retry the entire ShowProgress() function again
throw new std::runtime_error(std::string("syncstatus failed"));
}
}
});
int interval = 1*1000;
syncTimer->setInterval(interval);
syncTimer->start();
DEBUG("Start sync timer with interval=" << interval);
}
void ConnectionLoader::doAutoConnect()
@ -140,43 +165,53 @@ void ConnectionLoader::doAutoConnect()
auto config = std::shared_ptr<ConnectionConfig>(new ConnectionConfig());
config->dangerous = false;
config->server = Settings::getInstance()->getSettings().server;
qDebug() << __func__ << " server=" << config->server;
DEBUG(" server=" << config->server);
// Initialize the library
main->logger->write(QObject::tr("Attempting to initialize library with ") + config->server);
DEBUG("Attempting to initialize library with "<< config->server);
// Check to see if there's an existing wallet
if (litelib_wallet_exists(Settings::getDefaultChainName().toStdString().c_str())) {
qDebug() << __func__ << ": using existing wallet";
DEBUG("using existing wallet");
main->logger->write(QObject::tr("Using existing wallet."));
char* resp = litelib_initialize_existing(
config->dangerous,
config->server.toStdString().c_str()
);
QString response = litelib_process_response(resp);
if (response.toUpper().trimmed() != "OK") {
config->server = Settings::getRandomServer();
resp = litelib_initialize_existing(
QString response = "";
try {
char* resp = litelib_initialize_existing(
config->dangerous,
config->server.toStdString().c_str()
);
response = litelib_process_response(resp);
} catch (const std::exception& e) {
DEBUG("caught an exception, ignoring: " << e.what());
}
if (response.toUpper().trimmed() != "OK") {
config->server = Settings::getRandomServer();
try {
char* resp = litelib_initialize_existing(
config->dangerous,
config->server.toStdString().c_str()
);
response = litelib_process_response(resp);
} catch (const std::exception& e) {
DEBUG("caught an exception, ignoring: " << e.what());
}
if (response.toUpper().trimmed() != "OK") {
QString resp = "Error when connecting to " + config->server + ": " + response;
showError(resp);
return;
} else {
qDebug() << __func__ << ": Successfully connected to random server: " << config->server << " !!!";
DEBUG("Successfully connected to random server: " << config->server << " !!!");
}
} else {
qDebug() << __func__ << ": Successfully connected to " << config->server << " !!!";
DEBUG("Successfully connected to " << config->server << " !!!");
}
} else {
qDebug() << __func__ << ": no existing wallet";
DEBUG("no existing wallet");
main->logger->write(QObject::tr("Create/restore wallet."));
createOrRestore(config->dangerous, config->server);
d->show();
@ -187,61 +222,77 @@ void ConnectionLoader::doAutoConnect()
qDebug() << __func__ << ": server=" << config->server
<< " connection=" << connection << " me=" << me << endl;
// After the lib is initialized, try to do get info
connection->doRPC("info", "", [=](auto reply) {
// If success, set the connection
main->logger->write("Connection is online.");
DEBUG("Connection is online.");
connection->setInfo(reply);
main->logger->write("getting Connection reply");
DEBUG("getting Connection reply");
isSyncing = new QAtomicInteger<bool>();
isSyncing->store(true);
main->logger->write("isSyncing");
DEBUG("isSyncing");
// Do a sync at startup
syncTimer = new QTimer(main);
main->logger->write("Beginning sync");
connection->doRPCWithDefaultErrorHandling("sync", "", [=](auto) {
DEBUG("Beginning sync");
connection->doRPC("sync", "", [=](auto) {
DEBUG("finished syncing");
isSyncing->store(false);
// Cancel the timer
syncTimer->deleteLater();
// When sync is done, set the connection
this->doRPCSetConnection(connection);
}, [=](auto) mutable {
DEBUG("sync rpc error! server=" << config->server);
// continually retry sync RPC until it succeeds
// don't change server each time it fails
bool failed = true;
do {
// config->server = Settings::getRandomServer();
// auto connection = makeConnection(config);
// DEBUG("changed server to " << config->server);
connection->doRPC("sync", "", [=](auto) mutable {
DEBUG("sync success with server=" << config->server);
failed = false;
isSyncing->store(false);
// Cancel the timer
syncTimer->deleteLater();
// When sync is done, set the connection
this->doRPCSetConnection(connection);
}, [=](auto) {
DEBUG("sync failed with server=" << config->server << " . continuing sync loop" );
});
} while (failed);
});
// While it is syncing, we'll show the status updates while it is alive.
QObject::connect(syncTimer, &QTimer::timeout, [=]() {
// Check the sync status
DEBUG("Check the sync status");
if (isSyncing != nullptr && isSyncing->load()) {
// Get the sync status
DEBUG("Getting the sync status");
try {
connection->doRPC("syncstatus", "", [=](json reply) {
if (isSyncing != nullptr && reply.find("synced_blocks") != reply.end())
{
if (isSyncing != nullptr && reply.find("synced_blocks") != reply.end()) {
qint64 synced = reply["synced_blocks"].get<json::number_unsigned_t>();
qint64 total = reply["total_blocks"].get<json::number_unsigned_t>();
me->showInformation(
"Syncing... " + QString::number(synced) + " / " + QString::number(total)
);
}
},
[=](QString err) {
qDebug() << "Sync error" << err;
});
}catch (...)
{
main->logger->write("catch sync progress reply");
}
},
[=](QString err) {
DEBUG("syncstatus error" << err);
});
} catch (const std::exception& e) {
DEBUG("caught exception from syncstatus: " << e.what());
}
}
});
syncTimer->setInterval(1* 1000);
int interval = 1*1000;
syncTimer->setInterval(interval);
syncTimer->start();
main->logger->write("Start sync timer");
DEBUG("Start sync timer with interval=" << interval);
}, [=](QString err) {
showError(err);
@ -255,13 +306,13 @@ void ConnectionLoader::createOrRestore(bool dangerous, QString server)
d->hide();
// Create a wizard
FirstTimeWizard wizard(dangerous,server);
main->logger->write("Start new Wallet with FirstimeWizard");
DEBUG("Start new Wallet with FirstimeWizard");
wizard.exec();
}
void ConnectionLoader::doRPCSetConnection(Connection* conn)
{
qDebug() << "Connectionloader finished, setting connection";
DEBUG("Connectionloader finished, setting connection");
main->logger->write("Connectionloader finished, setting connection");
rpc->setConnection(conn);
d->accept();
@ -272,17 +323,16 @@ void ConnectionLoader::doRPCSetConnection(Connection* conn)
main->logger->write("Path to Wallet.dat : " );
qDebug() << __func__ << ": wallet path =" << plaintextWallet;
plaintextWallet.remove();
} catch (...) {
qDebug() << "No plaintext wallet found! file=" << plaintextWallet;
} catch (const std::exception& e) {
DEBUG("Caught exception" << e.what() );
DEBUG("No plaintext wallet found! file=" << plaintextWallet);
main->logger->write("no Plaintext wallet.dat");
}
}
void ConnectionLoader::doRPCSetConnectionShield(Connection* conn)
{
qDebug() << "Importing finished, setting connection";
DEBUG("Importing finished, setting connection");
rpc->setConnection(conn);
d->accept();
main->getRPC()->shield([=] (auto) {});
@ -293,9 +343,10 @@ void ConnectionLoader::doRPCSetConnectionShield(Connection* conn)
main->logger->write("Path to Wallet.dat : " );
qDebug() << __func__ << ": wallet path =" << plaintextWallet;
plaintextWallet.remove();
} catch (...) {
} catch (const std::exception& e) {
DEBUG("Caught exception" << e.what() );
main->logger->write("no Plaintext wallet.dat");
qDebug() << "No plaintext wallet found! file=" << plaintextWallet;
DEBUG("No plaintext wallet found! file=" << plaintextWallet);
}
}
@ -346,19 +397,50 @@ QString litelib_process_response(char* resp)
************************************************************************************/
void Executor::run()
{
char* resp = litelib_execute(this->cmd.toStdString().c_str(), this->args.toStdString().c_str());
QString reply = litelib_process_response(resp);
auto parsed = json::parse(
reply.toStdString().c_str(),
nullptr,
false
);
if (parsed.is_discarded() || parsed.is_null())
emit handleError(reply);
auto config = std::shared_ptr<ConnectionConfig>(new ConnectionConfig());
DEBUG("cmd=" << cmd << " args=" << args << " server=" << config->server);
QString response = "";
try {
char* resp = litelib_execute(this->cmd.toStdString().c_str(), this->args.toStdString().c_str());
response = litelib_process_response(resp);
} catch (const std::exception& e) {
DEBUG("ignoring exception: " << e.what() );
}
else
//TODO: we can do stricter error checking
if (response.isEmpty()) {
config->server = Settings::getRandomServer();
try {
char* resp = litelib_initialize_existing(
config->dangerous,
config->server.toStdString().c_str()
);
response = litelib_process_response(resp);
resp = litelib_execute(this->cmd.toStdString().c_str(), this->args.toStdString().c_str());
response = litelib_process_response(resp);
} catch (const std::exception& e) {
DEBUG("server= " << config->server << " gave exception: " << e.what() );
emit handleError(response);
}
}
try {
auto parsed = json::parse(
response.toStdString().c_str(),
nullptr,
false
);
if (parsed.is_discarded() || parsed.is_null()) {
emit handleError(response);
} else {
emit responseReady(parsed);
}
} catch (const std::exception& e) {
DEBUG("exception when parsing json: " << e.what() );
emit handleError(response);
}
emit responseReady(parsed);
}
void Callback::processRPCCallback(json resp)
@ -386,11 +468,12 @@ Connection::Connection(MainWindow* m, std::shared_ptr<ConnectionConfig> conf)
void Connection::doRPC(const QString cmd, const QString args, const std::function<void(json)>& cb, const std::function<void(QString)>& errCb)
{
if (shutdownInProgress)
// Ignoring RPC because shutdown in progress
if (shutdownInProgress) {
DEBUG("Ignoring RPC because shutdown in progress");
return;
}
qDebug() << __func__ << ": " << cmd;
DEBUG("cmd=" << cmd << " args=" << args);
// Create a runner.
auto runner = new Executor(cmd, args);
@ -405,7 +488,7 @@ void Connection::doRPC(const QString cmd, const QString args, const std::functio
void Connection::doRPCWithDefaultErrorHandling(const QString cmd, const QString args, const std::function<void(json)>& cb)
{
qDebug() << __func__ << ": " << cmd;
DEBUG("cmd=" << cmd << " args=" << args);
doRPC(cmd, args, cb, [=] (QString err) {
this->showTxError(err);
});
@ -413,7 +496,7 @@ void Connection::doRPCWithDefaultErrorHandling(const QString cmd, const QString
void Connection::doRPCIgnoreError(const QString cmd, const QString args, const std::function<void(json)>& cb)
{
qDebug() << __func__ << ": " << cmd;
DEBUG("cmd=" << cmd << " args=" << args);
doRPC(cmd, args, cb, [=] (auto) {
// Ignored error handling
});
@ -445,5 +528,6 @@ void Connection::showTxError(const QString& error)
*/
void Connection::shutdown()
{
DEBUG("shutting down");
shutdownInProgress = true;
}

1
src/connection.h

@ -6,6 +6,7 @@
#include "mainwindow.h"
#include "ui_connection.h"
#include "precompiled.h"
#include <QRunnable>
using json = nlohmann::json;

70
src/controller.cpp

@ -7,7 +7,6 @@
#include "settings.h"
#include "version.h"
#include "camount.h"
#include "websockets.h"
#include "Model/ChatItem.h"
#include "DataStore/DataStore.h"
@ -262,10 +261,12 @@ void Controller::noConnection()
// Clear balances
ui->balSheilded->setText("");
ui->balUnconfirmed->setText("");
ui->balTransparent->setText("");
ui->balTotal->setText("");
ui->balSheilded->setToolTip("");
ui->balUnconfirmed->setToolTip("");
ui->balTransparent->setToolTip("");
ui->balTotal->setToolTip("");
}
@ -298,12 +299,19 @@ void Controller::processInfo(const json& info)
main->disableRecurring();
}
void Controller::getInfoThenRefresh(bool force)
void Controller::getInfoThenRefresh(bool force)
{
qDebug()<< __func__;
if (!zrpc->haveConnection())
if (!zrpc->haveConnection())
return noConnection();
// Update current server in Info Tab
auto current_server = Settings::getInstance()->getSettings().server;
ui->current_server->setText(current_server);
// no need to update sticky server because currently there is no
// way to change that at run-time via GUI
static bool prevCallSucceeded = false;
zrpc->fetchInfo([=] (const json& reply) {
@ -732,6 +740,7 @@ void Controller::updateUIBalances()
{
CAmount balT = getModel()->getBalT();
CAmount balZ = getModel()->getBalZ();
CAmount balU = getModel()->getBalU();
CAmount balVerified = getModel()->getBalVerified();
CAmount balSpendable = getModel()->getBalSpendable();
@ -749,6 +758,7 @@ void Controller::updateUIBalances()
// Balances table
ui->balSheilded->setText(balZ.toDecimalhushString());
ui->balVerified->setText(balVerified.toDecimalhushString());
ui->balUnconfirmed->setText(balU.toDecimalhushString());
ui->balTransparent->setText(balT.toDecimalhushString());
ui->balSpendable->setText(balSpendable.toDecimalhushString());
ui->balTotal->setText(balTotal.toDecimalhushString());
@ -888,17 +898,16 @@ void Controller::refreshBalances()
zrpc->fetchBalance([=] (json reply) {
CAmount balT = CAmount::fromqint64(reply["tbalance"].get<json::number_unsigned_t>());
CAmount balZ = CAmount::fromqint64(reply["zbalance"].get<json::number_unsigned_t>());
CAmount balU = CAmount::fromqint64(reply["unconfirmed"].get<json::number_unsigned_t>());
CAmount balVerified = CAmount::fromqint64(reply["verified_zbalance"].get<json::number_unsigned_t>());
CAmount balSpendable = CAmount::fromqint64(reply["spendable_zbalance"].get<json::number_unsigned_t>());
model->setBalT(balT);
model->setBalZ(balZ);
model->setBalU(balU);
model->setBalVerified(balVerified);
model->setBalSpendable(balSpendable);
// This is for the websockets
AppDataModel::getInstance()->setBalances(balT, balZ);
// This is for the datamodel
CAmount balAvailable = balT + balVerified;
model->setAvailableBalance(balAvailable);
@ -933,6 +942,26 @@ void Controller::refreshBalances()
});
}
void printJsonValue(QTextStream& out, const nlohmann::json& j, int depth = 0) {
if (j.is_array()) {
for (auto& elem : j) {
printJsonValue(out, elem, depth + 1);
}
} else if (j.is_object()) {
for (auto it = j.begin(); it != j.end(); ++it) {
std::string key = it.key();
const nlohmann::json& value = it.value();
out << QString::fromStdString(std::string(depth * 4, ' '))
<< QString::fromStdString(key) << ": ";
printJsonValue(out, value, depth + 1);
}
} else {
out << QString::fromStdString(j.dump(4)) << "\n";
}
}
void Controller::refreshTransactions() {
qDebug()<< __func__;
if (!zrpc->haveConnection())
@ -1044,7 +1073,6 @@ void Controller::refreshTransactions() {
#define MESSAGEAS1 ((const unsigned char *) hashEncryptionKeyraw) ///////////
#define MESSAGEAS1_LEN length
unsigned char sk[crypto_kx_SECRETKEYBYTES];
unsigned char pk[crypto_kx_PUBLICKEYBYTES];
@ -1058,9 +1086,7 @@ void Controller::refreshTransactions() {
unsigned char server_rx[crypto_kx_SESSIONKEYBYTES], server_tx[crypto_kx_SESSIONKEYBYTES];
////////////////Get the pubkey from Bob, so we can create the share key
/////Create the shared key for sending the message
if (crypto_kx_server_session_keys(server_rx, server_tx, pk, sk, pubkeyBob) != 0)
{
main->logger->write("Suspicious client public outgoing key, bail out ");
@ -1082,15 +1108,11 @@ void Controller::refreshTransactions() {
{
//////unsigned char* as message from QString
#define MESSAGE2 (const unsigned char *) encryptedMemo
///////// length of the encrypted message
#define CIPHERTEXT1_LEN encryptedMemoSize1
///////Message length is smaller then the encrypted message
#define MESSAGE1_LEN encryptedMemoSize1 - crypto_secretstream_xchacha20poly1305_ABYTES
//////Set the length of the decrypted message
unsigned char decrypted[MESSAGE1_LEN];
unsigned char tag[crypto_secretstream_xchacha20poly1305_TAG_FINAL];
crypto_secretstream_xchacha20poly1305_state state;
@ -1118,7 +1140,6 @@ void Controller::refreshTransactions() {
/////Now we can convert it to QString
//////////////Give us the output of the decrypted message as debug to see if it was successfully
ChatItem item = ChatItem(
datetime,
address,
@ -1161,13 +1182,13 @@ void Controller::refreshTransactions() {
} else {
{ // Incoming Transaction
address = (it["address"].is_null() ? "" : QString::fromStdString(it["address"]));
model->markAddressUsed(address);
QString memo;
address = (it["address"].is_null() ? "" : QString::fromStdString(it["address"]));
QString memo;
if (!it["memo"].is_null()) {
memo = QString::fromStdString(it["memo"]);
}
items.push_back(TransactionItemDetail{ address,
CAmount::fromqint64(it["amount"].get<json::number_integer_t>()),
memo
@ -1187,7 +1208,7 @@ void Controller::refreshTransactions() {
QString contactname = "";
bool isContact = false;
if (!it["memo"].is_null()) {
if (!memo.isNull()) {
if (memo.startsWith("{")) {
try {
@ -1257,7 +1278,8 @@ void Controller::refreshTransactions() {
if (position == 1)
{
chatModel->addMemo(txid, headerbytes);
} else {
}
else {
//
}
@ -1273,7 +1295,6 @@ void Controller::refreshTransactions() {
#define MESSAGEAS1 ((const unsigned char *) hashEncryptionKeyraw)///////////
#define MESSAGEAS1_LEN length
unsigned char sk[crypto_kx_SECRETKEYBYTES];
unsigned char pk[crypto_kx_PUBLICKEYBYTES];
@ -1287,7 +1308,6 @@ void Controller::refreshTransactions() {
unsigned char client_rx[crypto_kx_SESSIONKEYBYTES], client_tx[crypto_kx_SESSIONKEYBYTES];
////////////////Get the pubkey from Bob, so we can create the share key
/////Create the shared key for sending the message
if (crypto_kx_client_session_keys(client_rx, client_tx, pk, sk, pubkeyBob) != 0)
{
@ -1304,18 +1324,13 @@ void Controller::refreshTransactions() {
int encryptedMemoSize1 = ba.length();
//int headersize = ba1.length();
//////unsigned char* as message from QString
#define MESSAGE2 (const unsigned char *) encryptedMemo
///////// length of the encrypted message
#define CIPHERTEXT1_LEN encryptedMemoSize1
///////Message length is smaller then the encrypted message
#define MESSAGE1_LEN encryptedMemoSize1 - crypto_secretstream_xchacha20poly1305_ABYTES
//////Set the length of the decrypted message
unsigned char decrypted[MESSAGE1_LEN+1];
unsigned char tag[crypto_secretstream_xchacha20poly1305_TAG_FINAL];
crypto_secretstream_xchacha20poly1305_state state;
@ -1343,7 +1358,6 @@ void Controller::refreshTransactions() {
memodecrypt = QString::fromUtf8( decryptedMemo.data(), decryptedMemo.size());
////Give us the output of the decrypted message as debug to see if it was successfully
ChatItem item = ChatItem(
datetime,
address,
@ -1367,8 +1381,6 @@ void Controller::refreshTransactions() {
qDebug() << __func__ << ": ignoring txid="<< txid;
}
//} else if (memo.startsWith("{")) {
//qDebug() << __func__ << ": ignoring a header memo";
} else {
// Add a chatitem for the initial CR
ChatItem item = ChatItem(

6
src/datamodel.h

@ -5,7 +5,7 @@
#include "camount.h"
#include "precompiled.h"
#include <QReadLocker>
struct UnspentOutput {
QString address;
@ -48,6 +48,9 @@ public:
CAmount getBalZ() { QReadLocker locker(lock); return balZ; }
void setBalZ(CAmount a) { QReadLocker locker(lock); this->balZ = a; }
CAmount getBalU() { QReadLocker locker(lock); return balU; }
void setBalU(CAmount a) { QReadLocker locker(lock); this->balU = a; }
CAmount getBalVerified() { QReadLocker locker(lock); return balVerified; }
void setBalVerified(CAmount a) { QReadLocker locker(lock); this->balVerified = a; }
@ -76,6 +79,7 @@ private:
CAmount balT;
CAmount balZ;
CAmount balU;
CAmount balVerified;
CAmount balSpendable;

149
src/firsttimewizard.cpp

@ -293,10 +293,28 @@ void NewSeedPage::initializePage() {
// Call the library to create a new wallet.
qDebug() << __func__;
char* resp = litelib_initialize_new(parent->dangerous,parent->server.toStdString().c_str());
QString reply = litelib_process_response(resp);
QString reply = "";
try {
char* resp = litelib_initialize_new(parent->dangerous,parent->server.toStdString().c_str());
reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
}
qDebug() << __func__ << ": reply=" << reply;
if (reply.isEmpty()) {
qDebug() << "Lite server " << parent->server << " is down, getting a random one";
parent->server = Settings::getRandomServer();
qDebug() << __func__ << ": new server is " << parent->server;
// retry with the new server
// we use litelib_initialize_existing because the call to litelib_initialize_new above
// has already created a wallet on disk
char* resp = litelib_initialize_existing(parent->dangerous,parent->server.toStdString().c_str());
reply = litelib_process_response(resp);
}
auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false);
if (parsed.is_discarded() || parsed.is_null() || parsed.find("seed") == parsed.end()) {
form.txtSeed->setPlainText(tr("Error creating a wallet") + "\n" + reply);
@ -607,21 +625,49 @@ bool NewSeedPage::validatePage() {
dialog.exec();
if ((verifyseed.verify->toPlainText() == seed) && (verifyseed.verifyBirthday->toPlainText() == birthday))
{
char* resp = litelib_execute("save", "");
QString reply = litelib_process_response(resp);
QString reply = "";
if ((verifyseed.verify->toPlainText() == seed) && (verifyseed.verifyBirthday->toPlainText() == birthday)) {
try {
char* resp = litelib_execute("save", "");
reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
}
auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false);
if (parsed.is_discarded() || parsed.is_null() || parsed.find("result") == parsed.end()) {
qDebug() << __func__ << ": reply=" << reply;
// TODO: this is duplicated code that should be refactored
// into a dedicated function
if (reply.isEmpty()) {
qDebug() << "Lite server " << parent->server << " is down, getting a random one";
parent->server = Settings::getRandomServer();
qDebug() << __func__ << ": new server is " << parent->server;
// make a new connection to the new server
char* resp = litelib_initialize_new(parent->dangerous,parent->server.toStdString().c_str());
reply = litelib_process_response(resp);
// retry with the new server
try {
resp = litelib_execute("save", "");
reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception with new server, something is fucky: " << e.what();
}
}
QMessageBox::warning(this, tr("Failed to save wallet"),
tr("Couldn't save the wallet") + "\n" + reply,
QMessageBox::Ok);
return false;
} else {
return true;
}
qDebug() << __func__ << ": reply=" << reply;
auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false);
if (parsed.is_discarded() || parsed.is_null() || parsed.find("result") == parsed.end()) {
QMessageBox::warning(this, tr("Failed to save wallet"),
tr("Couldn't save the wallet") + "\n" + "server=" + parent->server + "\n" + reply,
QMessageBox::Ok);
return false;
} else {
return true;
}
}else{
qDebug()<<"Wrong Seed";
QFile file(dirwalletencfirst);
@ -629,9 +675,7 @@ bool NewSeedPage::validatePage() {
file.remove();
file1.remove();
QMessageBox::warning(this, tr("Wrong Seed"),
tr("Please try again") + "\n" ,
QMessageBox::Ok);
QMessageBox::warning(this, tr("Wrong Seed"), tr("Please try again") + "\n" , QMessageBox::Ok);
form.birthday->setVisible(true);
form.txtSeed->setVisible(true);
return false;
@ -686,32 +730,79 @@ bool RestoreSeedPage::validatePage() {
qint64 number = number_str.toUInt();
// 3. Attempt to restore wallet with the seed phrase
{
char* resp = litelib_initialize_new_from_phrase(parent->dangerous, parent->server.toStdString().c_str(),
seed.toStdString().c_str(), birthday, number);
QString reply = litelib_process_response(resp);
QString reply = "";
try {
char* resp = litelib_initialize_new_from_phrase(parent->dangerous, parent->server.toStdString().c_str(),
seed.toStdString().c_str(), birthday, number);
reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
}
qDebug() << __func__ << ": reply=" << reply;
if (reply.toUpper().trimmed() != "OK") {
QMessageBox::warning(this, tr("Failed to restore wallet"),
tr("Couldn't restore the wallet") + "\n" + reply,
qDebug() << "Lite server " << parent->server << " is down, getting a random one";
parent->server = Settings::getRandomServer();
qDebug() << __func__ << ": new server is " << parent->server;
// retry with the new server
char* resp = litelib_initialize_new_from_phrase(parent->dangerous, parent->server.toStdString().c_str(),
seed.toStdString().c_str(), birthday, number);
reply = litelib_process_response(resp);
}
if (reply.toUpper().trimmed() != "OK") {
QMessageBox::warning(this, tr("Failed to restore wallet"),
tr("Couldn't restore the wallet") + "\n" + "server=" + parent->server + "\n" + reply,
QMessageBox::Ok);
return false;
}
}
}
// 4. Finally attempt to save the wallet
{
char* resp = litelib_execute("save", "");
QString reply = litelib_process_response(resp);
QString reply = "";
try {
char* resp = litelib_execute("save", "");
reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
}
// TODO: this is duplicated code that should be refactored
// into a dedicated function
if (reply.isEmpty()) {
qDebug() << "Lite server " << parent->server << " is down, getting a random one";
parent->server = Settings::getRandomServer();
qDebug() << __func__ << ": new server is " << parent->server;
// make a new connection to the new server
char* resp = litelib_initialize_new_from_phrase(parent->dangerous, parent->server.toStdString().c_str(),
seed.toStdString().c_str(), birthday, number);
reply = litelib_process_response(resp);
// retry with the new server
try {
resp = litelib_execute("save", "");
reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception with new server, something is fucky: " << e.what();
}
}
qDebug() << __func__ << ": reply=" << reply;
auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false);
if (parsed.is_discarded() || parsed.is_null() || parsed.find("result") == parsed.end()) {
qDebug() << __func__ << ": Failed to save wallet, reply=" << reply;
QMessageBox::warning(this, tr("Failed to save wallet"),
tr("Couldn't save the wallet") + "\n" + reply,
QMessageBox::warning(this, tr("Failed to save wallet"),
tr("Couldn't save the wallet") + "\n" + "server=" + parent->server + "\n" + reply,
QMessageBox::Ok);
return false;
} else {
return true;
}
}
}
}

1
src/main.cpp

@ -6,6 +6,7 @@
#include "mainwindow.h"
#include "controller.h"
#include "settings.h"
#include <QCommandLineParser>
#include "version.h"

130
src/mainwindow.cpp

@ -25,7 +25,6 @@
#include "ui_startupencryption.h"
#include "ui_removeencryption.h"
#include "ui_seedrestore.h"
#include "websockets.h"
#include "sodium.h"
#include "sodium/crypto_generichash_blake2b.h"
#include <QRegularExpression>
@ -38,6 +37,7 @@
#include <QCoreApplication>
#include <QGuiApplication>
#include <QKeyEvent>
#include "sdl.h"
using json = nlohmann::json;
@ -159,11 +159,11 @@ MainWindow::MainWindow(QWidget *parent) :
// Rescan
QObject::connect(ui->actionRescan, &QAction::triggered, [=]() {
Ui_Restore restoreSeed;
QDialog dialog(this);
restoreSeed.setupUi(&dialog);
Settings::saveRestore(&dialog);
DEBUG("rescan action triggered");
Ui_Restore restoreSeed;
QDialog dialog(this);
restoreSeed.setupUi(&dialog);
Settings::saveRestore(&dialog);
rpc->fetchSeed([=](json reply) {
if (isJsonError(reply)) {
@ -209,35 +209,74 @@ MainWindow::MainWindow(QWidget *parent) :
config->server = Settings::getInstance()->getSettings().server;
// 3. Attempt to restore wallet with the seed phrase
{
char* resp = litelib_initialize_new_from_phrase(config->dangerous, config->server.toStdString().c_str(),
seed.toStdString().c_str(), birthday, number);
QString reply = litelib_process_response(resp);
QString reply = "";
try {
char* resp = litelib_initialize_new_from_phrase(config->dangerous, config->server.toStdString().c_str(),
seed.toStdString().c_str(), birthday, number);
reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
}
if (reply.toUpper().trimmed() != "OK") {
qDebug() << "Lite server " << config->server << " is down, getting a random one";
config->server = Settings::getRandomServer();
qDebug() << __func__ << ": new server is " << config->server;
// retry with the new server
char* resp = litelib_initialize_new_from_phrase(config->dangerous,config->server.toStdString().c_str(),
seed.toStdString().c_str(), birthday, number);
reply = litelib_process_response(resp);
}
if (reply.toUpper().trimmed() != "OK") {
QMessageBox::warning(this, tr("Failed to restore wallet"),
tr("Couldn't restore the wallet") + "\n" + reply,
QMessageBox::warning(this, tr("Failed to restore wallet"),
tr("Couldn't restore the wallet") + "\n" + "server=" + config->server + "\n" + reply,
QMessageBox::Ok);
}
return false;
}
}
// 4. Finally attempt to save the wallet
{
char* resp = litelib_execute("save", "");
QString reply = litelib_process_response(resp);
QString reply = "";
try {
char* resp = litelib_execute("save", "");
QString reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
}
if (reply.isEmpty()) {
qDebug() << "Lite server " << config->server << " is down, getting a random one";
config->server = Settings::getRandomServer();
qDebug() << __func__ << ": new server is " << config->server;
// make a new connection to the new server
char* resp = litelib_initialize_new_from_phrase(config->dangerous,config->server.toStdString().c_str(),
seed.toStdString().c_str(), birthday, number);
reply = litelib_process_response(resp);
// retry with the new server
try {
resp = litelib_execute("save", "");
reply = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception with new server, something is fucky: " << e.what();
}
}
QByteArray ba_reply = reply.toUtf8();
QJsonDocument jd_reply = QJsonDocument::fromJson(ba_reply);
QJsonObject parsed = jd_reply.object();
if (parsed.isEmpty() || parsed["result"].isNull()) {
QMessageBox::warning(this, tr("Failed to save wallet"),
tr("Couldn't save the wallet") + "\n" + reply,
QMessageBox::warning(this, tr("Failed to save wallet"),
tr("Couldn't save the wallet") + "\n" + "server=" + config->server + "\n" + reply,
QMessageBox::Ok);
} else {
qDebug() << __func__ << ": saved wallet correctly";
}
}
dialog.close();
// To rescan, we clear the wallet state, and then reload the connection
@ -250,15 +289,14 @@ MainWindow::MainWindow(QWidget *parent) :
// Then reload the connection. The ConnectionLoader deletes itself.
auto cl = new ConnectionLoader(this, rpc);
cl->loadConnection();
});
});
});
}
});
dialog.exec();
});
}); // actionReason
// Import Privkey
QObject::connect(ui->actionImport_Privatkey, &QAction::triggered, this, &MainWindow::importPrivKey);
@ -294,16 +332,6 @@ MainWindow::MainWindow(QWidget *parent) :
restoreSavedStates();
if (AppDataServer::getInstance()->isAppConnected()) {
qDebug() << __func__ << ": app is connected to wormhole";
auto ads = AppDataServer::getInstance();
QString wormholecode = "";
if (ads->getAllowInternetConnection())
wormholecode = ads->getWormholeCode(ads->getSecretHex());
createWebsocket(wormholecode);
}
}
bool MainWindow::fileExists(QString path)
@ -312,36 +340,6 @@ bool MainWindow::fileExists(QString path)
return (check_file.exists() && check_file.isFile());
}
void MainWindow::createWebsocket(QString wormholecode) {
qDebug() << "Listening for app connections on port 8777";
// Create the websocket server, for listening to direct connections
wsserver = new WSServer(8777, false, this);
if (!wormholecode.isEmpty()) {
// Connect to the wormhole service
wormhole = new WormholeClient(this, wormholecode);
}
}
void MainWindow::stopWebsocket() {
delete wsserver;
wsserver = nullptr;
delete wormhole;
wormhole = nullptr;
qDebug() << "Websockets for app connections shut down";
}
bool MainWindow::isWebsocketListening() {
return wsserver != nullptr;
}
void MainWindow::replaceWormholeClient(WormholeClient* newClient) {
delete wormhole;
wormhole = newClient;
}
void MainWindow::restoreSavedStates() {
QSettings s;
restoreGeometry(s.value("geometry").toByteArray());
@ -1097,10 +1095,10 @@ void MainWindow::payhushURI(QString uri, QString myAddr) {
// Save the wallet
this->getRPC()->saveWallet([=] (auto) {
// Then reload the connection. The ConnectionLoader deletes itself.
auto cl = new ConnectionLoader(this, rpc);
auto cl = new ConnectionLoader(this, rpc);
cl->loadProgress();
});
});
});
});
}else if ((pui.rescan->isChecked() == true) && (pui.privKeyTxt->toPlainText().startsWith("secret"))){
@ -2724,8 +2722,6 @@ MainWindow::~MainWindow()
delete loadingMovie;
delete logger;
delete wsserver;
delete wormhole;
}
void MainWindow::on_givemeZaddr_clicked()
{

10
src/mainwindow.h

@ -14,8 +14,6 @@ using json = nlohmann::json;
// Forward declare to break circular dependency.
class Controller;
class Settings;
class WSServer;
class WormholeClient;
class ChatModel;
@ -70,10 +68,6 @@ public:
void replaceWormholeClient(WormholeClient* newClient);
bool isWebsocketListening();
void createWebsocket(QString wormholecode);
void stopWebsocket();
void saveContact();
void saveandsendContact();
void showRequesthush();
@ -200,10 +194,6 @@ private:
bool uiPaymentsReady = false;
QString pendingURIPayment;
WSServer* wsserver = nullptr;
WormholeClient* wormhole = nullptr;
Controller* rpc = nullptr;
QCompleter* labelCompleter = nullptr;

41
src/mainwindow.ui

@ -641,6 +641,33 @@
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Unconfirmed</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="balUnconfirmed">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2_1">
<item>
<widget class="QLabel" name="label_2_1">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
@ -664,7 +691,7 @@
</item>
</layout>
</item>
<item row="3" column="0">
<item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_20">
<item>
<widget class="QLabel" name="label_40">
@ -691,14 +718,14 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
@ -735,7 +762,7 @@
</item>
</layout>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="lblSyncWarning">
<property name="styleSheet">
<string notr="true">color:red;</string>
@ -748,7 +775,7 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="8" column="0">
<widget class="QLabel" name="unconfirmedWarning">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -767,14 +794,14 @@
</property>
</widget>
</item>
<item row="8" column="0">
<item row="9" column="0">
<widget class="QPushButton" name="depositHushButton">
<property name="text">
<string>Deposit Hush</string>
</property>
</widget>
</item>
<item row="9" column="0">
<item row="10" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>

16
src/mobileappconnector.cpp

@ -1,16 +0,0 @@
// Copyright 2019-2023 The Hush developers
// Released under the GPLv3
#include "mobileappconnector.h"
#include "ui_mobileappconnector.h"
MobileAppConnector::MobileAppConnector(QWidget *parent) :
QDialog(parent),
ui(new Ui::MobileAppConnector)
{
ui->setupUi(this);
}
MobileAppConnector::~MobileAppConnector()
{
delete ui;
}

24
src/mobileappconnector.h

@ -1,24 +0,0 @@
// Copyright 2019-2023 The Hush developers
// Released under the GPLv3
#ifndef MOBILEAPPCONNECTOR_H
#define MOBILEAPPCONNECTOR_H
#include <QDialog>
namespace Ui {
class MobileAppConnector;
}
class MobileAppConnector : public QDialog
{
Q_OBJECT
public:
explicit MobileAppConnector(QWidget *parent = nullptr);
~MobileAppConnector();
private:
Ui::MobileAppConnector *ui;
};
#endif // MOBILEAPPCONNECTOR_H

214
src/mobileappconnector.ui

@ -1,214 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MobileAppConnector</class>
<widget class="QDialog" name="MobileAppConnector">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>530</height>
</rect>
</property>
<property name="windowTitle">
<string>Mobile Connector App</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="1" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Scan this QRCode from your silentdragon companion app to connect your phone</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>QR Code</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Connection String</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="txtConnStr">
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRCodeLabel" name="qrcode">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">background-color: #fff</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="chkInternetConn">
<property name="text">
<string>Allow connections over the internet via silentdragon wormhole</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="2" rowspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>silentdragon Companion App</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="5" column="0">
<widget class="QPushButton" name="btnDisconnect">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Disconnect</string>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lblLastSeen">
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Last seen:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="lblRemoteName">
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Connection type:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lblConnectionType">
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QRCodeLabel</class>
<extends>QLabel</extends>
<header>qrcodelabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MobileAppConnector</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MobileAppConnector</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

1
src/precompiled.h

@ -64,7 +64,6 @@
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtWebSockets/QtWebSockets>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>

2
src/scripts/docker/Dockerfile

@ -40,7 +40,7 @@ RUN cd /opt && rm qt-everywhere-src-5.11.2.tar.xz && rm -rf qt-everywhere-src-5.
RUN cd /opt && \
git clone https://github.com/mxe/mxe.git && \
cd /opt/mxe && \
make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase qtwebsockets
make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase
# Add rust
RUN apt install -y gcc-aarch64-linux-gnu

1
src/sdl.h

@ -0,0 +1 @@
#define DEBUG(x) (qDebug() << QString(__func__) << ": " << x)

14
src/settings.cpp

@ -4,6 +4,7 @@
#include "settings.h"
#include "camount.h"
#include "../lib/silentdragonlitelib.h"
#include <QUrlQuery>
Settings* Settings::instance = nullptr;
@ -36,9 +37,14 @@ Config Settings::getSettings() {
if (server.trimmed().isEmpty()) {
server = Settings::getRandomServer();
QString response = "";
// make sure existing server in conf is alive, otherwise choose random one
char* resp = litelib_initialize_existing(false, server.toStdString().c_str());
QString response = litelib_process_response(resp);
try {
char* resp = litelib_initialize_existing(false, server.toStdString().c_str());
response = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
}
if (response.toUpper().trimmed() != "OK") {
qDebug() << "Lite server in conf " << server << " is down, getting a random one";
@ -310,14 +316,14 @@ QString Settings::getRandomServer() {
// We try every server,in order, starting from a random place in the list
while (tries < servers.size() ) {
qDebug() << "Checking if lite server " << server << " is a alive, try=" << tries;
char* resp = litelib_initialize_existing(false, server.toStdString().c_str());
QString response = "";
try {
char* resp = litelib_initialize_existing(false, server.toStdString().c_str());
response = litelib_process_response(resp);
} catch (const std::exception& e) {
qDebug() << __func__ << ": litelib_process_response threw an exception, ignoring: " << e.what();
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
}
// if we see a valid connection, return this server

2
src/version.h

@ -1,3 +1,3 @@
// Copyright 2019-2023 The Hush developers
// Released under the GPLv3
#define APP_VERSION "1.5.3"
#define APP_VERSION "1.5.4"

940
src/websockets.cpp

@ -1,940 +0,0 @@
// Copyright 2019-2023 The Hush developers
// Released under the GPLv3
#include "websockets.h"
#include "controller.h"
#include "settings.h"
#include "ui_mobileappconnector.h"
#include "version.h"
// Weap the sendTextMessage to check if the connection is valid and that the parent WebServer didn't close this connection
// for some reason.
void ClientWebSocket::sendTextMessage(QString m) {
if (client) {
if (server && !server->isValidConnection(client)) {
return;
}
if (client->isValid())
client->sendTextMessage(m);
}
}
WSServer::WSServer(quint16 port, bool debug, QObject *parent) :
QObject(parent),
m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Direct Connection Server"),
QWebSocketServer::NonSecureMode, this)),
m_debug(debug)
{
m_mainWindow = (MainWindow *) parent;
if (m_pWebSocketServer->listen(QHostAddress::AnyIPv4, port)) {
if (m_debug)
qDebug() << "Echoserver listening on port" << port;
connect(m_pWebSocketServer, &QWebSocketServer::newConnection,
this, &WSServer::onNewConnection);
connect(m_pWebSocketServer, &QWebSocketServer::closed, this, &WSServer::closed);
}
}
WSServer::~WSServer()
{
qDebug() << "Closing websocket server";
m_pWebSocketServer->close();
qDeleteAll(m_clients.begin(), m_clients.end());
qDebug() << "Deleted all websocket clients";
}
void WSServer::onNewConnection()
{
qDebug() << "Websocket server: new connection";
QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection();
connect(pSocket, &QWebSocket::textMessageReceived, this, &WSServer::processTextMessage);
connect(pSocket, &QWebSocket::binaryMessageReceived, this, &WSServer::processBinaryMessage);
connect(pSocket, &QWebSocket::disconnected, this, &WSServer::socketDisconnected);
m_clients << pSocket;
}
void WSServer::processTextMessage(QString message)
{
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
if (m_debug)
qDebug() << "Message received:" << message;
if (pClient) {
std::shared_ptr<ClientWebSocket> client = std::make_shared<ClientWebSocket>(pClient, this);
AppDataServer::getInstance()->processMessage(message, m_mainWindow, client, AppConnectionType::DIRECT);
}
}
void WSServer::processBinaryMessage(QByteArray message)
{
//QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
if (m_debug)
qDebug() << "Binary Message received:" << message;
}
void WSServer::socketDisconnected()
{
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
if (m_debug)
qDebug() << "socketDisconnected:" << pClient;
if (pClient) {
m_clients.removeAll(pClient);
pClient->deleteLater();
}
}
//===============================
// WormholeClient
//===============================
WormholeClient::WormholeClient(MainWindow* p, QString wormholeCode) {
this->parent = p;
this->code = wormholeCode;
connect();
qDebug() << "New wormhole client after connect()";
}
WormholeClient::~WormholeClient() {
qDebug() << "WormholeClient destructor";
shuttingDown = true;
if (m_webSocket && m_webSocket->isValid()) {
qDebug() << "Wormhole closing!";
m_webSocket->close();
}
if (timer) {
qDebug() << "Wormhole timer stopping";
timer->stop();
}
delete timer;
qDebug() << "Wormhole timer deleted";
}
void WormholeClient::connect() {
qDebug() << "Wormhole::connect";
delete m_webSocket;
m_webSocket = new QWebSocket();
if (m_webSocket) {
QObject::connect(m_webSocket, &QWebSocket::connected, this, &WormholeClient::onConnected);
QObject::connect(m_webSocket, &QWebSocket::disconnected, this, &WormholeClient::closed);
} else {
qDebug() << "Invalid websocket object!";
}
m_webSocket->open(QUrl("wss://wormhole.hush.is:443"));
//m_webSocket->open(QUrl("ws://127.0.0.1:7070"));
}
void WormholeClient::retryConnect() {
QTimer::singleShot(5 * 1000 * pow(2, retryCount), [=]() {
if (retryCount < 10) {
qDebug() << "Retrying websocket connection";
this->retryCount++;
connect();
} else {
qDebug() << "Retry count exceeded, will not attempt retry any more";
}
});
}
// Called when the websocket is closed. If this was closed without our explicitly closing it,
// then we need to try and reconnect
void WormholeClient::closed() {
if (!shuttingDown) {
retryConnect();
}
}
void WormholeClient::onConnected()
{
qDebug() << "WebSocket connected";
retryCount = 0;
qDebug() << "WebSocket connected, retryCount=" << retryCount;
QObject::connect(m_webSocket, &QWebSocket::textMessageReceived,
this, &WormholeClient::onTextMessageReceived);
auto payload = QJsonDocument( QJsonObject {
{"register", code}
}).toJson();
m_webSocket->sendTextMessage(payload);
// On connected, we'll also create a timer to ping it every 4 minutes, since the websocket
// will timeout after 5 minutes
if (m_webSocket && m_webSocket->isValid()) {
m_webSocket->sendTextMessage(payload);
qDebug() << "Sent registration message with code=" << code;
// On connected, we'll also create a timer to ping it every 4 minutes, since the websocket
// will timeout after 5 minutes
timer = new QTimer(parent);
qDebug() << "Created QTimer";
QObject::connect(timer, &QTimer::timeout, [=]() {
qDebug() << "Timer timeout!";
if (!shuttingDown && m_webSocket && m_webSocket->isValid()) {
auto payload = QJsonDocument(QJsonObject { {"ping", "ping"} }).toJson();
qint64 bytes = m_webSocket->sendTextMessage(payload);
qDebug() << "Sent ping, " << bytes << " bytes";
}
});
unsigned int interval = 4*60*1000;
timer->start(interval); // 4 minutes
qDebug() << "Started timer with interval=" << interval;
} else {
qDebug() << "Invalid websocket object onConnected!";
}
}
void WormholeClient::onTextMessageReceived(QString message)
{
AppDataServer::getInstance()->processMessage(message, parent, std::make_shared<ClientWebSocket>(m_webSocket), AppConnectionType::INTERNET);
qDebug() << "Destroyed tempWormholeClient and ui";
}
// ==============================
// AppDataServer
// ==============================
AppDataServer* AppDataServer::instance = nullptr;
QString AppDataServer::getWormholeCode(QString secretHex) {
unsigned char* secret = new unsigned char[crypto_secretbox_KEYBYTES];
sodium_hex2bin(secret, crypto_secretbox_KEYBYTES, secretHex.toStdString().c_str(), crypto_secretbox_KEYBYTES*2,
NULL, NULL, NULL);
unsigned char* out1 = new unsigned char[crypto_hash_sha256_BYTES];
crypto_hash_sha256(out1, secret, crypto_secretbox_KEYBYTES);
unsigned char* out2 = new unsigned char[crypto_hash_sha256_BYTES];
crypto_hash_sha256(out2, out1, crypto_hash_sha256_BYTES);
char* wmcode = new char[crypto_hash_sha256_BYTES*2 + 1];
sodium_bin2hex(wmcode, crypto_hash_sha256_BYTES*2 + 1, out2, crypto_hash_sha256_BYTES);
QString wmcodehex(wmcode);
delete[] wmcode;
delete[] out2;
delete[] out1;
delete[] secret;
return wmcodehex;
}
QString AppDataServer::getSecretHex() {
QSettings s;
return s.value("mobileapp/secret", "").toString();
}
void AppDataServer::saveNewSecret(QString secretHex) {
QSettings().setValue("mobileapp/secret", secretHex);
if (secretHex.isEmpty())
setAllowInternetConnection(false);
}
bool AppDataServer::getAllowInternetConnection() {
return QSettings().value("mobileapp/allowinternet", false).toBool();
}
void AppDataServer::setAllowInternetConnection(bool allow) {
QSettings().setValue("mobileapp/allowinternet", allow);
}
void AppDataServer::saveLastConnectedOver(AppConnectionType type) {
QSettings().setValue("mobileapp/lastconnectedover", type);
}
AppConnectionType AppDataServer::getLastConnectionType() {
return (AppConnectionType) QSettings().value("mobileapp/lastconnectedover", AppConnectionType::DIRECT).toInt();
}
void AppDataServer::saveLastSeenTime() {
QSettings().setValue("mobileapp/lastseentime", QDateTime::currentSecsSinceEpoch());
}
QDateTime AppDataServer::getLastSeenTime() {
return QDateTime::fromSecsSinceEpoch(QSettings().value("mobileapp/lastseentime", 0).toLongLong());
}
void AppDataServer::setConnectedName(QString name) {
QSettings().setValue("mobileapp/connectedname", name);
}
QString AppDataServer::getConnectedName() {
return QSettings().value("mobileapp/connectedname", "").toString();
}
bool AppDataServer::isAppConnected() {
return !getConnectedName().isEmpty() &&
getLastSeenTime().daysTo(QDateTime::currentDateTime()) < 14;
}
void AppDataServer::connectAppDialog(MainWindow* parent) {
QDialog d(parent);
ui = new Ui_MobileAppConnector();
ui->setupUi(&d);
Settings::saveRestore(&d);
updateUIWithNewQRCode(parent);
updateConnectedUI();
QObject::connect(ui->btnDisconnect, &QPushButton::clicked, [=] () {
QSettings().setValue("mobileapp/connectedname", "");
saveNewSecret("");
updateConnectedUI();
});
QObject::connect(ui->txtConnStr, &QLineEdit::cursorPositionChanged, [=](int, int) {
ui->txtConnStr->selectAll();
});
QObject::connect(ui->chkInternetConn, &QCheckBox::stateChanged, [=] (int state) {
if (state == Qt::Checked) {
}
updateUIWithNewQRCode(parent);
});
// If we're not listening for the app, then start the websockets
if (!parent->isWebsocketListening()) {
QString wormholecode = "";
if (getAllowInternetConnection())
wormholecode = AppDataServer::getInstance()->getWormholeCode(AppDataServer::getInstance()->getSecretHex());
parent->createWebsocket(wormholecode);
}
d.exec();
// If there is nothing connected when the dialog exits, then shutdown the websockets
if (!isAppConnected()) {
parent->stopWebsocket();
}
// Cleanup
tempSecret = "";
delete tempWormholeClient;
tempWormholeClient = nullptr;
delete ui;
ui = nullptr;
}
void AppDataServer::updateUIWithNewQRCode(MainWindow* mainwindow) {
// Get the address of the localhost
auto addrList = QNetworkInterface::allAddresses();
// Find a suitable address
QString ipv4Addr;
for (auto addr : addrList) {
if (addr.isLoopback() || addr.protocol() == QAbstractSocket::IPv6Protocol)
continue;
ipv4Addr = addr.toString();
break;
}
if (ipv4Addr.isEmpty())
return;
QString uri = "ws://" + ipv4Addr + ":8777";
qDebug() << "Websocket URI: " << uri;
// Get a new secret
unsigned char* secretBin = new unsigned char[crypto_secretbox_KEYBYTES];
randombytes_buf(secretBin, crypto_secretbox_KEYBYTES);
char* secretHex = new char[crypto_secretbox_KEYBYTES*2 + 1];
sodium_bin2hex(secretHex, crypto_secretbox_KEYBYTES*2+1, secretBin, crypto_secretbox_KEYBYTES);
QString secretStr(secretHex);
QString codeStr = uri + "," + secretStr;
if (ui->chkInternetConn->isChecked()) {
codeStr = codeStr + ",1";
}
registerNewTempSecret(secretStr, ui->chkInternetConn->isChecked(), mainwindow);
ui->qrcode->setQrcodeString(codeStr);
ui->txtConnStr->setText(codeStr);
qDebug() << "New QR="<<codeStr;
}
void AppDataServer::registerNewTempSecret(QString tmpSecretHex, bool allowInternet, MainWindow* main) {
qDebug() << "Registering new tempSecret, allowInternet=" << allowInternet;
tempSecret = tmpSecretHex;
delete tempWormholeClient;
tempWormholeClient = nullptr;
if (allowInternet)
tempWormholeClient = new WormholeClient(main, getWormholeCode(tempSecret));
qDebug() << "Created new wormhole client";
}
QString AppDataServer::connDesc(AppConnectionType t) {
if (t == AppConnectionType::DIRECT) {
return QObject::tr("Connected directly");
}
else {
return QObject::tr("Connected over the internet via silentdragon wormhole service");
}
}
void AppDataServer::updateConnectedUI() {
if (ui == nullptr)
return;
auto remoteName = getConnectedName();
ui->lblRemoteName->setText(remoteName.isEmpty() ? "(Not connected to any device)" : remoteName);
ui->lblLastSeen->setText(remoteName.isEmpty() ? "" : getLastSeenTime().toString(Qt::SystemLocaleLongDate));
ui->lblConnectionType->setText(remoteName.isEmpty() ? "" : connDesc(getLastConnectionType()));
ui->btnDisconnect->setEnabled(!remoteName.isEmpty());
}
QString AppDataServer::getNonceHex(NonceType nt) {
QSettings s;
QString hex;
if (nt == NonceType::LOCAL) {
// The default local nonce starts from 1, to always keep it odd
auto defaultLocalNonce = "01" + QString("00").repeated(crypto_secretbox_NONCEBYTES-1);
hex = s.value("mobileapp/localnoncehex", defaultLocalNonce).toString();
}
else {
hex = s.value("mobileapp/remotenoncehex", QString("00").repeated(crypto_secretbox_NONCEBYTES)).toString();
}
return hex;
}
void AppDataServer::saveNonceHex(NonceType nt, QString noncehex) {
QSettings s;
assert(noncehex.length() == crypto_secretbox_NONCEBYTES * 2);
if (nt == NonceType::LOCAL) {
s.setValue("mobileapp/localnoncehex", noncehex);
}
else {
s.setValue("mobileapp/remotenoncehex", noncehex);
}
s.sync();
}
// Encrypt an outgoing message with the stored secret key.
QString AppDataServer::encryptOutgoing(QString msg) {
int padding = 16*1024;
qDebug() << "Encrypt msg(pad="<<padding<<") prepad len=" << msg.length();
if (msg.length() % padding > 0) {
msg = msg + QString(" ").repeated(padding - (msg.length() % padding));
}
qDebug() << "Encrypt msg postpad len=" << msg.length();
QString localNonceHex = getNonceHex(NonceType::LOCAL);
unsigned char* noncebin = new unsigned char[crypto_secretbox_NONCEBYTES];
sodium_hex2bin(noncebin, crypto_secretbox_NONCEBYTES, localNonceHex.toStdString().c_str(), localNonceHex.length(),
NULL, NULL, NULL);
// Increment the nonce +2 and save
sodium_increment(noncebin, crypto_secretbox_NONCEBYTES);
sodium_increment(noncebin, crypto_secretbox_NONCEBYTES);
char* newLocalNonce = new char[crypto_secretbox_NONCEBYTES*2 + 1];
sodium_memzero(newLocalNonce, crypto_secretbox_NONCEBYTES*2 + 1);
sodium_bin2hex(newLocalNonce, crypto_secretbox_NONCEBYTES*2+1, noncebin, crypto_box_NONCEBYTES);
saveNonceHex(NonceType::LOCAL, QString(newLocalNonce));
unsigned char* secret = new unsigned char[crypto_secretbox_KEYBYTES];
sodium_hex2bin(secret, crypto_secretbox_KEYBYTES, getSecretHex().toStdString().c_str(), crypto_secretbox_KEYBYTES*2,
NULL, NULL, NULL);
int msgSize = strlen(msg.toStdString().c_str());
unsigned char* encrpyted = new unsigned char[ msgSize + crypto_secretbox_MACBYTES];
crypto_secretbox_easy(encrpyted, (const unsigned char *)msg.toStdString().c_str(), msgSize, noncebin, secret);
int encryptedHexSize = (msgSize + crypto_secretbox_MACBYTES) * 2 + 1;
char * encryptedHex = new char[encryptedHexSize];
sodium_memzero(encryptedHex, encryptedHexSize);
sodium_bin2hex(encryptedHex, encryptedHexSize, encrpyted, msgSize + crypto_secretbox_MACBYTES);
auto json = QJsonDocument(QJsonObject{
{"nonce", QString(newLocalNonce)},
{"payload", QString(encryptedHex)},
{"to", getWormholeCode(getSecretHex())}
});
delete[] noncebin;
delete[] newLocalNonce;
delete[] secret;
delete[] encrpyted;
delete[] encryptedHex;
return json.toJson();
}
/**
Attempt to decrypt a message. If the decryption fails, it returns the string "error", the decrypted message otherwise.
It will use the given secret to attempt decryption. In addition, it will enforce that the nonce is greater than the last seen nonce,
unless the skipNonceCheck = true, which is used when attempting decrtption with a temp secret key.
*/
QString AppDataServer::decryptMessage(QJsonDocument msg, QString secretHex, QString lastRemoteNonceHex) {
// Decrypt and then process
QString noncehex = msg.object().value("nonce").toString();
QString encryptedhex = msg.object().value("payload").toString();
// Enforce limits on the size of the message
if (noncehex.length() > ((int)crypto_secretbox_NONCEBYTES * 2) ||
encryptedhex.length() > 2 * 50 * 1024 /*50kb*/) {
return "error";
}
// Check to make sure that the nonce is greater than the last known remote nonce
unsigned char* lastRemoteBin = new unsigned char[crypto_secretbox_NONCEBYTES];
sodium_hex2bin(lastRemoteBin, crypto_secretbox_NONCEBYTES, lastRemoteNonceHex.toStdString().c_str(), lastRemoteNonceHex.length(),
NULL, NULL, NULL);
unsigned char* noncebin = new unsigned char[crypto_secretbox_NONCEBYTES];
sodium_hex2bin(noncebin, crypto_secretbox_NONCEBYTES, noncehex.toStdString().c_str(), noncehex.length(),
NULL, NULL, NULL);
assert(crypto_secretbox_KEYBYTES == crypto_hash_sha256_BYTES);
if (sodium_compare(lastRemoteBin, noncebin, crypto_secretbox_NONCEBYTES) != -1) {
// Refuse to accept a lower nonce, return an error
delete[] lastRemoteBin;
delete[] noncebin;
return "error";
}
unsigned char* secret = new unsigned char[crypto_secretbox_KEYBYTES];
sodium_hex2bin(secret, crypto_secretbox_KEYBYTES, secretHex.toStdString().c_str(), crypto_secretbox_KEYBYTES*2,
NULL, NULL, NULL);
unsigned char* encrypted = new unsigned char[encryptedhex.length() / 2];
sodium_hex2bin(encrypted, encryptedhex.length() / 2, encryptedhex.toStdString().c_str(), encryptedhex.length(),
NULL, NULL, NULL);
int decryptedLen = encryptedhex.length() / 2 - crypto_secretbox_MACBYTES;
unsigned char* decrypted = new unsigned char[decryptedLen];
int result = crypto_secretbox_open_easy(decrypted, encrypted, encryptedhex.length() / 2, noncebin, secret);
QString payload;
if (result == -1) {
payload = "error";
} else {
// Update the last seen remote hex
saveNonceHex(NonceType::REMOTE, noncehex);
saveLastSeenTime();
char* decryptedStr = new char[decryptedLen + 1];
sodium_memzero(decryptedStr, decryptedLen + 1);
memcpy(decryptedStr, decrypted, decryptedLen);
payload = QString(decryptedStr);
delete[] decryptedStr;
}
delete[] secret;
delete[] lastRemoteBin;
delete[] noncebin;
delete[] encrypted;
delete[] decrypted;
qDebug() << "Returning decrypted payload="<<payload;
return payload;
}
// Process an incoming text message. The message has to be encrypted with the secret key (or the temporary secret key)
void AppDataServer::processMessage(QString message, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient, AppConnectionType connType) {
qDebug() << "processMessage message";
//qDebug() << "processMessage message=" << message; // this can log sensitive info
auto replyWithError = [=]() {
auto r = QJsonDocument(QJsonObject{
{"error", "Encryption error"},
{"to", getWormholeCode(getSecretHex())}
}).toJson();
pClient->sendTextMessage(r);
return;
};
// First, extract the command from the message
auto msg = QJsonDocument::fromJson(message.toUtf8());
// Check if we got an error from the websocket
if (msg.object().contains("error")) {
qDebug() << "Error:" << msg.toJson();
return;
}
// If the message is a ping, just ignore it
if (msg.object().contains("ping")) {
return;
}
// Then, check if the message is encrpted
if (!msg.object().contains("nonce")) {
replyWithError();
return;
}
auto decrypted = decryptMessage(msg, getSecretHex(), getNonceHex(NonceType::REMOTE));
// If the decryption failed, maybe this is a new connection, so see if the dialog is open and a
// temp secret is in place
if (decrypted == "error") {
// If the dialog is open, then there might be a temporary, new secret key. Attempt to decrypt
// with that.
if (!tempSecret.isEmpty()) {
// Since this is a temp secret, the last seen nonce will be "0", so basically we'll accept any nonce
QString zeroNonce = QString("00").repeated(crypto_secretbox_NONCEBYTES);
decrypted = decryptMessage(msg, tempSecret, zeroNonce);
if (decrypted == "error") {
// Oh, well. Just return an error
replyWithError();
return;
}
else {
// This is a new connection. So, update the the secret. Note the last seen remote nonce has already been updated by
// decryptMessage()
saveNewSecret(tempSecret);
setAllowInternetConnection(tempWormholeClient != nullptr);
// Swap out the wormhole connection
mainWindow->replaceWormholeClient(tempWormholeClient);
tempWormholeClient = nullptr;
saveLastConnectedOver(connType);
processDecryptedMessage(decrypted, mainWindow, pClient);
// If the Connection UI is showing, we have to update the UI as well
if (ui != nullptr) {
// Update the connected phone information
updateConnectedUI();
// Update with a new QR Code for safety, so this secret isn't used by anyone else
updateUIWithNewQRCode(mainWindow);
}
return;
}
}
else {
replyWithError();
return;
}
} else {
saveLastConnectedOver(connType);
processDecryptedMessage(decrypted, mainWindow, pClient);
return;
}
}
// Decrypted method will be executed here.
void AppDataServer::processDecryptedMessage(QString message, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient) {
//qDebug() << "processDecryptedMessage message=" << message;
// First, extract the command from the message
auto msg = QJsonDocument::fromJson(message.toUtf8());
if (!msg.object().contains("command")) {
auto r = QJsonDocument(QJsonObject{
{"errorCode", -1},
{"errorMessage", "Unknown JSON format"}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
return;
}
if (msg.object()["command"] == "getInfo") {
processGetInfo(msg.object(), mainWindow, pClient);
}
else if (msg.object()["command"] == "getTransactions") {
processGetTransactions(mainWindow, pClient);
}
else if (msg.object()["command"] == "sendTx") {
processSendTx(msg.object()["tx"].toObject(), mainWindow, pClient);
}
else if (msg.object()["command"] == "sendmanyTx") {
processSendManyTx(msg.object()["tx"].toObject(), mainWindow, pClient);
}
else {
auto r = QJsonDocument(QJsonObject{
{"errorCode", -1},
{"errorMessage", "Command not found:" + msg.object()["command"].toString()}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
}
}
// "sendTx" command. This method will actually send money, so be careful with everything
void AppDataServer::processSendTx(QJsonObject sendTx, MainWindow* mainwindow, std::shared_ptr<ClientWebSocket> pClient) {
qDebug() << "processSendTx with to=" << sendTx["to"].toString();
auto error = [=](QString reason) {
auto r = QJsonDocument(QJsonObject{
{"errorCode", -1},
{"errorMessage", "Couldn't send Tx:" + reason}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
return;
};
// Refuse to send if the node is still syncing
if (Settings::getInstance()->isSyncing()) {
error(QObject::tr("Node is still syncing."));
return;
}
// Create a Tx Object
Tx tx;
tx.fee = Settings::getMinerFee();
// Find a from address that has at least the sending amout
CAmount amt = CAmount::fromDecimalString(sendTx["amount"].toString());
auto allBalances = mainwindow->getRPC()->getModel()->getAllBalances();
QList<QPair<QString, CAmount>> bals;
for (auto i : allBalances.keys()) {
// Filter out balances that don't have the requisite amount
if (allBalances.value(i) < amt)
continue;
bals.append(QPair<QString, CAmount>(i, allBalances.value(i)));
}
if (bals.isEmpty()) {
error(QObject::tr("No sapling or transparent addresses with enough balance to spend."));
return;
}
std::sort(bals.begin(), bals.end(), [=](const QPair<QString, CAmount>a, const QPair<QString, CAmount> b) -> bool {
// Sort z addresses first
return a.first > b.first;
});
tx.fromAddr = bals[0].first;
tx.toAddrs = { ToFields{ sendTx["to"].toString(), amt, sendTx["memo"].toString()} };
// TODO: Respect the autoshield change setting
QString validation = mainwindow->doSendTxValidations(tx);
if (!validation.isEmpty()) {
error(validation);
return;
}
json params = json::array();
mainwindow->getRPC()->fillTxJsonParams(params, tx);
std::cout << std::setw(2) << params << std::endl;
// And send the Tx
mainwindow->getRPC()->executeTransaction(tx,
[=] (QString txid) {
auto r = QJsonDocument(QJsonObject{
{"version", 1.0},
{"command", "sendTxSubmitted"},
{"txid", txid}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
},
// Errored while submitting Tx
[=] (QString, QString errStr) {
auto r = QJsonDocument(QJsonObject{
{"version", 1.0},
{"command", "sendTxFailed"},
{"err", errStr}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
}
);
auto r = QJsonDocument(QJsonObject{
{"version", 1.0},
{"command", "sendTx"},
{"result", "success"}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
}
// "sendmanyTx" command. This method will actually send money, so be careful with everything
void AppDataServer::processSendManyTx(QJsonObject sendmanyTx, MainWindow* mainwindow, std::shared_ptr<ClientWebSocket> pClient) {
qDebug() << "processSendManyTx with to=" << sendmanyTx["to"].toString();
auto error = [=](QString reason) {
auto r = QJsonDocument(QJsonObject{
{"errorCode", -1},
{"errorMessage", "Couldn't send Tx:" + reason}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
return;
};
// Refuse to send if the node is still syncing
if (Settings::getInstance()->isSyncing()) {
error(QObject::tr("Node is still syncing."));
return;
}
// Create a Tx Object
Tx tx;
tx.fee = Settings::getMinerFee();
// Find a from address that has at least the sending amout
CAmount amt = CAmount::fromDecimalString(sendmanyTx["amount"].toString());
auto allBalances = mainwindow->getRPC()->getModel()->getAllBalances();
QList<QPair<QString, CAmount>> bals;
for (auto i : allBalances.keys()) {
// Filter out balances that don't have the requisite amount
if (allBalances.value(i) < amt)
continue;
bals.append(QPair<QString, CAmount>(i, allBalances.value(i)));
}
if (bals.isEmpty()) {
error(QObject::tr("No sapling or transparent addresses with enough balance to spend."));
return;
}
std::sort(bals.begin(), bals.end(), [=](const QPair<QString, CAmount>a, const QPair<QString, CAmount> b) -> bool {
// Sort z addresses first
return a.first > b.first;
});
//send to more then one Receipent
int totalSendManyItems = sendmanyTx.size();
for (int i=0; i < totalSendManyItems; i++) {
amt = CAmount::fromDecimalString(sendmanyTx["amount"].toString() % QString::number(i+1));
QString addr = sendmanyTx["to"].toString() % QString::number(i+1);
QString memo = sendmanyTx["memo"].toString() % QString::number(i+1);
tx.fromAddr = bals[0].first;
tx.toAddrs = { ToFields{ addr, amt, memo} };
}
// TODO: Respect the autoshield change setting
QString validation = mainwindow->doSendTxValidations(tx);
if (!validation.isEmpty()) {
error(validation);
return;
}
json params = json::array();
mainwindow->getRPC()->fillTxJsonParams(params, tx);
std::cout << std::setw(2) << params << std::endl;
// And send the Tx
mainwindow->getRPC()->executeTransaction(tx,
[=] (QString txid) {
auto r = QJsonDocument(QJsonObject{
{"version", 1.0},
{"command", "sendTxSubmitted"},
{"txid", txid}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
},
// Errored while submitting Tx
[=] (QString, QString errStr) {
auto r = QJsonDocument(QJsonObject{
{"version", 1.0},
{"command", "sendTxFailed"},
{"err", errStr}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
}
);
auto r = QJsonDocument(QJsonObject{
{"version", 1.0},
{"command", "sendTx"},
{"result", "success"}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
}
// "getInfo" command
void AppDataServer::processGetInfo(QJsonObject jobj, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient) {
auto connectedName = jobj["name"].toString();
if (mainWindow == nullptr || mainWindow->getRPC() == nullptr) {
pClient->close(QWebSocketProtocol::CloseCodeNormal, "Not yet ready");
return;
}
// Max spendable safely from a z address and from any address
CAmount maxZSpendable;
CAmount maxSpendable;
for (auto a : mainWindow->getRPC()->getModel()->getAllBalances().keys()) {
if (Settings::getInstance()->isSaplingAddress(a)) {
if (mainWindow->getRPC()->getModel()->getAllBalances().value(a) > maxZSpendable) {
maxZSpendable = mainWindow->getRPC()->getModel()->getAllBalances().value(a);
}
}
if (mainWindow->getRPC()->getModel()->getAllBalances().value(a) > maxSpendable) {
maxSpendable = mainWindow->getRPC()->getModel()->getAllBalances().value(a);
}
}
setConnectedName(connectedName);
auto r = QJsonDocument(QJsonObject {
{"version", 1.0},
{"command", "getInfo"},
{"saplingAddress", mainWindow->getRPC()->getDefaultSaplingAddress()},
{"tAddress", mainWindow->getRPC()->getDefaultTAddress()},
{"balance", AppDataModel::getInstance()->getTotalBalance().toDecimalDouble()},
{"maxspendable", maxSpendable.toDecimalDouble()},
{"maxzspendable", maxZSpendable.toDecimalDouble()},
{"tokenName", Settings::getTokenName()},
// changing this to hushprice is a backward incompatible change that requires
// changing SDL, litewalletd and SDA in unison, and would break older clients
// so we just leave it for now
{"zecprice", Settings::getInstance()->getHUSHPrice()},
{"serverversion", QString(APP_VERSION)}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
}
void AppDataServer::processGetTransactions(MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient) {
QJsonArray txns;
auto model = mainWindow->getRPC()->getTransactionsModel();
qDebug() << "processGetTransactions";
// Add transactions
for (int i = 0; i < model->rowCount(QModelIndex()) && i < Settings::getMaxMobileAppTxns(); i++) {
txns.append(QJsonObject{
{"type", model->getType(i)},
{"datetime", model->getDate(i)},
{"amount", model->getAmt(i)},
{"txid", model->getTxId(i)},
{"address", model->getAddr(i)},
// {"memo", model->getMemo(i)},
{"confirmations", model->getConfirmations(i)}
});
}
auto r = QJsonDocument(QJsonObject{
{"version", 1.0},
{"command", "getTransactions"},
{"transactions", txns}
}).toJson();
pClient->sendTextMessage(encryptOutgoing(r));
}
// ==============================
// AppDataModel
// ==============================
AppDataModel* AppDataModel::instance = nullptr;

179
src/websockets.h

@ -1,179 +0,0 @@
// Copyright 2019-2023 The Hush developers
// Released under the GPLv3
#ifndef WEBSOCKETS_H
#define WEBSOCKETS_H
#include "precompiled.h"
#include "camount.h"
#include "mainwindow.h"
#include "ui_mobileappconnector.h"
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
QT_FORWARD_DECLARE_CLASS(QWebSocket)
class WSServer;
// We're going to wrap the websocket in this class, because the underlying QWebSocket might get closed
// or deleted while a callback is waiting to get the data back. Therefore, we write a custom "sendTextMessage"
// class that checks all this before sending.
class ClientWebSocket {
public:
ClientWebSocket(QWebSocket* c, WSServer* s = nullptr) { client = c; server = s; }
void sendTextMessage(QString m);
void close(QWebSocketProtocol::CloseCode code, const QString& msg) { client->close(code, msg); }
private:
QWebSocket* client;
WSServer* server;
};
class WSServer : public QObject
{
Q_OBJECT
public:
explicit WSServer(quint16 port, bool debug = false, QObject *parent = nullptr);
bool isValidConnection(QWebSocket* c) { return m_clients.contains(c); }
~WSServer();
Q_SIGNALS:
void closed();
private Q_SLOTS:
void onNewConnection();
void processTextMessage(QString message);
void processBinaryMessage(QByteArray message);
void socketDisconnected();
private:
QWebSocketServer *m_pWebSocketServer;
MainWindow *m_mainWindow;
QList<QWebSocket *> m_clients;
bool m_debug;
};
class WormholeClient : public QObject {
Q_OBJECT
private Q_SLOTS:
void onConnected();
void onTextMessageReceived(QString message);
void closed();
public:
WormholeClient(MainWindow* parent, QString wormholeCode);
~WormholeClient();
void connect();
void retryConnect();
private:
MainWindow* parent = nullptr;
QWebSocket* m_webSocket = nullptr;
QTimer* timer = nullptr;
QString code;
int retryCount = 0;
bool shuttingDown = false;
};
enum NonceType {
LOCAL = 1,
REMOTE
};
enum AppConnectionType {
DIRECT = 1,
INTERNET
};
class AppDataServer {
public:
static AppDataServer* getInstance() {
if (instance == nullptr) {
instance = new AppDataServer();
}
return instance;
}
void connectAppDialog(MainWindow* parent);
void updateConnectedUI();
void updateUIWithNewQRCode(MainWindow* mainwindow);
void processSendTx(QJsonObject sendTx, MainWindow* mainwindow, std::shared_ptr<ClientWebSocket> pClient);
void processSendManyTx(QJsonObject sendmanyTx, MainWindow* mainwindow, std::shared_ptr<ClientWebSocket> pClient);
void processMessage(QString message, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient, AppConnectionType connType);
void processGetInfo(QJsonObject jobj, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient);
void processDecryptedMessage(QString message, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient);
void processGetTransactions(MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient);
QString decryptMessage(QJsonDocument msg, QString secretHex, QString lastRemoteNonceHex);
QString encryptOutgoing(QString msg);
QString getWormholeCode(QString secretHex);
QString getSecretHex();
void saveNewSecret(QString secretHex);
void registerNewTempSecret(QString tmpSecretHex, bool allowInternet, MainWindow* main);
QString getNonceHex(NonceType nt);
void saveNonceHex(NonceType nt, QString noncehex);
bool getAllowInternetConnection();
void setAllowInternetConnection(bool allow);
void saveLastSeenTime();
QDateTime getLastSeenTime();
void setConnectedName(QString name);
QString getConnectedName();
bool isAppConnected();
QString connDesc(AppConnectionType t);
void saveLastConnectedOver(AppConnectionType type);
AppConnectionType getLastConnectionType();
private:
AppDataServer() = default;
static AppDataServer* instance;
Ui_MobileAppConnector* ui;
QString tempSecret;
WormholeClient* tempWormholeClient = nullptr;
};
class AppDataModel {
public:
static AppDataModel* getInstance() {
if (instance == NULL)
instance = new AppDataModel();
return instance;
}
CAmount getTBalance() { return balTransparent; }
CAmount getZBalance() { return balShielded; }
CAmount getTotalBalance() { return balTotal; }
void setBalances(CAmount transparent, CAmount shielded) {
balTransparent = transparent;
balShielded = shielded;
balTotal = balTransparent + balShielded;
}
private:
AppDataModel() = default; // Private, for singleton
CAmount balTransparent;
CAmount balShielded;
CAmount balTotal;
QString saplingAddress;
static AppDataModel* instance;
};
#endif // WEBSOCKETS_H
Loading…
Cancel
Save