Browse Source

Merge branch 'dev'

master v1.4.1
Duke 4 months ago
parent
commit
b3f195432e
  1. 1
      README.md
  2. 21
      doc/relnotes/README.md
  3. BIN
      res/silentdragon_be.qm
  4. 635
      res/silentdragon_be.ts
  5. 639
      res/silentdragon_bg.ts
  6. BIN
      res/silentdragon_de.qm
  7. 635
      res/silentdragon_de.ts
  8. BIN
      res/silentdragon_es.qm
  9. 635
      res/silentdragon_es.ts
  10. BIN
      res/silentdragon_fi.qm
  11. 635
      res/silentdragon_fi.ts
  12. BIN
      res/silentdragon_fil.qm
  13. 635
      res/silentdragon_fil.ts
  14. BIN
      res/silentdragon_fr.qm
  15. 635
      res/silentdragon_fr.ts
  16. BIN
      res/silentdragon_hr.qm
  17. 635
      res/silentdragon_hr.ts
  18. BIN
      res/silentdragon_it.qm
  19. 635
      res/silentdragon_it.ts
  20. BIN
      res/silentdragon_nl.qm
  21. 635
      res/silentdragon_nl.ts
  22. BIN
      res/silentdragon_pl.qm
  23. 635
      res/silentdragon_pl.ts
  24. BIN
      res/silentdragon_pt.qm
  25. 635
      res/silentdragon_pt.ts
  26. BIN
      res/silentdragon_ro.qm
  27. 635
      res/silentdragon_ro.ts
  28. BIN
      res/silentdragon_ru.qm
  29. 635
      res/silentdragon_ru.ts
  30. BIN
      res/silentdragon_sr.qm
  31. 635
      res/silentdragon_sr.ts
  32. BIN
      res/silentdragon_tr.qm
  33. 635
      res/silentdragon_tr.ts
  34. BIN
      res/silentdragon_uk.qm
  35. 635
      res/silentdragon_uk.ts
  36. BIN
      res/silentdragon_zh.qm
  37. 637
      res/silentdragon_zh.ts
  38. 1
      silentdragon.pro
  39. 1
      silentdragonx.pro
  40. 5
      src/connection.cpp
  41. 350
      src/mainwindow.cpp
  42. 4
      src/mainwindow.h
  43. 69
      src/mainwindow.ui
  44. 34
      src/privkey.ui
  45. 26
      src/rpc.cpp
  46. 2
      src/rpc.h
  47. 31
      src/settings.cpp
  48. 2
      src/settings.h
  49. 3
      src/validateaddress.cpp
  50. 2
      src/validateaddress.h
  51. 2
      src/version.h
  52. 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

21
doc/relnotes/README.md

@ -10,6 +10,27 @@ 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.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_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 \

5
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;
@ -936,8 +937,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 +961,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;
} }

350
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,52 @@ 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){
qDebug() << __func__ << ":rescanning from height " << pui.rescanfrom->text().toInt() << " 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 +1985,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 +2195,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 +2301,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 +2330,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>

26
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"},
@ -352,7 +374,7 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function<void(
{"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"; qDebug() << "Detected new-style HUSH WIF";
@ -360,7 +382,7 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function<void(
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "42"}, {"id", "42"},
{"method", "importprivkey"}, {"method", "importprivkey"},
{"params", QJsonArray { privkey, (rescan? "yes" : "no") }}, {"params", QJsonArray { privkey, "", rescan }},
}; };
} }

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);

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.1"

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