@ -13,6 +13,7 @@
# include "ui_settings.h"
# include "ui_viewalladdresses.h"
# include "ui_validateaddress.h"
# include "ui_viewtransaction.h"
# include "ui_rescandialog.h"
# include "ui_getblock.h"
# include "rpc.h"
@ -99,6 +100,9 @@ MainWindow::MainWindow(QWidget *parent) :
// Get Block
QObject : : connect ( ui - > actionGet_Block , & QAction : : triggered , this , & MainWindow : : getBlock ) ;
// View tx
QObject : : connect ( ui - > actionView_Transaction , & QAction : : triggered , this , & MainWindow : : viewTransaction ) ;
// Address Book
QObject : : connect ( ui - > action_Address_Book , & QAction : : triggered , this , & MainWindow : : addressBook ) ;
@ -132,6 +136,7 @@ MainWindow::MainWindow(QWidget *parent) :
qDebug ( ) < < " Created RPC " ;
setupMiningTab ( ) ;
setupDebugLogTab ( ) ;
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
void MainWindow : : getBlock ( ) {
// Make sure everything is up and running
@ -912,7 +1060,7 @@ void MainWindow::getBlock() {
// First thing is ask the user for a block height
bool ok ;
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 )
return ;
@ -934,19 +1082,42 @@ void MainWindow::getBlock() {
QString property_value ;
DEBUG ( " property = " < < props . toObject ( ) [ property_name ] ) ;
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 " ) ;
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 (
QPair < QString , QString > ( property_name ,
property_value )
) ;
// tx properties are added in their own special way above
if ( property_name ! = " tx " ) {
propsList . append (
QPair < QString , QString > ( property_name , property_value )
) ;
}
}
ValidateAddressesModel model ( gb . tblProps , propsList ) ;
@ -962,21 +1133,22 @@ void MainWindow::doImport(QList<QString>* keys) {
return ;
}
DEBUG ( " keys.size= " < < keys - > size ( ) ) ;
if ( keys - > isEmpty ( ) ) {
delete keys ;
ui - > statusBar - > showMessage ( tr ( " Private key import rescan finished " ) ) ;
return ;
}
// Pop the first key
QString key = keys - > first ( ) ;
keys - > pop_front ( ) ;
bool rescan = keys - > isEmpty ( ) ;
// Get the first key
QString key = keys - > takeFirst ( ) ;
if ( key . startsWith ( " SK " ) | |
key . startsWith ( " secret " ) ) { // Z key
bool rescan = false ;
if ( Settings : : getInstance ( ) - > isValidSaplingPrivateKey ( key ) ) {
DEBUG ( " importing zaddr privkey with rescan= " < < rescan ) ;
rpc - > importZPrivKey ( key , rescan , [ = ] ( auto ) { this - > doImport ( keys ) ; } ) ;
} else {
DEBUG ( " importing taddr privkey with rescan= " < < rescan ) ;
rpc - > importTPrivKey ( key , rescan , [ = ] ( auto ) { this - > doImport ( keys ) ; } ) ;
}
}
@ -1077,7 +1249,6 @@ void MainWindow::payHushURI(QString uri, QString myAddr) {
}
}
void MainWindow : : importPrivKey ( ) {
QDialog d ( this ) ;
Ui_PrivKey pui ;
@ -1089,20 +1260,27 @@ void MainWindow::importPrivKey() {
tr ( " Please paste your private keys here, one per line " ) % " . \n " %
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 ( ) ) {
auto rawkeys = pui . privKeyTxt - > toPlainText ( ) . trimmed ( ) . split ( " \n " ) ;
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 ) {
return ! key . startsWith ( " # " ) & & ! key . trimmed ( ) . isEmpty ( ) ;
} ) ;
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 ) {
return key . trimmed ( ) . split ( " " ) [ 0 ] ;
} ) ;
// Special case.
// 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
@ -1113,13 +1291,53 @@ void MainWindow::importPrivKey() {
delete multiline ;
}
// Start the import. The function takes ownership of keys
QTimer : : singleShot ( 1 , [ = ] ( ) { doImport ( keys ) ; } ) ;
// Finally, validate all keys, removing any which are invalid
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.
QMessageBox : : information ( this ,
" Imported " , tr ( " The keys were imported! It may take several minutes to rescan the blockchain. Until then, functionality may be limited " ) ,
if ( rescan ) {
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 ) ;
} 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
}
}
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 ( ) {
qDebug ( ) < < __FUNCTION__ ;
// Set up context menu on peers tab
@ -1918,33 +2196,8 @@ void MainWindow::setupPeersTab() {
menu . exec ( ui - > peersTable - > viewport ( ) - > mapToGlobal ( pos ) ) ;
} ) ;
/*
//grep 'BAN THRESHOLD EXCEEDED' ~/.hush/HUSH3/debug.log
//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;
}
@ -2049,6 +2302,11 @@ void MainWindow::setupTransactionsTab() {
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 ) ;
} ) ;
menu . addAction ( tr ( " View transaction " ) , [ = ] ( ) {
ui - > statusBar - > showMessage ( tr ( " Viewing transaction " ) + " " + txid , 3 * 1000 ) ;
viewTxid ( txid ) ;
} ) ;
if ( ! addr . isEmpty ( ) ) {
menu . addAction ( tr ( " Copy address " ) , [ = ] ( ) {
QGuiApplication : : clipboard ( ) - > setText ( addr ) ;