Compare commits

...

53 Commits

Author SHA1 Message Date
onryo fe04ef3095 Merge branch 'dev' 3 months ago
onryo 23e3e3899a Add release name before merging 3 months ago
Duke 23813ae00e Update relnotes 3 months ago
Duke d8e528f696 Bump version to 1.4.2 3 months ago
Duke 5610d6c572 Update relnotes for 1.4.2 3 months ago
Duke 3a6f632b9b Try to prevent #155 4 months ago
Duke 2754629a95 Do not log sensitive data to STDOUT 4 months ago
Duke eea2a9a6bf Choose number of compile jobs in build.sh #156 5 months ago
duke e6195fd918 Merge pull request 'Replace old graphics with new' (#147) from onryo into dev 5 months ago
Duke b3f195432e Merge branch 'dev' 5 months ago
Duke 02325ad7ae relnotes 5 months ago
Duke 0c9a3f43ad linguist 5 months ago
Duke 00b143e04f Create relnotes for 1.4.1 5 months ago
Duke c442377473 bump version 5 months ago
duke 5659a2a725 Merge pull request 'Debug tab' (#148) from debuglog into dev 5 months ago
Duke 702619a8e0 Support dragonx debug log and custom number of lines 5 months ago
onryo ee5e94cfd0 fix build when removing old files 5 months ago
onryo 8119c5b6e3 fix 146 5 months ago
Duke da3fb9c8a4 Add refresh button for debug log 5 months ago
Duke 0f45b3fe5b Add tab for viewing the debug log 5 months ago
Duke 05790935ba Revert "change gif when sending" 5 months ago
duke 203c23dad2 Merge pull request 'Allow custom rescan height on privkey import' (#145) from import into dev 5 months ago
Duke 1b10aefbe8 Cleanup 5 months ago
Duke d35a601dcc verify that taddr privkeys are base58 5 months ago
Duke b52c7bc151 Remove isSproutAddress 5 months ago
Duke a0cc4184bc Trim leading and trailing whitespace from rescan height when importing a privkey 5 months ago
Duke 8b41a3b0f4 Disable rescanfrom input if rescan is unchecked 5 months ago
Duke 68bd0db44c Only import valid zaddr+taddr privkeys and only rescan if there was at least 1 valid privkey 5 months ago
Duke 2000377783 Too bad this code does not seem to work 5 months ago
Duke e042c1fb09 We do not support sprout privkeys that begin with SK 5 months ago
Duke 7db95fbf49 Support disabling rescan or a custom rescan height when importing privkeys 5 months ago
Duke 8bb0b3df5c Make import popup look nicer 5 months ago
Duke f689547a36 Add basic rescan options to import popup 5 months ago
onryo f50ccd78b5 change gif when sending 5 months ago
onryo 219e41ccb5 dialog text 5 months ago
Duke ec6327a6b0 Add function to validate taddr privkeys 5 months ago
Duke 227dcaddbb Slightly better zaddr privkey validation 5 months ago
onryo 868bf83155 add rescanfrom 5 months ago
onryo 4ef65d1a3f add chkrescan 5 months ago
Duke 17c44521be Use takeFirst() instead of first() and pop_front() 5 months ago
Duke c5ceac8443 View transparent spends and shielded outputs in txs; Allow double clicking on a tx in tx tab to view tx 6 months ago
Duke 8b8bca02ac Add View transaction as right menu option in tx tab 6 months ago
Duke 81702d25c1 Add view transaction ui file 6 months ago
Duke fb67b86d0d View Transaction 6 months ago
Duke f914f6d61a Just say Error, since we show this error for all errors, not just transaction errors 6 months ago
Duke a18014b6d4 Render valuePools json and each tx in their own property 6 months ago
Duke 1e4095651a Allow items to be selected 6 months ago
Duke 946ed661e9 Tor v3 has been supported for a while 6 months ago
Duke 9e4c26ccd2 Show transaction list in CSV format when viewing a block; allow viewing block 0 6 months ago
Duke 5dc1944084 More debugging about block properties 6 months ago
Duke 9187f3c620 Add getrawtransaction RPC method 6 months ago
Duke 4d28ac2969 Add z_viewtransaction RPC method 6 months ago
Duke d3846c7bdf Merge branch 'master' into dev 6 months ago
  1. 1
      README.md
  2. 3
      application.qrc
  3. 23
      build.sh
  4. 30
      doc/relnotes/README.md
  5. BIN
      res/silentdragon-animated-dark.gif
  6. BIN
      res/silentdragon-animated-startup.gif
  7. BIN
      res/silentdragon-animated.gif
  8. BIN
      res/silentdragon_be.qm
  9. 635
      res/silentdragon_be.ts
  10. 639
      res/silentdragon_bg.ts
  11. BIN
      res/silentdragon_de.qm
  12. 635
      res/silentdragon_de.ts
  13. BIN
      res/silentdragon_es.qm
  14. 635
      res/silentdragon_es.ts
  15. BIN
      res/silentdragon_fi.qm
  16. 635
      res/silentdragon_fi.ts
  17. BIN
      res/silentdragon_fil.qm
  18. 635
      res/silentdragon_fil.ts
  19. BIN
      res/silentdragon_fr.qm
  20. 635
      res/silentdragon_fr.ts
  21. BIN
      res/silentdragon_hr.qm
  22. 635
      res/silentdragon_hr.ts
  23. BIN
      res/silentdragon_it.qm
  24. 635
      res/silentdragon_it.ts
  25. BIN
      res/silentdragon_nl.qm
  26. 635
      res/silentdragon_nl.ts
  27. BIN
      res/silentdragon_pl.qm
  28. 635
      res/silentdragon_pl.ts
  29. BIN
      res/silentdragon_pt.qm
  30. 635
      res/silentdragon_pt.ts
  31. BIN
      res/silentdragon_ro.qm
  32. 635
      res/silentdragon_ro.ts
  33. BIN
      res/silentdragon_ru.qm
  34. 635
      res/silentdragon_ru.ts
  35. BIN
      res/silentdragon_sr.qm
  36. 635
      res/silentdragon_sr.ts
  37. BIN
      res/silentdragon_tr.qm
  38. 635
      res/silentdragon_tr.ts
  39. BIN
      res/silentdragon_uk.qm
  40. 635
      res/silentdragon_uk.ts
  41. BIN
      res/silentdragon_zh.qm
  42. 637
      res/silentdragon_zh.ts
  43. 1
      silentdragon.pro
  44. 1
      silentdragonx.pro
  45. 23
      src/connection.cpp
  46. 351
      src/mainwindow.cpp
  47. 4
      src/mainwindow.h
  48. 69
      src/mainwindow.ui
  49. 34
      src/privkey.ui
  50. 34
      src/rpc.cpp
  51. 2
      src/rpc.h
  52. 4
      src/sendtab.cpp
  53. 31
      src/settings.cpp
  54. 2
      src/settings.h
  55. 3
      src/validateaddress.cpp
  56. 2
      src/validateaddress.h
  57. 2
      src/version.h
  58. 85
      src/viewtransaction.ui

1
README.md

@ -37,7 +37,6 @@ bits of data.
This means your IP address is known to these servers. Enable Tor setting This means your IP address is known to these servers. Enable Tor setting
in SilentDragon to prevent this, or better yet, use TAILS: https://tails.boum.org/ in SilentDragon to prevent this, or better yet, use TAILS: https://tails.boum.org/
NOTE: Tor v3 is not yet supported.
# Installation # Installation

3
application.qrc

@ -26,9 +26,6 @@
<qresource prefix="/img"> <qresource prefix="/img">
<file alias="hushdlogo.png">res/hushdlogo.png</file> <file alias="hushdlogo.png">res/hushdlogo.png</file>
<file alias="logobig.gif">res/logobig.gif</file> <file alias="logobig.gif">res/logobig.gif</file>
<file alias="silentdragon-animated.gif">res/silentdragon-animated.gif</file>
<file alias="silentdragon-animated-dark.gif">res/silentdragon-animated-dark.gif</file>
<file alias="silentdragon-animated-startup.gif">res/silentdragon-animated-startup.gif</file>
<file alias="silentdragon-animated-startup-dark.gif">res/silentdragon-animated-startup-dark.gif</file> <file alias="silentdragon-animated-startup-dark.gif">res/silentdragon-animated-startup-dark.gif</file>
</qresource> </qresource>
<qresource prefix="/translations"> <qresource prefix="/translations">

23
build.sh

@ -4,18 +4,7 @@
set -e set -e
UNAME=$(uname) UNAME=$(uname)
VERSION=$(grep APP_VERSION src/version.h |cut -d\" -f2)
if [ "$UNAME" == "Linux" ] ; then
JOBS=$(nproc)
elif [ "$UNAME" == "FreeBSD" ] ; then
JOBS=$(nproc)
elif [ "$UNAME" == "Darwin" ] ; then
JOBS=$(sysctl -n hw.ncpu)
else
JOBS=1
fi
VERSION=$(cat src/version.h |cut -d\" -f2)
CONF=${SDCONF:-silentdragon.pro} CONF=${SDCONF:-silentdragon.pro}
WALLET="SilentDragon" WALLET="SilentDragon"
if [ "$CONF" == "silentdragonx.pro" ] ; then if [ "$CONF" == "silentdragonx.pro" ] ; then
@ -40,12 +29,12 @@ make --version
qbuild () { qbuild () {
qmake $CONF -spec linux-clang CONFIG+=debug qmake $CONF -spec linux-clang CONFIG+=debug
make -j$JOBS make -j2 "$@"
} }
qbuild_release () { qbuild_release () {
qmake $CONF -spec linux-clang CONFIG+=release qmake $CONF -spec linux-clang CONFIG+=release
make -j$JOBS make -j2 "$@"
} }
if [ "$1" == "clean" ]; then if [ "$1" == "clean" ]; then
@ -55,9 +44,9 @@ elif [ "$1" == "linguist" ]; then
lrelease $CONF lrelease $CONF
elif [ "$1" == "cleanbuild" ]; then elif [ "$1" == "cleanbuild" ]; then
make clean make clean
qbuild qbuild "$@"
elif [ "$1" == "release" ]; then elif [ "$1" == "release" ]; then
qbuild_release qbuild_release "$@"
else else
qbuild qbuild "$@"
fi fi

30
doc/relnotes/README.md

@ -10,6 +10,36 @@ and no longer on Github, since they banned Duke Leto and
also because they censor many people around the world and work with also because they censor many people around the world and work with
evil organizations. evil organizations.
# SilentDragon 1.4.2 "Waggish Weevil"
```
11 files changed, 35 insertions(+), 32 deletions(-)
```
* Fix a coredump that can happen after importing a private key https://git.hush.is/hush/SilentDragon/issues/155
* Prevent the logging of sensitive data to STDOUT, since it could be redirected to a file or shared in a bug report or screenshot https://git.hush.is/hush/SilentDragon/commit/2754629a95c1efe603b3c3245f90a26b3ed7f177
* Update startup animation graphic https://git.hush.is/hush/SilentDragon/pulls/147
* Allow compiling with a custom number of jobs in build.sh https://git.hush.is/hush/SilentDragon/issues/156
# SilentDragon 1.4.1 "Scintillating Sundew"
```
52 files changed, 7163 insertions(+), 5023 deletions(-)
```
* View details of a transaction #136
* If a tx has no memo, you can simply double click on it to view more details.
* Otherwise, right click and choose View Transaction
* Render more details in View Block #135
* Now all transactions are listed as well as valuePools key
* Greatly improved Private Key Importing
* A custom rescan height or disabling rescanning is now possible
* Invalid privkeys are now filtered out before being sent to the full node
* This prevents confusing popups #142
* Fixed bug where importing multiple taddrs would rescan multiple times
* New Tab "Debug Log" renders content of debug.log
* Allows showing the last X lines of debug.log, defaults to 50 lines
# SilentDragon 1.4.0 "Zany Zooid" # SilentDragon 1.4.0 "Zany Zooid"
``` ```

BIN
res/silentdragon-animated-dark.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

BIN
res/silentdragon-animated-startup.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

BIN
res/silentdragon-animated.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

BIN
res/silentdragon_be.qm

Binary file not shown.

635
res/silentdragon_be.ts

File diff suppressed because it is too large

639
res/silentdragon_bg.ts

File diff suppressed because it is too large

BIN
res/silentdragon_de.qm

Binary file not shown.

635
res/silentdragon_de.ts

File diff suppressed because it is too large

BIN
res/silentdragon_es.qm

Binary file not shown.

635
res/silentdragon_es.ts

File diff suppressed because it is too large

BIN
res/silentdragon_fi.qm

Binary file not shown.

635
res/silentdragon_fi.ts

File diff suppressed because it is too large

BIN
res/silentdragon_fil.qm

Binary file not shown.

635
res/silentdragon_fil.ts

File diff suppressed because it is too large

BIN
res/silentdragon_fr.qm

Binary file not shown.

635
res/silentdragon_fr.ts

File diff suppressed because it is too large

BIN
res/silentdragon_hr.qm

Binary file not shown.

635
res/silentdragon_hr.ts

File diff suppressed because it is too large

BIN
res/silentdragon_it.qm

Binary file not shown.

635
res/silentdragon_it.ts

File diff suppressed because it is too large

BIN
res/silentdragon_nl.qm

Binary file not shown.

635
res/silentdragon_nl.ts

File diff suppressed because it is too large

BIN
res/silentdragon_pl.qm

Binary file not shown.

635
res/silentdragon_pl.ts

File diff suppressed because it is too large

BIN
res/silentdragon_pt.qm

Binary file not shown.

635
res/silentdragon_pt.ts

File diff suppressed because it is too large

BIN
res/silentdragon_ro.qm

Binary file not shown.

635
res/silentdragon_ro.ts

File diff suppressed because it is too large

BIN
res/silentdragon_ru.qm

Binary file not shown.

635
res/silentdragon_ru.ts

File diff suppressed because it is too large

BIN
res/silentdragon_sr.qm

Binary file not shown.

635
res/silentdragon_sr.ts

File diff suppressed because it is too large

BIN
res/silentdragon_tr.qm

Binary file not shown.

635
res/silentdragon_tr.ts

File diff suppressed because it is too large

BIN
res/silentdragon_uk.qm

Binary file not shown.

635
res/silentdragon_uk.ts

File diff suppressed because it is too large

BIN
res/silentdragon_zh.qm

Binary file not shown.

637
res/silentdragon_zh.ts

File diff suppressed because it is too large

1
silentdragon.pro

@ -90,6 +90,7 @@ HEADERS += \
FORMS += \ FORMS += \
src/getblock.ui \ src/getblock.ui \
src/viewtransaction.ui \
src/mainwindow.ui \ src/mainwindow.ui \
src/qrcode.ui \ src/qrcode.ui \
src/rescandialog.ui \ src/rescandialog.ui \

1
silentdragonx.pro

@ -89,6 +89,7 @@ HEADERS += \
FORMS += \ FORMS += \
src/getblock.ui \ src/getblock.ui \
src/viewtransaction.ui \
src/mainwindow.ui \ src/mainwindow.ui \
src/qrcode.ui \ src/qrcode.ui \
src/rescandialog.ui \ src/rescandialog.ui \

23
src/connection.cpp

@ -8,6 +8,7 @@
#include "rpc.h" #include "rpc.h"
#include "precompiled.h" #include "precompiled.h"
#include "version.h" #include "version.h"
#include "sd.h"
extern bool isdragonx; extern bool isdragonx;
@ -889,15 +890,25 @@ Connection::~Connection() {
void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJsonValue)>& cb, void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJsonValue)>& cb,
const std::function<void(QNetworkReply*, const QJsonValue&)>& ne) { const std::function<void(QNetworkReply*, const QJsonValue&)>& ne) {
if (shutdownInProgress) { if (shutdownInProgress) {
qDebug() << __func__ << ": Ignoring RPC because shutdown in progress"; DEBUG("Ignoring RPC because shutdown in progress");
return; return;
} }
if(payload.isNull() || payload.isUndefined()) { if(payload.isNull() || payload.isUndefined()) {
qDebug() << "no payload! ignoring"; DEBUG("no payload! ignoring");
return; return;
} else { } else {
qDebug() << __func__ << ": " << payload["method"].toString() << payload; // this will match importprivkey z_importkey z_importviewingkey importwallet z_importwallet
// and some other RPCs that have no GUI
// So this code ends up redacting payloads which contain private keys and filenames which contain private keys
QRegExp re("import");
//DEBUG("payload.toString==" << payload["method"].toString());
//DEBUG("payload.toString.indexIn==" << re.indexIn(payload["method"].toString()) );
if( re.indexIn(payload["method"].toString()) == -1 ) {
DEBUG( payload["method"].toString() << payload );
} else {
DEBUG( payload["method"].toString() << " PAYLOAD REDACTED " );
}
} }
QJsonDocument jd_rpc_call(payload.toObject()); QJsonDocument jd_rpc_call(payload.toObject());
@ -908,7 +919,7 @@ void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJson
QObject::connect(reply, &QNetworkReply::finished, [=] { QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater(); reply->deleteLater();
if (shutdownInProgress) { if (shutdownInProgress) {
// Ignoring callback because shutdown in progress DEBUG("Ignoring callback because shutdown in progress");
return; return;
} }
@ -936,8 +947,10 @@ void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJson
void Connection::doRPCWithDefaultErrorHandling(const QJsonValue& payload, const std::function<void(QJsonValue)>& cb) { void Connection::doRPCWithDefaultErrorHandling(const QJsonValue& payload, const std::function<void(QJsonValue)>& cb) {
doRPC(payload, cb, [=] (QNetworkReply* reply, const QJsonValue &parsed) { doRPC(payload, cb, [=] (QNetworkReply* reply, const QJsonValue &parsed) {
if (!parsed.isUndefined() && !parsed["error"].toObject()["message"].isNull()) { if (!parsed.isUndefined() && !parsed["error"].toObject()["message"].isNull()) {
DEBUG("got a parse error");
this->showTxError(parsed["error"].toObject()["message"].toString()); this->showTxError(parsed["error"].toObject()["message"].toString());
} else { } else {
DEBUG("got a reply error");
this->showTxError(reply->errorString()); this->showTxError(reply->errorString());
} }
}); });
@ -958,7 +971,7 @@ void Connection::showTxError(const QString& error) {
return; return;
shown = true; shown = true;
QMessageBox::critical(main, QObject::tr("Transaction Error"), QObject::tr("There was an error! : ") + "\n\n" QMessageBox::critical(main, QObject::tr("Error"), QObject::tr("There was an error! : ") + "\n\n"
+ error, QMessageBox::StandardButton::Ok); + error, QMessageBox::StandardButton::Ok);
shown = false; shown = false;
} }

351
src/mainwindow.cpp

@ -13,6 +13,7 @@
#include "ui_settings.h" #include "ui_settings.h"
#include "ui_viewalladdresses.h" #include "ui_viewalladdresses.h"
#include "ui_validateaddress.h" #include "ui_validateaddress.h"
#include "ui_viewtransaction.h"
#include "ui_rescandialog.h" #include "ui_rescandialog.h"
#include "ui_getblock.h" #include "ui_getblock.h"
#include "rpc.h" #include "rpc.h"
@ -99,6 +100,9 @@ MainWindow::MainWindow(QWidget *parent) :
// Get Block // Get Block
QObject::connect(ui->actionGet_Block, &QAction::triggered, this, &MainWindow::getBlock); QObject::connect(ui->actionGet_Block, &QAction::triggered, this, &MainWindow::getBlock);
// View tx
QObject::connect(ui->actionView_Transaction, &QAction::triggered, this, &MainWindow::viewTransaction);
// Address Book // Address Book
QObject::connect(ui->action_Address_Book, &QAction::triggered, this, &MainWindow::addressBook); QObject::connect(ui->action_Address_Book, &QAction::triggered, this, &MainWindow::addressBook);
@ -132,6 +136,7 @@ MainWindow::MainWindow(QWidget *parent) :
qDebug() << "Created RPC"; qDebug() << "Created RPC";
setupMiningTab(); setupMiningTab();
setupDebugLogTab();
restoreSavedStates(); restoreSavedStates();
} }
@ -903,6 +908,149 @@ void MainWindow::validateAddress() {
}); });
} }
// Ask user for txid to view
void MainWindow::viewTransaction() {
// Make sure everything is up and running
if (!getRPC() || !getRPC()->getConnection())
return;
// First thing is ask the user for a txid
bool ok;
QString txid = QInputDialog::getText(this, tr("View Transaction"),
tr("Enter Transaction ID (txid):"), QLineEdit::Normal, "", &ok);
if (!ok)
return;
viewTxid(txid);
}
// view a given txid
void MainWindow::viewTxid(QString txid) {
// ignore leading and trailing whitespace
txid = txid.trimmed();
QRegExp rx("^[0-9a-f]{64}$");
if(!rx.exactMatch(txid)) {
DEBUG("invalid txid " << txid );
return;
}
// we got a valid txid
getRPC()->getrawtransaction(txid, [=] (QJsonValue props) {
// TODO: only z_viewtransaction shows memo
// getRPC()->z_viewtransaction(txid, [=] (QJsonValue props) {
QDialog d(this);
Ui_ViewTransaction vt;
vt.setupUi(&d);
Settings::saveRestore(&d);
Settings::saveRestoreTableHeader(vt.tblProps, &d, "getblockprops");
vt.tblProps->horizontalHeader()->setStretchLastSection(true);
vt.lblHeight->setText(txid);
QList<QPair<QString, QString>> propsList;
for (QString property_name: props.toObject().keys()) {
QString property_value;
DEBUG("property " << property_name << "=" << props.toObject()[property_name] );
if (props.toObject()[property_name].isString()) {
property_value = props.toObject()[property_name].toString();
} else if (props.toObject()[property_name].isDouble()) {
property_value = QString::number( props.toObject()[property_name].toDouble(), 'f', 0);
} else if (props.toObject()[property_name].isBool()) {
property_value = props.toObject()[property_name].toBool() ? "true" : "false" ;
} else if (props.toObject()[property_name].isArray()) {
DEBUG( property_name << " is an array");
// vin is the Vector of INputs of transparent spends
if( property_name == "vin" ) {
int index = 0;
auto vins = props.toObject()[property_name].toArray();
for (const auto& vin : vins) {
QString this_vin = "vin " + QString::number(index);
auto vinObj = vin.toObject();
// Is this a normal input or coinbase input?
bool is_coinbase = vinObj["coinbase"].toString().length() > 0;
if (is_coinbase) {
propsList.append(
QPair<QString, QString>( this_vin + " coinbase", vinObj["coinbase"].toString() )
);
} else {
// the address of this spend
propsList.append(
QPair<QString, QString>( this_vin + " address", vinObj["address"].toString() )
);
// the value of this spend
propsList.append(
QPair<QString, QString>( this_vin + " value", QString::number(vinObj["value"].toDouble()) )
);
// the txid in which this UTXO was created
propsList.append(
QPair<QString, QString>( this_vin + " txid", vinObj["txid"].toString() )
);
// the index of vout in the txid it was created
propsList.append(
QPair<QString, QString>( this_vin + " vout", QString::number(vinObj["vout"].toInt()) )
);
}
// sequence number is part of both coinbase and non-coinbase vins
propsList.append(
QPair<QString, QString>( this_vin + " sequence", QString::number(vinObj["sequence"].toDouble()) )
);
index++;
}
} else if (property_name == "vout") {
// vout = Vector of OUTputs of transparent sends
auto vouts = props.toObject()[property_name].toArray();
if(vouts.size() == 0) {
propsList.append( QPair<QString, QString>( "vout", "Empty") );
} else {
//...
}
} else if (property_name == "vShieldedOutput") {
// the vector of shielded outputs
auto zouts = props.toObject()[property_name].toArray();
if(zouts.size() == 0) {
propsList.append( QPair<QString, QString>( property_name, "Empty"));
} else {
int index = 0;
QString property_value = "";
for (const auto& zout : zouts) {
auto zoutObj = zout.toObject();
auto properties = {"proof", "cv", "cmu", "encCiphertext", "outCiphertext", "ephemeralKey" };
for(const auto& prop : properties ) {
propsList.append(
QPair<QString, QString>( "vShieldedOutput " + QString::number(index) + " " + prop, zoutObj[prop].toString() )
);
}
index++;
}
}
} else if (property_name == "vShieldedSpend") {
// the vector of shielded spends
auto zins = props.toObject()[property_name].toArray();
if(zins.size() == 0) {
propsList.append( QPair<QString, QString>( property_name, "Empty"));
} else {
// TODO
}
}
} else if (props.toObject()[property_name].isObject()) {
DEBUG( property_name << " is an object");
}
if (property_name != "vin" && property_name != "vout" &&
property_name != "vShieldedOutput" && property_name != "vShieldedSpend" ) {
propsList.append( QPair<QString, QString>( property_name, property_value ));
}
}
ValidateAddressesModel model(vt.tblProps, propsList);
vt.tblProps->setModel(&model);
d.exec();
});
}
// Get block info // Get block info
void MainWindow::getBlock() { void MainWindow::getBlock() {
// Make sure everything is up and running // Make sure everything is up and running
@ -912,7 +1060,7 @@ void MainWindow::getBlock() {
// First thing is ask the user for a block height // First thing is ask the user for a block height
bool ok; bool ok;
int i = QInputDialog::getInt(this, tr("Get Block Info"), int i = QInputDialog::getInt(this, tr("Get Block Info"),
tr("Enter Block Height:"), 1, 1, 2147483647, 1, &ok); tr("Enter Block Height:"), 12345, 0, 2147483647, 1, &ok);
if (!ok) if (!ok)
return; return;
@ -934,19 +1082,42 @@ void MainWindow::getBlock() {
QString property_value; QString property_value;
DEBUG("property = " << props.toObject()[property_name] ); DEBUG("property " << property_name << "=" << props.toObject()[property_name] );
if (props.toObject()[property_name].isString()) { if (props.toObject()[property_name].isString()) {
property_value = props.toObject()[property_name].toString(); property_value = props.toObject()[property_name].toString();
} else if (props.toObject()[property_name].isDouble()) { } else if (props.toObject()[property_name].isDouble()) {
property_value = QString::number( props.toObject()[property_name].toDouble(), 'f', 0); property_value = QString::number( props.toObject()[property_name].toDouble(), 'f', 0);
} else if (props.toObject()[property_name].isBool()) { } else if (props.toObject()[property_name].isBool()) {
property_value = props.toObject()[property_name].toBool() ? "true" : "false" ; property_value = props.toObject()[property_name].toBool() ? "true" : "false" ;
} else if (props.toObject()[property_name].isArray()) {
DEBUG( property_name << " is an array");
if( property_name == "tx") {
auto txs = props.toObject()[property_name].toArray();
int index = 0;
// create a property for each tx in the block so it renders in a more useful way
for (const auto& tx : txs) {
QString this_property_name = "tx " + QString::number(index);
propsList.append(
QPair<QString, QString>( this_property_name, tx.toString() )
);
index++;
}
} else if (property_name == "valuePools") {
auto stuff = props.toObject()[property_name].toArray();
property_value = QJsonDocument(stuff).toJson();
property_value.remove("\n");
property_value.remove(" ");
}
} else if (props.toObject()[property_name].isObject()) {
DEBUG( property_name << " is an object");
} }
propsList.append( // tx properties are added in their own special way above
QPair<QString, QString>( property_name, if (property_name != "tx") {
property_value ) propsList.append(
); QPair<QString, QString>( property_name, property_value )
);
}
} }
ValidateAddressesModel model(gb.tblProps, propsList); ValidateAddressesModel model(gb.tblProps, propsList);
@ -962,21 +1133,22 @@ void MainWindow::doImport(QList<QString>* keys) {
return; return;
} }
DEBUG(" keys.size= " << keys->size() );
if (keys->isEmpty()) { if (keys->isEmpty()) {
delete keys; delete keys;
ui->statusBar->showMessage(tr("Private key import rescan finished"));
return; return;
} }
// Pop the first key // Get the first key
QString key = keys->first(); QString key = keys->takeFirst();
keys->pop_front();
bool rescan = keys->isEmpty();
if (key.startsWith("SK") || bool rescan = false;
key.startsWith("secret")) { // Z key if (Settings::getInstance()->isValidSaplingPrivateKey(key) ) {
DEBUG("importing zaddr privkey with rescan=" << rescan);
rpc->importZPrivKey(key, rescan, [=] (auto) { this->doImport(keys); }); rpc->importZPrivKey(key, rescan, [=] (auto) { this->doImport(keys); });
} else { } else {
DEBUG("importing taddr privkey with rescan=" << rescan);
rpc->importTPrivKey(key, rescan, [=] (auto) { this->doImport(keys); }); rpc->importTPrivKey(key, rescan, [=] (auto) { this->doImport(keys); });
} }
} }
@ -1077,7 +1249,6 @@ void MainWindow::payHushURI(QString uri, QString myAddr) {
} }
} }
void MainWindow::importPrivKey() { void MainWindow::importPrivKey() {
QDialog d(this); QDialog d(this);
Ui_PrivKey pui; Ui_PrivKey pui;
@ -1089,20 +1260,27 @@ void MainWindow::importPrivKey() {
tr("Please paste your private keys here, one per line") % ".\n" % tr("Please paste your private keys here, one per line") % ".\n" %
tr("The keys will be imported into your connected Hush node")); tr("The keys will be imported into your connected Hush node"));
// if rescan is not checked, disable the rescan height input
QObject::connect(pui.chkrescan, &QCheckBox::stateChanged, [=](auto checked) {
pui.rescanfrom->setEnabled(checked);
});
if (d.exec() == QDialog::Accepted && !pui.privKeyTxt->toPlainText().trimmed().isEmpty()) { if (d.exec() == QDialog::Accepted && !pui.privKeyTxt->toPlainText().trimmed().isEmpty()) {
auto rawkeys = pui.privKeyTxt->toPlainText().trimmed().split("\n"); auto rawkeys = pui.privKeyTxt->toPlainText().trimmed().split("\n");
QList<QString> keysTmp; QList<QString> keysTmp;
// Filter out all the empty keys. // Filter out all the empty keys and comment lines
std::copy_if(rawkeys.begin(), rawkeys.end(), std::back_inserter(keysTmp), [=] (auto key) { std::copy_if(rawkeys.begin(), rawkeys.end(), std::back_inserter(keysTmp), [=] (auto key) {
return !key.startsWith("#") && !key.trimmed().isEmpty(); return !key.startsWith("#") && !key.trimmed().isEmpty();
}); });
auto keys = new QList<QString>(); auto keys = new QList<QString>();
// ignore anything after the first space of a line, such as if you paste a line from z_exportwallet output
std::transform(keysTmp.begin(), keysTmp.end(), std::back_inserter(*keys), [=](auto key) { std::transform(keysTmp.begin(), keysTmp.end(), std::back_inserter(*keys), [=](auto key) {
return key.trimmed().split(" ")[0]; return key.trimmed().split(" ")[0];
}); });
// Special case. // Special case.
// Sometimes, when importing from a paperwallet or such, the key is split by newlines, and might have // Sometimes, when importing from a paperwallet or such, the key is split by newlines, and might have
// been pasted like that. So check to see if the whole thing is one big private key // been pasted like that. So check to see if the whole thing is one big private key
@ -1113,13 +1291,53 @@ void MainWindow::importPrivKey() {
delete multiline; delete multiline;
} }
// Start the import. The function takes ownership of keys // Finally, validate all keys, removing any which are invalid
QTimer::singleShot(1, [=]() {doImport(keys);}); auto keysValidated = new QList<QString>();
auto settings = Settings::getInstance();
std::copy_if(keys->begin(), keys->end(), std::back_inserter(*keysValidated), [=] (auto key) {
bool isValid = settings->isValidSaplingPrivateKey(key) || settings->isValidTransparentPrivateKey(key);
if (!isValid) { DEBUG("privkey " << key << " is not valid"); }
return isValid;
});
DEBUG("found " << keysValidated->size() << " valid privkeys");
bool rescan = pui.chkrescan->isChecked();
// avoid giving invalid data to RPCs and a rescan if there were no valid privkeys
if(keysValidated->size() == 0) {
QMessageBox::information(this, "No valid keys",
tr("No valid private keys were found, please make sure you copy and pasted correctly"),
QMessageBox::Ok);
return;
}
// Start the import. The function takes ownership of keysValidated
QTimer::singleShot(1, [=]() {
// we import all keys without rescanning and then finally decide if we will rescan once
doImport(keysValidated);
if (rescan) {
//TODO: verify rescanfrom is a valid integer
rpc->rescan(pui.rescanfrom->text().trimmed().toInt() , [=] (QJsonValue response){
//DEBUG("rescanning from height " << pui.rescanfrom->text().toInt() << " finished" << response);
DEBUG("rescanning finished" << response);
ui->statusBar->showMessage(tr("Rescanning finished"), 5000);
});
}
});
// Show the dialog that keys will be imported. // Show the dialog that keys will be imported.
QMessageBox::information(this, if(rescan) {
"Imported", tr("The keys were imported! It may take several minutes to rescan the blockchain. Until then, functionality may be limited"), QMessageBox::information(this, "Imported",
tr("The keys were imported! It may take several hours to rescan the blockchain. Until then, functionality may be limited"),
QMessageBox::Ok); QMessageBox::Ok);
} else {
QMessageBox::information(this, "Imported",
tr("The keys were imported! You chose to not rescan, so funds in that address will not show up in your wallet yet."),
QMessageBox::Ok);
}
} }
} }
@ -1768,6 +1986,66 @@ void MainWindow::setupMiningTab() {
// Mining tab currently only enabled for DragonX // Mining tab currently only enabled for DragonX
} }
} }
QString MainWindow::readDebugLines(uint32_t lines) {
QString coin = isdragonx ? "DRAGONX" : "HUSH3";
#ifdef Q_OS_LINUX
QFile file(QDir::homePath() + "/.hush/" + coin + "/debug.log");
#elif defined(Q_OS_DARWIN)
QFile file(QDir::homePath() + "/Library/Application Support/Hush/" + coin + "/debug.log");
#elif defined(Q_OS_WIN64)
QFile file(QDir::homePath() + "/AppData/Roaming/Hush/" + coin + "/debug.log");
#else
// Bless Your Heart, You Like Danger!
QFile file(QDir::homePath() + "/.hush/" + coin + "/debug.log");
#endif // Q_OS_LINUX
if(file.exists()) {
DEBUG(": Found debug.log at " << file);
} else {
DEBUG("No debug.log found!");
return "";
}
if(file.open(QIODevice::ReadOnly))
{
qint64 fileSize = file.size();
DEBUG("debug.log size=" << fileSize);
if(fileSize < 2) {
DEBUG("debug.log is too small");
return "";
}
file.seek(file.size()-1);
uint32_t count = 0;
while ( (count < lines) && (file.pos() > 0) )
{
QString ch = file.read(1);
file.seek(file.pos()-2);
if (ch == "\n")
count++;
}
file.seek(file.pos()+2);
QString debugText = file.readAll();
DEBUG("got " << debugText.size() << " bytes of debugText");
file.close();
return debugText;
}
return "";
}
void MainWindow::setupDebugLogTab() {
ui->debugLog->setReadOnly(true);
ui->debugLog->setPlainText("Loading debug log...");
QObject::connect(ui->refreshDebugButton, &QPushButton::clicked, [=] () {
uint32_t debugLines = ui->debugLines->text().trimmed().toInt();
if (debugLines == 0) { debugLines = 50; }
DEBUG("refresh debug log clicked with debugLines=" << debugLines);
ui->debugLog->setPlainText( readDebugLines(debugLines) );
});
ui->debugLog->setPlainText( readDebugLines() );
}
void MainWindow::setupPeersTab() { void MainWindow::setupPeersTab() {
qDebug() << __FUNCTION__; qDebug() << __FUNCTION__;
// Set up context menu on peers tab // Set up context menu on peers tab
@ -1918,33 +2196,8 @@ void MainWindow::setupPeersTab() {
menu.exec(ui->peersTable->viewport()->mapToGlobal(pos)); menu.exec(ui->peersTable->viewport()->mapToGlobal(pos));
}); });
/*
//grep 'BAN THRESHOLD EXCEEDED' ~/.hush/HUSH3/debug.log //grep 'BAN THRESHOLD EXCEEDED' ~/.hush/HUSH3/debug.log
//grep Disconnected ... //grep Disconnected ...
QFile debuglog = "";
#ifdef Q_OS_LINUX
debuglog = "~/.hush/HUSH3/debug.log";
#elif defined(Q_OS_DARWIN)
debuglog = "~/Library/Application Support/Hush/HUSH3/debug.log";
#elif defined(Q_OS_WIN64)
// "C:/Users/<USER>/AppData/Roaming/<APPNAME>",
// TODO: get current username
debuglog = "C:/Users/<USER>/AppData/Roaming/Hush/HUSH3/debug.log";
#else
// Bless Your Heart, You Like Danger!
// There are open bounties to port HUSH softtware to OpenBSD and friends:
// git.hush.is/hush/tasks
debuglog = "~/.hush/HUSH3/debug.log";
#endif // Q_OS_LINUX
if(debuglog.exists()) {
qDebug() << __func__ << ": Found debuglog at " << debuglog;
} else {
qDebug() << __func__ << ": No debug.log found";
}
*/
//ui->recentlyBannedPeers = "Could not open " + debuglog; //ui->recentlyBannedPeers = "Could not open " + debuglog;
} }
@ -2049,6 +2302,11 @@ void MainWindow::setupTransactionsTab() {
mb.exec(); mb.exec();
} }
} }
} else {
// if no memo, show View Transaction
DEBUG("double clicked tx index=" << index);
QString txid = txModel->getTxId(index.row());
viewTxid(txid);
} }
}); });
@ -2073,6 +2331,11 @@ void MainWindow::setupTransactionsTab() {
ui->statusBar->showMessage(tr("Copied to clipboard"), 3 * 1000); ui->statusBar->showMessage(tr("Copied to clipboard"), 3 * 1000);
}); });
menu.addAction(tr("View transaction"), [=] () {
ui->statusBar->showMessage(tr("Viewing transaction") + " " + txid, 3 * 1000);
viewTxid(txid);
});
if (!addr.isEmpty()) { if (!addr.isEmpty()) {
menu.addAction(tr("Copy address"), [=] () { menu.addAction(tr("Copy address"), [=] () {
QGuiApplication::clipboard()->setText(addr); QGuiApplication::clipboard()->setText(addr);

4
src/mainwindow.h

@ -53,6 +53,8 @@ public:
void validateAddress(); void validateAddress();
void getBlock(); void getBlock();
void viewTransaction();
void viewTxid(QString txid = "");
void updateLabels(); void updateLabels();
void updateTAddrCombo(bool checked); void updateTAddrCombo(bool checked);
@ -83,6 +85,7 @@ private:
void closeEvent(QCloseEvent* event); void closeEvent(QCloseEvent* event);
QString readDebugLines(uint32_t lines = 50);
void setupSendTab(); void setupSendTab();
void setupPeersTab(); void setupPeersTab();
void setupTransactionsTab(); void setupTransactionsTab();
@ -92,6 +95,7 @@ private:
void setupChatTab(); void setupChatTab();
void setupMarketTab(); void setupMarketTab();
void setupMiningTab(); void setupMiningTab();
void setupDebugLogTab();
void slot_change_theme(QString& themeName); void slot_change_theme(QString& themeName);
void slot_change_currency(const QString& currencyName); void slot_change_currency(const QString& currencyName);

69
src/mainwindow.ui

@ -1130,6 +1130,68 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_debug">
<attribute name="title">
<string>Debug Log</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="refreshDebugButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
Click to see the latest debug log data
<string/>
</property>
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="debugLabel">
<property name="text">
<string>Number of lines to show</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="debugLines">
<property name="enabled">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>50</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<widget class="QPlainTextEdit" name="debugLog">
<property name="plainText">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_5"> <widget class="QWidget" name="tab_5">
<attribute name="title"> <attribute name="title">
<string>Node info</string> <string>Node info</string>
@ -1602,6 +1664,7 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QMenuBar" name="menuBar"> <widget class="QMenuBar" name="menuBar">
<property name="geometry"> <property name="geometry">
<rect> <rect>
@ -1641,6 +1704,7 @@
<string>&amp;Apps</string> <string>&amp;Apps</string>
</property> </property>
<addaction name="actionGet_Block"/> <addaction name="actionGet_Block"/>
<addaction name="actionView_Transaction"/>
<addaction name="actionValidate_Address"/> <addaction name="actionValidate_Address"/>
<addaction name="separator"/> <addaction name="separator"/>
</widget> </widget>
@ -1751,6 +1815,11 @@
<string>Get Block Info</string> <string>Get Block Info</string>
</property> </property>
</action> </action>
<action name="actionView_Transaction">
<property name="text">
<string>View Transaction Info</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>

34
src/privkey.ui

@ -14,7 +14,7 @@
<string>Private Keys</string> <string>Private Keys</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="0"> <item row="1" column="0" colspan="3">
<widget class="QPlainTextEdit" name="privKeyTxt"> <widget class="QPlainTextEdit" name="privKeyTxt">
<property name="plainText"> <property name="plainText">
<string/> <string/>
@ -22,6 +22,38 @@
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QCheckBox" name="chkrescan">
<property name="text">
<string>Rescan</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="rescanlabel">
<property name="text">
<string>Rescan Height</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="rescanfrom">
<property name="text">
<string notr="true">1</string>
</property>
<property name="minimumSize">
<size>
<width>50</width>
</size>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>

34
src/rpc.cpp

@ -275,6 +275,28 @@ void RPC::getZUnspent(const std::function<void(QJsonValue)>& cb) {
conn->doRPCWithDefaultErrorHandling(payload, cb); conn->doRPCWithDefaultErrorHandling(payload, cb);
} }
void RPC::z_viewtransaction(QString txid, const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = {
{"jsonrpc", "1.0"},
{"id", "42"},
{"method", "z_viewtransaction"},
{"params", QJsonArray {txid}}
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::getrawtransaction(QString txid, const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = {
{"jsonrpc", "1.0"},
{"id", "42"},
{"method", "getrawtransaction"},
{"params", QJsonArray {txid, 1}}
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::newZaddr(const std::function<void(QJsonValue)>& cb) { void RPC::newZaddr(const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
@ -347,24 +369,24 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function<void(
// If privkey starts with 5, K or L, use old-style Hush params, same as BTC+ZEC // If privkey starts with 5, K or L, use old-style Hush params, same as BTC+ZEC
if( privkey.startsWith("5") || privkey.startsWith("K") || privkey.startsWith("L") ) { if( privkey.startsWith("5") || privkey.startsWith("K") || privkey.startsWith("L") ) {
qDebug() << "Detected old-style HUSH WIF"; DEBUG("Detected old-style taddr HUSH WIF");
payload = { payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "42"}, {"id", "42"},
{"method", "importprivkey"}, {"method", "importprivkey"},
{"params", QJsonArray { privkey, "", "false", "0", "128" }}, {"params", QJsonArray { privkey, "", rescan , "0", "128" }},
}; };
} else { } else {
qDebug() << "Detected new-style HUSH WIF"; DEBUG("Detected new-style taddr HUSH WIF");
payload = { payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "42"}, {"id", "42"},
{"method", "importprivkey"}, {"method", "importprivkey"},
{"params", QJsonArray { privkey, (rescan? "yes" : "no") }}, {"params", QJsonArray { privkey, "", rescan }},
}; };
} }
qDebug() << "Importing WIF with rescan=" << rescan; DEBUG("Importing taddr WIF with rescan=" << rescan);
conn->doRPCWithDefaultErrorHandling(payload, cb); conn->doRPCWithDefaultErrorHandling(payload, cb);
} }
@ -1693,7 +1715,7 @@ void RPC::shutdownHushd() {
d.setWindowTitle("SilentDragonX"); d.setWindowTitle("SilentDragonX");
} }
QMovie *movie1 = new QMovie(":/img/silentdragon-animated-dark.gif");; QMovie *movie1 = new QMovie(":/img/silentdragon-animated-startup-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name(); auto theme = Settings::getInstance()->get_theme_name();
movie1->setScaledSize(QSize(512,512)); movie1->setScaledSize(QSize(512,512));
connD.topIcon->setMovie(movie1); connD.topIcon->setMovie(movie1);

2
src/rpc.h

@ -101,6 +101,8 @@ public:
void newZaddr(const std::function<void(QJsonValue)>& cb); void newZaddr(const std::function<void(QJsonValue)>& cb);
void newTaddr(const std::function<void(QJsonValue)>& cb); void newTaddr(const std::function<void(QJsonValue)>& cb);
void z_viewtransaction(QString txid, const std::function<void(QJsonValue)>& cb);
void getrawtransaction(QString txid, const std::function<void(QJsonValue)>& cb);
void setGenerate(int proclimit, const std::function<void(QJsonValue)>& cb); void setGenerate(int proclimit, const std::function<void(QJsonValue)>& cb);
void stopGenerate(int proclimit, const std::function<void(QJsonValue)>& cb); void stopGenerate(int proclimit, const std::function<void(QJsonValue)>& cb);

4
src/sendtab.cpp

@ -693,7 +693,7 @@ bool MainWindow::confirmTx(Tx tx) {
return true; return true;
} else { } else {
return false; return false;
} }
} }
// Send button clicked // Send button clicked
@ -720,7 +720,7 @@ void MainWindow::sendButton() {
auto connD = new Ui_ConnectionDialog(); auto connD = new Ui_ConnectionDialog();
connD->setupUi(d); connD->setupUi(d);
QMovie *movie1 = new QMovie(":/img/silentdragon-animated-dark.gif");; QMovie *movie1 = new QMovie(":/img/silentdragon-animated-startup-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name(); auto theme = Settings::getInstance()->get_theme_name();
movie1->setScaledSize(QSize(512,512)); movie1->setScaledSize(QSize(512,512));
connD->topIcon->setMovie(movie1); connD->topIcon->setMovie(movie1);

31
src/settings.cpp

@ -134,13 +134,6 @@ bool Settings::isSaplingAddress(QString addr) {
(!isTestnet() && addr.startsWith("zs1")); (!isTestnet() && addr.startsWith("zs1"));
} }
bool Settings::isSproutAddress(QString addr) {
if (!isValidAddress(addr))
return false;
return isZAddress(addr) && !isSaplingAddress(addr);
}
bool Settings::isZAddress(QString addr) { bool Settings::isZAddress(QString addr) {
if (!isValidAddress(addr)) if (!isValidAddress(addr))
return false; return false;
@ -435,6 +428,30 @@ double Settings::getMinerFee() {
return 0.0001; return 0.0001;
} }
bool Settings::isValidTransparentPrivateKey(QString pk) {
if (pk.length() > 52) {
DEBUG("privkey invalid, too long");
return false;
}
if (pk.length() < 51) {
DEBUG("privkey invalid, too short");
return false;
}
// TODO: can a taddr privkey start with anything else?
if (pk.startsWith("U") || pk.startsWith("5") || pk.startsWith("L") || pk.startsWith("K") || pk.startsWith("7")) {
// verify only contains base58 characters
QRegExp exp("^[U5LK7][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51,52}$", Qt::CaseSensitive);
bool valid = exp.exactMatch(pk);
if(!valid) { DEBUG("privkey invalid, not base58"); }
return valid;
} else {
DEBUG("privkey invalid, wrong prefix");
return false;
}
}
bool Settings::isValidSaplingPrivateKey(QString pk) { bool Settings::isValidSaplingPrivateKey(QString pk) {
if (isTestnet()) { if (isTestnet()) {
QRegExp zspkey("^secret-extended-key-test[0-9a-z]{278}$", Qt::CaseInsensitive); QRegExp zspkey("^secret-extended-key-test[0-9a-z]{278}$", Qt::CaseInsensitive);

2
src/settings.h

@ -47,9 +47,9 @@ public:
void setTestnet(bool isTestnet); void setTestnet(bool isTestnet);
bool isSaplingAddress(QString addr); bool isSaplingAddress(QString addr);
bool isSproutAddress(QString addr);
bool isValidSaplingPrivateKey(QString pk); bool isValidSaplingPrivateKey(QString pk);
bool isValidTransparentPrivateKey(QString pk);
bool isSyncing(); bool isSyncing();
void setSyncing(bool syncing); void setSyncing(bool syncing);

3
src/validateaddress.cpp

@ -29,6 +29,9 @@ QVariant ValidateAddressesModel::data(const QModelIndex &index, int role) const
return QVariant(); return QVariant();
} }
Qt::ItemFlags ValidateAddressesModel::flags(const QModelIndex &index) const {
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QVariant ValidateAddressesModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant ValidateAddressesModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {

2
src/validateaddress.h

@ -18,6 +18,8 @@ public:
QVariant data(const QModelIndex &index, int role) const; QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
private: private:
QList<QPair<QString, QString>> props; QList<QPair<QString, QString>> props;
QStringList headers; QStringList headers;

2
src/version.h

@ -1 +1 @@
#define APP_VERSION "1.4.0" #define APP_VERSION "1.4.2"

85
src/viewtransaction.ui

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ViewTransaction</class>
<widget class="QDialog" name="ViewTransaction">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>View Transaction</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLabel" name="lblHeight">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction ID (txid):</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QTableView" name="tblProps">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item row="2" column="0" 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>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ViewTransaction</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>ViewTransaction</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>
Loading…
Cancel
Save