// Copyright 2019-2022 The Hush Developers
// Released under the GPLv3
# include <QLCDNumber>
# include "rpc.h"
# include "addressbook.h"
# include "settings.h"
# include "senttxstore.h"
# include "version.h"
# include <QThread>
# include "sd.h"
extern bool isdragonx ;
RPC : : RPC ( MainWindow * main ) {
auto cl = new ConnectionLoader ( main , this ) ;
// Execute the load connection async, so we can set up the rest of RPC properly.
QTimer : : singleShot ( 1 , [ = ] ( ) { cl - > loadConnection ( ) ; } ) ;
this - > main = main ;
this - > ui = main - > ui ;
// Setup balances table model
balancesTableModel = new BalancesTableModel ( main - > ui - > balancesTable ) ;
main - > ui - > balancesTable - > setModel ( balancesTableModel ) ;
// Setup transactions table model
transactionsTableModel = new TxTableModel ( ui - > transactionsTable ) ;
main - > ui - > transactionsTable - > setModel ( transactionsTableModel ) ;
main - > ui - > transactionsTable - > horizontalHeader ( ) - > setSectionResizeMode ( 3 , QHeaderView : : Stretch ) ;
peersTableModel = new PeersTableModel ( ui - > peersTable ) ;
main - > ui - > peersTable - > setModel ( peersTableModel ) ;
// tls ciphersuite is wide
main - > ui - > peersTable - > horizontalHeader ( ) - > setSectionResizeMode ( 1 , QHeaderView : : Stretch ) ;
bannedPeersTableModel = new BannedPeersTableModel ( ui - > bannedPeersTable ) ;
main - > ui - > bannedPeersTable - > setModel ( bannedPeersTableModel ) ;
main - > ui - > bannedPeersTable - > horizontalHeader ( ) - > setSectionResizeMode ( 2 , QHeaderView : : Stretch ) ;
qDebug ( ) < < __func__ < < " Done settings up TableModels " ;
// Set up timer to refresh Price
priceTimer = new QTimer ( main ) ;
QObject : : connect ( priceTimer , & QTimer : : timeout , [ = ] ( ) {
qDebug ( ) < < " Refreshing price data " ;
refreshPrice ( ) ;
} ) ;
priceTimer - > start ( Settings : : priceRefreshSpeed ) ;
qDebug ( ) < < __func__ < < " : started price refresh at speed= " < < Settings : : priceRefreshSpeed ;
qDebug ( ) < < " setting up timer for rescan data " ;
rescanTimer = new QTimer ( main ) ;
// PreciseTimer is needed for timely GUI updates
rescanTimer - > setTimerType ( Qt : : PreciseTimer ) ;
QObject : : connect ( rescanTimer , & QTimer : : timeout , [ = ] ( ) {
qDebug ( ) < < " Refreshing rescan data " ;
refreshRescan ( ) ;
} ) ;
rescanTimer - > start ( 10000 ) ;
qDebug ( ) < < __func__ < < " : started rescanTimer " ;
qDebug ( ) < < __func__ < < " : Setting up a timer to refresh the UI every few seconds " ;
timer = new QTimer ( main ) ;
QObject : : connect ( timer , & QTimer : : timeout , [ = ] ( ) {
qDebug ( ) < < " Refreshing main UI " ;
refresh ( ) ;
} ) ;
timer - > start ( Settings : : updateSpeed ) ;
// PreciseTimer is needed for timely GUI updates
timer - > setTimerType ( Qt : : PreciseTimer ) ;
qDebug ( ) < < __func__ < < " : Set up the timer to watch for tx status " ;
txTimer = new QTimer ( main ) ;
QObject : : connect ( txTimer , & QTimer : : timeout , [ = ] ( ) {
//qDebug() << "Watching tx status";
watchTxStatus ( ) ;
} ) ;
qDebug ( ) < < __func__ < < " Done settings up all timers " ;
usedAddresses = new QMap < QString , bool > ( ) ;
auto lang = Settings : : getInstance ( ) - > get_language ( ) ;
qDebug ( ) < < __func__ < < " : found lang= " < < lang < < " in config file " ;
main - > loadLanguage ( lang ) ;
qDebug ( ) < < __func__ < < " : setting UI to lang= " < < lang < < " found in config file " ;
}
RPC : : ~ RPC ( ) {
qDebug ( ) < < " Deleting stuff " ;
delete timer ;
delete txTimer ;
delete priceTimer ;
delete transactionsTableModel ;
delete balancesTableModel ;
delete peersTableModel ;
delete bannedPeersTableModel ;
delete utxos ;
delete allBalances ;
delete usedAddresses ;
delete zaddresses ;
delete taddresses ;
delete conn ;
}
void RPC : : setEHushd ( std : : shared_ptr < QProcess > p ) {
ehushd = p ;
}
void RPC : : pauseTimer ( ) {
qDebug ( ) < < " pausing main timer " ;
timer - > stop ( ) ;
}
void RPC : : restartTimer ( ) {
qDebug ( ) < < " restarting main timer " ;
timer - > start ( Settings : : updateSpeed ) ;
}
// Called when a connection to hushd is available.
void RPC : : setConnection ( Connection * c ) {
if ( c = = nullptr ) return ;
delete conn ;
this - > conn = c ;
ui - > statusBar - > showMessage ( " Ready! Thank you for helping secure the Hush network by running a full node. " ) ;
// See if we need to remove the reindex/rescan flags from the conf file
auto hushConfLocation = Settings : : getInstance ( ) - > getHushdConfLocation ( ) ;
Settings : : removeFromHushConf ( hushConfLocation , " rescan " ) ;
Settings : : removeFromHushConf ( hushConfLocation , " reindex " ) ;
// Refresh the UI
refreshPrice ( ) ;
//checkForUpdate();
// Force update, because this might be coming from a settings update
// where we need to immediately refresh
refresh ( true ) ;
}
QJsonValue RPC : : makePayload ( QString method , QString param , QString param2 ) {
// qDebug() << __func__ << ": method=" << method << " payload=" << param << ", " << param2;
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , method } ,
{ " params " , QJsonArray { param , param2 } }
} ;
return payload ;
}
QJsonValue RPC : : makePayload ( QString method , QString param ) {
// qDebug() << __func__ << ": method=" << method << " " << "payload=" << param;
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , method } ,
{ " params " , QJsonArray { param } }
} ;
return payload ;
}
QJsonValue RPC : : makePayload ( QString method , int param ) {
// qDebug() << __func__ << ": method=" << method << " payload=" << param;
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , method } ,
{ " params " , QJsonArray { param } }
} ;
return payload ;
}
QJsonValue RPC : : makePayload ( QString method ) {
// qDebug() << __func__ << ": method=" << method << " with no payload";
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , method } ,
} ;
return payload ;
}
//TODO: we can use listaddresses
void RPC : : getTAddresses ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " getaddressesbyaccount " ;
QString params = " " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method , " " ) , cb ) ;
}
// full or partial rescan
void RPC : : rescan ( qint64 height , const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " rescan " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method , height ) , cb ) ;
}
// get rescan info
void RPC : : getRescanInfo ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " getrescaninfo " ;
// do not show an error in case getrescaninfo doesn't exist in this hushd
conn - > doRPCIgnoreError ( makePayload ( method ) , cb ) ;
}
void RPC : : getnetworksolps ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " getnetworksolps " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
void RPC : : getlocalsolps ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " getlocalsolps " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
// get help
void RPC : : help ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " help " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
// add/remove a banned node. ip can include an optional netmask
void RPC : : setban ( QString ip , QString command , const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " setban " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method , ip , command ) , cb ) ;
}
//unban all banned peer nodes
void RPC : : clearBanned ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " clearbanned " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
void RPC : : z_sweepstatus ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " z_sweepstatus " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
void RPC : : z_consolidationstatus ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " z_consolidationstatus " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
void RPC : : getZAddresses ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " z_listaddresses " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
void RPC : : getTransparentUnspent ( const std : : function < void ( QJsonValue ) > & cb ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " listunspent " } ,
{ " params " , QJsonArray { 0 } } // Get UTXOs with 0 confirmations as well.
} ;
conn - > doRPCWithDefaultErrorHandling ( payload , cb ) ;
}
void RPC : : getZUnspent ( const std : : function < void ( QJsonValue ) > & cb ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " z_listunspent " } ,
{ " params " , QJsonArray { 0 } } // Get UTXOs with 0 confirmations as well.
} ;
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 ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " z_getnewaddress " } ,
{ " params " , QJsonArray { " sapling " } } ,
} ;
conn - > doRPCWithDefaultErrorHandling ( payload , cb ) ;
}
void RPC : : newTaddr ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " getnewaddress " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
void RPC : : setGenerate ( int proclimit , const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " setgenerate " ;
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , method } ,
{ " params " , QJsonArray { true , proclimit } }
} ;
conn - > doRPCWithDefaultErrorHandling ( payload , cb ) ;
}
void RPC : : stopGenerate ( int proclimit , const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " setgenerate " ;
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , method } ,
{ " params " , QJsonArray { false , proclimit } }
} ;
conn - > doRPCWithDefaultErrorHandling ( payload , cb ) ;
}
void RPC : : getZViewKey ( QString addr , const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " z_exportviewingkey " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method , addr ) , cb ) ;
}
void RPC : : getZPrivKey ( QString addr , const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " z_exportkey " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method , addr ) , cb ) ;
}
void RPC : : getTPrivKey ( QString addr , const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " dumpprivkey " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method , addr ) , cb ) ;
}
void RPC : : importZPrivKey ( QString privkey , bool rescan , const std : : function < void ( QJsonValue ) > & cb ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " z_importkey " } ,
{ " params " , QJsonArray { privkey , ( rescan ? " yes " : " no " ) } } ,
} ;
conn - > doRPCWithDefaultErrorHandling ( payload , cb ) ;
}
// TODO: support rescan height and prefix
void RPC : : importTPrivKey ( QString privkey , bool rescan , const std : : function < void ( QJsonValue ) > & cb ) {
QJsonObject payload ;
// 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 " ) ) {
DEBUG ( " Detected old-style taddr HUSH WIF " ) ;
payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " importprivkey " } ,
{ " params " , QJsonArray { privkey , " " , rescan , " 0 " , " 128 " } } ,
} ;
} else {
DEBUG ( " Detected new-style taddr HUSH WIF " ) ;
payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " importprivkey " } ,
{ " params " , QJsonArray { privkey , " " , rescan } } ,
} ;
}
DEBUG ( " Importing taddr WIF with rescan= " < < rescan ) ;
conn - > doRPCWithDefaultErrorHandling ( payload , cb ) ;
}
void RPC : : validateAddress ( QString address , const std : : function < void ( QJsonValue ) > & cb ) {
QString method = address . startsWith ( " z " ) ? " z_validateaddress " : " validateaddress " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method , address ) , cb ) ;
}
void RPC : : getBlock ( QString height , const std : : function < void ( QJsonValue ) > & cb ) {
conn - > doRPCWithDefaultErrorHandling ( makePayload ( " getblock " , height ) , cb ) ;
}
void RPC : : getBalance ( const std : : function < void ( QJsonValue ) > & cb ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " z_gettotalbalance " } ,
{ " params " , QJsonArray { 0 } } // Get Unconfirmed balance as well.
} ;
conn - > doRPCWithDefaultErrorHandling ( payload , cb ) ;
}
void RPC : : getPeerInfo ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " getpeerinfo " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
void RPC : : listBanned ( const std : : function < void ( QJsonValue ) > & cb ) {
QString method = " listbanned " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , cb ) ;
}
void RPC : : getTransactions ( const std : : function < void ( QJsonValue ) > & cb ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " listtransactions " } ,
{ " params " , QJsonArray { " * " , 99999 } }
} ;
conn - > doRPCWithDefaultErrorHandling ( payload , cb ) ;
}
void RPC : : mergeToAddress ( QJsonArray & params , const std : : function < void ( QJsonValue ) > & cb ,
const std : : function < void ( QString ) > & err ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " z_mergetoaddress " } ,
{ " params " , params }
} ;
conn - > doRPC ( payload , cb , [ = ] ( QNetworkReply * reply , const QJsonValue & parsed ) {
if ( ! parsed . isUndefined ( ) & & ! parsed [ " error " ] . toObject ( ) [ " message " ] . isNull ( ) ) {
err ( parsed [ " error " ] . toObject ( ) [ " message " ] . toString ( ) ) ;
} else {
err ( reply - > errorString ( ) ) ;
}
} ) ;
}
void RPC : : shieldCoinbase ( QJsonArray & params , const std : : function < void ( QJsonValue ) > & cb ,
const std : : function < void ( QString ) > & err ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " z_shieldcoinbase " } ,
{ " params " , params }
} ;
conn - > doRPC ( payload , cb , [ = ] ( QNetworkReply * reply , const QJsonValue & parsed ) {
if ( ! parsed . isUndefined ( ) & & ! parsed [ " error " ] . toObject ( ) [ " message " ] . isNull ( ) ) {
err ( parsed [ " error " ] . toObject ( ) [ " message " ] . toString ( ) ) ;
} else {
err ( reply - > errorString ( ) ) ;
}
} ) ;
}
void RPC : : sendZTransaction ( QJsonValue params , const std : : function < void ( QJsonValue ) > & cb ,
const std : : function < void ( QString ) > & err ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " z_sendmany " } ,
{ " params " , params }
} ;
conn - > doRPC ( payload , cb , [ = ] ( QNetworkReply * reply , const QJsonValue & parsed ) {
if ( ! parsed . isUndefined ( ) & & ! parsed [ " error " ] . toObject ( ) [ " message " ] . isNull ( ) ) {
err ( parsed [ " error " ] . toObject ( ) [ " message " ] . toString ( ) ) ;
} else {
err ( reply - > errorString ( ) ) ;
}
} ) ;
}
/**
* Method to get all the private keys for both z and t addresses . It will make 2 batch calls ,
* combine the result , and call the callback with a single list containing both the t - addr and z - addr
* private keys
*/
void RPC : : getAllPrivKeys ( const std : : function < void ( QList < QPair < QString , QString > > ) > cb ) {
if ( conn = = nullptr ) {
// No connection, just return
return ;
}
// A special function that will call the callback when two lists have been added
auto holder = new QPair < int , QList < QPair < QString , QString > > > ( ) ;
holder - > first = 0 ; // This is the number of times the callback has been called, initialized to 0
auto fnCombineTwoLists = [ = ] ( QList < QPair < QString , QString > > list ) {
// Increment the callback counter
holder - > first + + ;
// Add all
std : : copy ( list . begin ( ) , list . end ( ) , std : : back_inserter ( holder - > second ) ) ;
// And if the caller has been called twice, do the parent callback with the
// collected list
if ( holder - > first = = 2 ) {
// Sort so z addresses are on top
std : : sort ( holder - > second . begin ( ) , holder - > second . end ( ) ,
[ = ] ( auto a , auto b ) { return a . first > b . first ; } ) ;
cb ( holder - > second ) ;
delete holder ;
}
} ;
// A utility fn to do the batch calling
auto fnDoBatchGetPrivKeys = [ = ] ( QJsonValue getAddressPayload , QString privKeyDumpMethodName ) {
conn - > doRPCWithDefaultErrorHandling ( getAddressPayload , [ = ] ( QJsonValue resp ) {
QList < QString > addrs ;
for ( auto addr : resp . toArray ( ) ) {
addrs . push_back ( addr . toString ( ) ) ;
}
// Then, do a batch request to get all the private keys
conn - > doBatchRPC < QString > (
addrs ,
[ = ] ( auto addr ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , privKeyDumpMethodName } ,
{ " params " , QJsonArray { addr } } ,
} ;
return payload ;
} ,
[ = ] ( QMap < QString , QJsonValue > * privkeys ) {
QList < QPair < QString , QString > > allTKeys ;
for ( QString addr : privkeys - > keys ( ) ) {
allTKeys . push_back (
QPair < QString , QString > (
addr ,
privkeys - > value ( addr ) . toString ( ) ) ) ;
}
fnCombineTwoLists ( allTKeys ) ;
delete privkeys ;
}
) ;
} ) ;
} ;
// First get all the t and z addresses.
QJsonObject payloadT = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " getaddressesbyaccount " } ,
{ " params " , QJsonArray { " " } }
} ;
QJsonObject payloadZ = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " 42 " } ,
{ " method " , " z_listaddresses " }
} ;
fnDoBatchGetPrivKeys ( payloadT , " dumpprivkey " ) ;
fnDoBatchGetPrivKeys ( payloadZ , " z_exportkey " ) ;
}
// Build the RPC JSON Parameters for this tx
void RPC : : fillTxJsonParams ( QJsonArray & params , Tx tx ) {
Q_ASSERT ( QJsonValue ( params ) . isArray ( ) ) ;
// Get all the addresses and amounts
QJsonArray allRecepients ;
// For each addr/amt/memo, construct the JSON and also build the confirm dialog box
for ( int i = 0 ; i < tx . toAddrs . size ( ) ; i + + ) {
auto toAddr = tx . toAddrs [ i ] ;
// Construct the JSON params
QJsonObject rec ;
rec [ " address " ] = toAddr . addr ;
// Force it through string for rounding. Without this, decimal points beyond 8 places
// will appear, causing an "invalid amount" error
rec [ " amount " ] = Settings : : getDecimalString ( toAddr . amount ) ; //.toDouble();
if ( toAddr . addr . startsWith ( " z " ) & & ! toAddr . encodedMemo . trimmed ( ) . isEmpty ( ) )
rec [ " memo " ] = toAddr . encodedMemo ;
allRecepients . push_back ( rec ) ;
}
// Add sender
params . push_back ( tx . fromAddr ) ;
params . push_back ( allRecepients ) ;
// Add fees if custom fees are allowed.
if ( Settings : : getInstance ( ) - > getAllowCustomFees ( ) ) {
params . push_back ( 1 ) ; // minconf
params . push_back ( tx . fee ) ;
}
}
void RPC : : noConnection ( ) {
QIcon i = QApplication : : style ( ) - > standardIcon ( QStyle : : SP_MessageBoxCritical ) ;
main - > statusIcon - > setPixmap ( i . pixmap ( 16 , 16 ) ) ;
main - > statusIcon - > setToolTip ( " " ) ;
main - > statusLabel - > setText ( QObject : : tr ( " No Connection " ) ) ;
main - > statusLabel - > setToolTip ( " " ) ;
main - > ui - > statusBar - > showMessage ( QObject : : tr ( " No Connection " ) , 1000 ) ;
// Clear balances table.
QMap < QString , double > emptyBalances ;
QList < UnspentOutput > emptyOutputs ;
balancesTableModel - > setNewData ( & emptyBalances , & emptyOutputs ) ;
// Clear Transactions table.
QList < TransactionItem > emptyTxs ;
transactionsTableModel - > addTData ( emptyTxs ) ;
transactionsTableModel - > addZRecvData ( emptyTxs ) ;
transactionsTableModel - > addZSentData ( emptyTxs ) ;
// Clear balances
ui - > balSheilded - > setText ( " " ) ;
ui - > balTransparent - > setText ( " " ) ;
ui - > balTotal - > setText ( " " ) ;
ui - > balUSDTotal - > setText ( " " ) ;
ui - > balSheilded - > setToolTip ( " " ) ;
ui - > balTransparent - > setToolTip ( " " ) ;
ui - > balTotal - > setToolTip ( " " ) ;
// Clear send tab from address
ui - > inputsCombo - > clear ( ) ;
}
// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction
void RPC : : refreshReceivedZTrans ( QList < QString > zaddrs ) {
if ( conn = = nullptr )
return noConnection ( ) ;
// We'll only refresh the received Z txs if settings allows us.
if ( ! Settings : : getInstance ( ) - > getSaveZtxs ( ) ) {
QList < TransactionItem > emptylist ;
transactionsTableModel - > addZRecvData ( emptylist ) ;
return ;
}
// This method is complicated because z_listreceivedbyaddress only returns the txid, and
// we have to make a follow up call to gettransaction to get details of that transaction.
// Additionally, it has to be done in batches, because there are multiple z-Addresses,
// and each z-Addr can have multiple received txs.
// 1. For each z-Addr, get list of received txs
conn - > doBatchRPC < QString > ( zaddrs ,
[ = ] ( QString zaddr ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " z_lrba " } ,
{ " method " , " z_listreceivedbyaddress " } ,
{ " params " , QJsonArray { zaddr , 0 } } // Accept 0 conf as well.
} ;
return payload ;
} ,
[ = ] ( QMap < QString , QJsonValue > * zaddrTxids ) {
// Process all txids, removing duplicates. This can happen if the same address
// appears multiple times in a single tx's outputs.
QSet < QString > txids ;
QMap < QString , QString > memos ;
for ( auto it = zaddrTxids - > constBegin ( ) ; it ! = zaddrTxids - > constEnd ( ) ; it + + ) {
auto zaddr = it . key ( ) ;
for ( const auto & i : it . value ( ) . toArray ( ) ) {
// Mark the address as used
usedAddresses - > insert ( zaddr , true ) ;
// Filter out change txs
if ( ! i . toObject ( ) [ " change " ] . toBool ( ) ) {
auto txid = i . toObject ( ) [ " txid " ] . toString ( ) ;
txids . insert ( txid ) ;
// Check for Memos
QString memoBytes = i . toObject ( ) [ " memo " ] . toString ( ) ;
if ( ! memoBytes . startsWith ( " f600 " ) ) {
QString memo ( QByteArray : : fromHex (
i . toObject ( ) [ " memo " ] . toString ( ) . toUtf8 ( ) ) ) ;
if ( ! memo . trimmed ( ) . isEmpty ( ) )
memos [ zaddr + txid ] = memo ;
}
}
}
}
// 2. For all txids, go and get the details of that txid.
conn - > doBatchRPC < QString > ( txids . toList ( ) ,
[ = ] ( QString txid ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " gettx " } ,
{ " method " , " gettransaction " } ,
{ " params " , QJsonArray { txid } }
} ;
return payload ;
} ,
[ = ] ( QMap < QString , QJsonValue > * txidDetails ) {
QList < TransactionItem > txdata ;
// Combine them both together. For every zAddr's txid, get the amount, fee, confirmations and time
for ( auto it = zaddrTxids - > constBegin ( ) ; it ! = zaddrTxids - > constEnd ( ) ; it + + ) {
for ( const auto & i : it . value ( ) . toArray ( ) ) {
// Filter out change txs
if ( i . toObject ( ) [ " change " ] . toBool ( ) )
continue ;
auto zaddr = it . key ( ) ;
auto txid = i . toObject ( ) [ " txid " ] . toString ( ) ;
// Lookup txid in the map
auto txidInfo = txidDetails - > value ( txid ) ;
qint64 timestamp ;
if ( ! txidInfo . toObject ( ) [ " time " ] . isUndefined ( ) ) {
timestamp = txidInfo . toObject ( ) [ " time " ] . toInt ( ) ;
} else {
timestamp = txidInfo . toObject ( ) [ " blocktime " ] . toInt ( ) ;
}
auto amount = i . toObject ( ) [ " amount " ] . toDouble ( ) ;
auto confirmations = ( unsigned long ) txidInfo [ " confirmations " ] . toInt ( ) ;
TransactionItem tx { QString ( " receive " ) , timestamp , zaddr , txid , amount ,
confirmations , " " , memos . value ( zaddr + txid , " " ) } ;
txdata . push_front ( tx ) ;
}
}
transactionsTableModel - > addZRecvData ( txdata ) ;
// Cleanup both responses;
delete zaddrTxids ;
delete txidDetails ;
}
) ;
}
) ;
}
/// This will refresh all the balance data from hushd
void RPC : : refresh ( bool force ) {
if ( conn = = nullptr )
return noConnection ( ) ;
getInfoThenRefresh ( force ) ;
}
void RPC : : getInfoThenRefresh ( bool force ) {
//qDebug() << "getinfo";
if ( conn = = nullptr )
return noConnection ( ) ;
static bool prevCallSucceeded = false ;
QString method = " getinfo " ;
conn - > doRPC ( makePayload ( method ) , [ = ] ( const QJsonValue & reply ) {
prevCallSucceeded = true ;
// Testnet?
if ( ! reply [ " testnet " ] . isNull ( ) ) {
Settings : : getInstance ( ) - > setTestnet ( reply [ " testnet " ] . toBool ( ) ) ;
} ;
// TODO: checkmark only when getinfo.synced == true!
// Connected, so display checkmark.
QIcon i ( " :/icons/connected.gif " ) ;
main - > statusIcon - > setPixmap ( i . pixmap ( 16 , 16 ) ) ;
static int lastBlock = 0 ;
int curBlock = reply [ " blocks " ] . toInt ( ) ;
int longestchain = reply [ " longestchain " ] . toInt ( ) ;
int version = reply [ " version " ] . toInt ( ) ;
int p2pport = reply [ " p2pport " ] . toInt ( ) ;
int rpcport = reply [ " rpcport " ] . toInt ( ) ;
int protocolversion = reply [ " protocolversion " ] . toInt ( ) ;
// int notarized = reply["notarized"].toInt();
// int lag = curBlock - notarized;
// TODO: store all future halvings
int blocks_until_halving = 2020000 - curBlock ;
char halving_days [ 8 ] ;
int blocktime = 75 ; // seconds
if ( isdragonx ) {
blocks_until_halving = 3500000 - curBlock ;
blocktime = 60 ;
}
sprintf ( halving_days , " %.2f " , ( double ) ( blocks_until_halving * blocktime ) / ( 60 * 60 * 24 ) ) ;
QString ntzhash = reply [ " notarizedhash " ] . toString ( ) ;
QString ntztxid = reply [ " notarizedtxid " ] . toString ( ) ;
Settings : : getInstance ( ) - > setHushdVersion ( version ) ;
ui - > longestchain - > setText ( QString : : number ( longestchain ) ) ;
// ui->notarizedhashvalue->setText( ntzhash );
// ui->notarizedtxidvalue->setText( ntztxid );
// ui->lagvalue->setText( QString::number(lag) );
ui - > version - > setText ( QString : : number ( version ) ) ;
ui - > protocolversion - > setText ( QString : : number ( protocolversion ) ) ;
ui - > p2pport - > setText ( QString : : number ( p2pport ) ) ;
ui - > rpcport - > setText ( QString : : number ( rpcport ) ) ;
ui - > halving - > setText ( QString : : number ( blocks_until_halving ) % " blocks, " % QString : : fromStdString ( halving_days ) % " days " ) ;
if ( force | | ( curBlock ! = lastBlock ) ) {
// Something changed, so refresh everything.
lastBlock = curBlock ;
refreshBalances ( ) ;
refreshPeers ( ) ;
refreshAddresses ( ) ; // This calls refreshZSentTransactions() and refreshReceivedZTrans()
refreshTransactions ( ) ;
}
int connections = reply [ " connections " ] . toInt ( ) ;
bool hasTLS = ! reply [ " tls_connections " ] . isNull ( ) ;
qDebug ( ) < < " Local node TLS support = " < < hasTLS ;
int tlsconnections = hasTLS ? reply [ " tls_connections " ] . toInt ( ) : 0 ;
Settings : : getInstance ( ) - > setPeers ( connections ) ;
if ( connections = = 0 ) {
// If there are no peers connected, then the internet is probably off or something else is wrong.
QIcon i = QApplication : : style ( ) - > standardIcon ( QStyle : : SP_MessageBoxWarning ) ;
main - > statusIcon - > setPixmap ( i . pixmap ( 16 , 16 ) ) ;
}
ui - > numconnections - > setText ( QString : : number ( connections ) + " ( " + QString : : number ( tlsconnections ) + " TLS) " ) ;
ui - > tlssupport - > setText ( hasTLS ? " Yes " : " No " ) ;
/*
// Get network sol/s
QString method = " getnetworksolps " ;
conn - > doRPCIgnoreError ( makePayload ( method ) , [ = ] ( const QJsonValue & reply ) {
qint64 solrate = reply . toInt ( ) ;
//TODO: format decimal
if ( isdragonx ) {
ui - > solrate - > setText ( QString : : number ( ( double ) solrate ) % " Hash/s " ) ;
} else {
ui - > solrate - > setText ( QString : : number ( ( double ) solrate / 1000000 ) % " MegaSol/s " ) ;
}
// find a QLCDNumber child of tabWidget named networkhashrate
auto hashrate = ui - > tabWidget - > findChild < QLCDNumber * > ( " networkhashrate " ) ;
qDebug ( ) < < " solrate= " < < QString : : number ( solrate ) ;
if ( hashrate ! = nullptr ) {
hashrate - > display ( QString : : number ( solrate , ' f ' , 2 ) ) ;
} else {
qDebug ( ) < < " no widget named networkhashrate found " ;
}
// hashrate->display( QString::number( (double) solrate) );
} ) ;
// only look at local hashrate for dragonx
if ( isdragonx ) {
QString method = " getlocalsolps " ;
DEBUG ( " calling getlocalsolps " ) ;
conn - > doRPCIgnoreError ( makePayload ( method ) , [ = ] ( const QJsonValue & reply ) {
qDebug ( ) < < " reply= " < < reply ;
double solrate = reply . toDouble ( ) ;
DEBUG ( " solrate= " % QString : : number ( solrate ) ) ;
// find a QLCDNumber child of tabWidget named localhashrate
auto hashrate = ui - > tabWidget - > findChild < QLCDNumber * > ( " localhashrate " ) ;
if ( hashrate ! = nullptr ) {
hashrate - > display ( QString : : number ( ( double ) solrate , ' f ' , 2 ) ) ;
} else {
qDebug ( ) < < " no widget named localhashrate found " ;
}
} ) ;
}
*/
// Get mining info
// This is better+faster than calling multiple RPCs such as getlocalsolps/getnetworksolps/getgenerate
// and getlocalsolps returns non-zero values even when not mining, which is not what we want
conn - > doRPCIgnoreError ( makePayload ( " getmininginfo " ) , [ = ] ( const QJsonValue & reply ) {
QString localhashps = QString : : number ( reply [ " localhashps " ] . toDouble ( ) ) ;
QString networkhashps = QString : : number ( reply [ " networkhashps " ] . toDouble ( ) ) ;
QString generate = QString : : number ( reply [ " generate " ] . toBool ( ) ) ;
QString difficulty = QString : : number ( reply [ " difficulty " ] . toDouble ( ) ) ;
QString genproclimit = QString : : number ( reply [ " genproclimit " ] . toInt ( ) ) ;
// Update network hashrate in "Node Info" tab
if ( isdragonx ) {
ui - > solrate - > setText ( QString : : number ( networkhashps . toDouble ( ) ) % " Hash/s " ) ;
} else {
ui - > solrate - > setText ( QString : : number ( networkhashps . toDouble ( ) / 1000000 ) % " MegaSol/s " ) ;
// The rest of this callback is for DRAGONX
return ;
}
if ( genproclimit = = " -1 " ) {
// Showing users they are mining with -1 threads by default is confusing
genproclimit = QString : : number ( 0 ) ;
}
auto stopbutton = ui - > tabWidget - > findChild < QPushButton * > ( " stopmining " ) ;
auto startbutton = ui - > tabWidget - > findChild < QPushButton * > ( " startmining " ) ;
if ( stopbutton = = nullptr ) {
return ;
}
if ( startbutton = = nullptr ) {
return ;
}
if ( generate = = " 1 " ) {
// already mining
stopbutton - > setEnabled ( true ) ;
startbutton - > setEnabled ( false ) ;
DEBUG ( " enabled stop mining button, disabled start mining button " ) ;
} else {
// not yet mining
startbutton - > setEnabled ( true ) ;
stopbutton - > setEnabled ( false ) ;
DEBUG ( " enabled start mining button, disabled stop mining button " ) ;
}
// find a QLCDNumber child of tabWidget named localhashrate
auto localhashrate = ui - > tabWidget - > findChild < QLCDNumber * > ( " localhashrate " ) ;
if ( localhashrate ! = nullptr ) {
localhashrate - > display ( QString : : number ( localhashps . toDouble ( ) , ' f ' , 2 ) ) ;
} else {
qDebug ( ) < < " no widget named localhashrate found " ;
}
// find a QLCDNumber child of tabWidget named networkhashrate
auto nethashrate = ui - > tabWidget - > findChild < QLCDNumber * > ( " networkhashrate " ) ;
if ( nethashrate ! = nullptr ) {
nethashrate - > display ( QString : : number ( networkhashps . toInt ( ) ) ) ;
} else {
qDebug ( ) < < " no widget named networkhashrate found " ;
}
// find a QLCDNumber child of tabWidget named difficulty
auto diff = ui - > tabWidget - > findChild < QLCDNumber * > ( " difficulty " ) ;
if ( nethashrate ! = nullptr ) {
diff - > display ( QString : : number ( difficulty . toDouble ( ) , ' f ' , 2 ) ) ;
} else {
qDebug ( ) < < " no widget named difficulty found " ;
}
// find a QLCDNumber child of tabWidget named miningthreads
auto miningthreads = ui - > tabWidget - > findChild < QLCDNumber * > ( " miningthreads " ) ;
if ( miningthreads ! = nullptr ) {
miningthreads - > display ( QString : : number ( genproclimit . toInt ( ) ) ) ;
} else {
qDebug ( ) < < " no widget named difficulty found " ;
}
// find a QLCDNumber child of tabWidget named luck
auto luck = ui - > tabWidget - > findChild < QLCDNumber * > ( " luck " ) ;
if ( luck ! = nullptr ) {
if ( generate = = " 0 " ) {
// not mining, luck is not applicable
luck - > display ( QString ( " - " ) ) ;
} else {
// luck = current estimate of time to find a block given current localhash and nethash
//TODO: maybe use this as a tooltip
double percentOfNetHash = localhashps . toDouble ( ) / networkhashps . toDouble ( ) ;
DEBUG ( " % of nethash= " < < percentOfNetHash ) ;
if ( localhashps . toDouble ( ) > 0 ) {
//TODO: this is only for DRAGONX
int blocktime = 36 ;
double luckInSeconds = ( networkhashps . toDouble ( ) / localhashps . toDouble ( ) ) * blocktime ;
double luckInHours = luckInSeconds / ( 60 * 60 ) ;
luck - > display ( QString : : number ( luckInHours , ' f ' , 2 ) ) ;
}
}
} else {
qDebug ( ) < < " no widget named luck found " ;
}
} ) ;
// Get network info
conn - > doRPCIgnoreError ( makePayload ( " getnetworkinfo " ) , [ = ] ( const QJsonValue & reply ) {
QString clientname = reply [ " subversion " ] . toString ( ) ;
QString localservices = reply [ " localservices " ] . toString ( ) ;
ui - > clientname - > setText ( clientname ) ;
ui - > localservices - > setText ( localservices ) ;
} ) ;
conn - > doRPCIgnoreError ( makePayload ( " getwalletinfo " ) , [ = ] ( const QJsonValue & reply ) {
int txcount = reply [ " txcount " ] . toInt ( ) ;
ui - > txcount - > setText ( QString : : number ( txcount ) ) ;
} ) ;
//TODO: If -zindex is enabled, show stats
conn - > doRPCIgnoreError ( makePayload ( " getchaintxstats " ) , [ = ] ( const QJsonValue & reply ) {
int txcount = reply [ " txcount " ] . toInt ( ) ;
ui - > chaintxcount - > setText ( QString : : number ( txcount ) ) ;
} ) ;
// Call to see if the blockchain is syncing.
conn - > doRPCIgnoreError ( makePayload ( " getblockchaininfo " ) , [ = ] ( const QJsonValue & reply ) {
auto progress = reply [ " verificationprogress " ] . toDouble ( ) ;
// TODO: use getinfo.synced
bool isSyncing = progress < 0.9999 ; // 99.99%
int blockNumber = reply [ " blocks " ] . toInt ( ) ;
int estimatedheight = 0 ;
if ( ! reply . toObject ( ) [ " estimatedheight " ] . isUndefined ( ) ) {
estimatedheight = reply [ " estimatedheight " ] . toInt ( ) ;
}
auto s = Settings : : getInstance ( ) ;
s - > setSyncing ( isSyncing ) ;
s - > setBlockNumber ( blockNumber ) ;
QString ticker = s - > get_currency_name ( ) ;
// Update hushd tab
if ( isSyncing ) {
QString txt = QString : : number ( blockNumber ) ;
if ( estimatedheight > 0 ) {
txt = txt % " / ~ " % QString : : number ( estimatedheight ) ;
// If estimated height is available, then use the download blocks
// as the progress instead of verification progress.
progress = ( double ) blockNumber / ( double ) estimatedheight ;
}
txt = txt % " ( " % QString : : number ( progress * 100 , ' f ' , 2 ) % " % ) " ;
ui - > blockheight - > setText ( txt ) ;
ui - > heightLabel - > setText ( QObject : : tr ( " Downloading blocks " ) ) ;
} else {
ui - > blockheight - > setText ( QString : : number ( blockNumber ) ) ;
ui - > heightLabel - > setText ( QObject : : tr ( " Block height " ) ) ;
}
QString extra = " " ;
QString price = " " ;
// No price data for dragonx for now
if ( ! isdragonx ) {
auto ticker_price = s - > get_price ( ticker ) ;
if ( ticker_price > 0 & & ticker ! = " BTC " ) {
extra = QString : : number ( s - > getBTCPrice ( ) ) % " sat " ;
}
if ( ticker_price > 0 ) {
price = QString ( " , " ) % " HUSH " % " = " % QString : : number ( ( double ) ticker_price , ' f ' , 8 ) % " " % ticker % " " % extra ;
}
}
// Update the status bar
QString statusText = QString ( ) %
( isSyncing ? QObject : : tr ( " Syncing " ) : QObject : : tr ( " Connected " ) ) %
" ( " %
( s - > isTestnet ( ) ? QObject : : tr ( " testnet: " ) : " " ) %
QString : : number ( blockNumber ) %
( isSyncing ? ( " / " % QString : : number ( progress * 100 , ' f ' , 2 ) % " % " ) : QString ( ) ) %
" ) " ;
// % " Lag: " % QString::number(blockNumber - notarized) % price;
main - > statusLabel - > setText ( statusText ) ;
auto hushPrice = Settings : : getUSDFormat ( 1 ) ;
QString tooltip ;
if ( connections > 0 ) {
tooltip = QObject : : tr ( " Connected " ) ;
} else {
tooltip = QObject : : tr ( " No peer connections! Network issues? " ) ;
}
tooltip = tooltip % " (v " % QString : : number ( Settings : : getInstance ( ) - > getHushdVersion ( ) ) % " ) " ;
if ( ! isdragonx & & ! hushPrice . isEmpty ( ) ) {
tooltip = " 1 HUSH = " % hushPrice % " \n " % tooltip ;
}
main - > statusLabel - > setToolTip ( tooltip ) ;
main - > statusIcon - > setToolTip ( tooltip ) ;
} ) ;
} , [ = ] ( QNetworkReply * reply , const QJsonValue & ) {
// hushd has probably disappeared.
this - > noConnection ( ) ;
// Prevent multiple dialog boxes, because these are called async
static bool shown = false ;
if ( ! shown & & prevCallSucceeded ) { // show error only first time
shown = true ;
QMessageBox : : critical ( main , QObject : : tr ( " Connection Error " ) , QObject : : tr ( " There was an error connecting to hushd. The error was " ) + " : \n \n "
+ reply - > errorString ( ) , QMessageBox : : StandardButton : : Ok ) ;
shown = false ;
}
prevCallSucceeded = false ;
} ) ;
}
void RPC : : refreshAddresses ( ) {
if ( conn = = nullptr )
return noConnection ( ) ;
auto newzaddresses = new QList < QString > ( ) ;
getZAddresses ( [ = ] ( QJsonValue reply ) {
for ( const auto & it : reply . toArray ( ) ) {
auto addr = it . toString ( ) ;
newzaddresses - > push_back ( addr ) ;
}
delete zaddresses ;
zaddresses = newzaddresses ;
// Refresh the sent and received txs from all these z-addresses
refreshSentZTrans ( ) ;
refreshReceivedZTrans ( * zaddresses ) ;
} ) ;
auto newtaddresses = new QList < QString > ( ) ;
getTAddresses ( [ = ] ( QJsonValue reply ) {
for ( const auto & it : reply . toArray ( ) ) {
auto addr = it . toString ( ) ;
if ( Settings : : isTAddress ( addr ) )
newtaddresses - > push_back ( addr ) ;
}
delete taddresses ;
taddresses = newtaddresses ;
} ) ;
}
// Function to create the data model and update the views, used below.
void RPC : : updateUI ( bool anyUnconfirmed ) {
ui - > unconfirmedWarning - > setVisible ( anyUnconfirmed ) ;
// Update balances model data, which will update the table too
balancesTableModel - > setNewData ( allBalances , utxos ) ;
// Update from address
main - > updateFromCombo ( ) ;
} ;
// Function to process reply of the listunspent and z_listunspent API calls, used below.
bool RPC : : processUnspent ( const QJsonValue & reply , QMap < QString , double > * balancesMap , QList < UnspentOutput > * newUtxos ) {
bool anyUnconfirmed = false ;
for ( const auto & it : reply . toArray ( ) ) {
QString qsAddr = it . toObject ( ) [ " address " ] . toString ( ) ;
auto confirmations = it . toObject ( ) [ " confirmations " ] . toInt ( ) ;
if ( confirmations = = 0 ) {
anyUnconfirmed = true ;
}
newUtxos - > push_back (
UnspentOutput { qsAddr , it . toObject ( ) [ " txid " ] . toString ( ) ,
Settings : : getDecimalString ( it . toObject ( ) [ " amount " ] . toDouble ( ) ) ,
( int ) confirmations , it . toObject ( ) [ " spendable " ] . toBool ( ) } ) ;
( * balancesMap ) [ qsAddr ] = ( * balancesMap ) [ qsAddr ] + it . toObject ( ) [ " amount " ] . toDouble ( ) ;
}
return anyUnconfirmed ;
} ;
void RPC : : refreshBalances ( ) {
if ( conn = = nullptr )
return noConnection ( ) ;
// 1. Get the Balances
getBalance ( [ = ] ( QJsonValue reply ) {
auto balT = reply [ " transparent " ] . toString ( ) . toDouble ( ) ;
auto balZ = reply [ " private " ] . toString ( ) . toDouble ( ) ;
auto balTotal = reply [ " total " ] . toString ( ) . toDouble ( ) ;
ui - > balSheilded - > setText ( Settings : : getDisplayFormat ( balZ ) ) ;
ui - > balTransparent - > setText ( Settings : : getDisplayFormat ( balT ) ) ;
ui - > balTotal - > setText ( Settings : : getDisplayFormat ( balTotal ) ) ;
ui - > balSheilded - > setToolTip ( Settings : : getUSDFormat ( balZ ) ) ;
ui - > balTransparent - > setToolTip ( Settings : : getUSDFormat ( balT ) ) ;
ui - > balTotal - > setToolTip ( Settings : : getUSDFormat ( balTotal ) ) ;
ui - > balUSDTotal - > setText ( Settings : : getUSDFormat ( balTotal ) ) ;
ui - > balUSDTotal - > setToolTip ( Settings : : getUSDFormat ( balTotal ) ) ;
} ) ;
// 2. Get the UTXOs
// First, create a new UTXO list. It will be replacing the existing list when everything is processed.
auto newUtxos = new QList < UnspentOutput > ( ) ;
auto newBalances = new QMap < QString , double > ( ) ;
// Call the Transparent and Z unspent APIs serially and then, once they're done, update the UI
getTransparentUnspent ( [ = ] ( QJsonValue reply ) {
auto anyTUnconfirmed = processUnspent ( reply , newBalances , newUtxos ) ;
getZUnspent ( [ = ] ( QJsonValue reply ) {
auto anyZUnconfirmed = processUnspent ( reply , newBalances , newUtxos ) ;
// Swap out the balances and UTXOs
delete allBalances ;
delete utxos ;
allBalances = newBalances ;
utxos = newUtxos ;
updateUI ( anyTUnconfirmed | | anyZUnconfirmed ) ;
main - > balancesReady ( ) ;
} ) ;
} ) ;
}
void RPC : : refreshRescan ( ) {
qDebug ( ) < < __func__ ;
if ( conn = = nullptr ) {
qDebug ( ) < < __func__ < < " : no connection " ;
return noConnection ( ) ;
}
getRescanInfo ( [ = ] ( QJsonValue response ) {
qDebug ( ) < < " got getrescaninfo json= " < < response ;
auto rescanning = response . toObject ( ) . value ( " rescanning " ) . toBool ( ) ;
auto rescan_progress = response . toObject ( ) . value ( " rescan_progress " ) . toString ( ) ;
auto rescan_start_height = ( qint64 ) response . toObject ( ) . value ( " rescan_start_height " ) . toInt ( ) ;
auto rescan_height = ( qint64 ) response . toObject ( ) . value ( " rescan_height " ) . toInt ( ) ;
double percent = QString ( rescan_progress ) . toDouble ( ) * 100 ;
qDebug ( ) < < __func__ < < " : getrescaninfo " < < rescanning < < " " < < percent < < " " < < rescan_start_height < < " " < < rescan_height ;
if ( rescanning ) {
// pauseTimer();
qDebug ( ) < < __func__ < < " : Rescanning at " < < percent < < " e " ;
ui - > statusBar - > showMessage ( QObject : : tr ( " Rescanning... " ) + QString : : number ( percent ) + " % " + QObject : : tr ( " at height " ) + " " + QString : : number ( rescan_height ) ) ;
} else {
qDebug ( ) < < __func__ < < " : not rescanning " ;
}
} ) ;
}
void RPC : : refreshPeers ( ) {
qDebug ( ) < < __func__ ;
if ( conn = = nullptr )
return noConnection ( ) ;
/*
[
{
" address " : " 199.247.28.148/255.255.255.255 " ,
" banned_until " : 1612869516
} ,
{
" address " : " 2001:19f0:5001:d26:5400:3ff:fe18:f6c2/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff " ,
" banned_until " : 1612870431
}
]
*/
listBanned ( [ = ] ( QJsonValue reply ) {
QList < BannedPeerItem > peerdata ;
for ( const auto & it : reply . toArray ( ) ) {
auto addr = it . toObject ( ) [ " address " ] . toString ( ) ;
auto asn = ( qint64 ) it . toObject ( ) [ " mapped_as " ] . toInt ( ) ;
auto bantime = ( qint64 ) it . toObject ( ) [ " banned_until " ] . toInt ( ) ;
auto parts = addr . split ( " / " ) ;
auto ip = parts [ 0 ] ;
auto subnet = parts [ 1 ] ;
BannedPeerItem peer { ip , subnet , bantime , asn } ;
qDebug ( ) < < " Adding banned peer with address= " < < addr ;
peerdata . push_back ( peer ) ;
}
bannedPeersTableModel - > addData ( peerdata ) ;
} ) ;
getPeerInfo ( [ = ] ( QJsonValue reply ) {
QList < PeerItem > peerdata ;
for ( const auto & it : reply . toArray ( ) ) {
auto addr = it . toObject ( ) [ " addr " ] . toString ( ) ;
auto asn = ( qint64 ) it . toObject ( ) [ " mapped_as " ] . toInt ( ) ;
auto recv = ( qint64 ) it . toObject ( ) [ " bytesrecv " ] . toInt ( ) ;
auto sent = ( qint64 ) it . toObject ( ) [ " bytessent " ] . toInt ( ) ;
PeerItem peer {
it . toObject ( ) [ " id " ] . toInt ( ) , // peerid
" " , // type
( qint64 ) it . toObject ( ) [ " conntime " ] . toInt ( ) ,
addr ,
asn ,
it . toObject ( ) [ " tls_cipher " ] . toString ( ) ,
// don't convert the JS string "false" to a bool, which is true, lulz
it . toObject ( ) [ " tls_verified " ] . toString ( ) = = ' true ' ? true : false ,
( qint64 ) ( it . toObject ( ) [ " banscore " ] . toInt ( ) ) ,
( qint64 ) ( it . toObject ( ) [ " version " ] . toInt ( ) ) , // protocolversion
it . toObject ( ) [ " subver " ] . toString ( ) ,
recv ,
sent ,
it . toObject ( ) [ " pingtime " ] . toDouble ( ) ,
} ;
qDebug ( ) < < " Adding peer with address= " < < addr < < " and AS= " < < asn
< < " bytesrecv= " < < recv < < " bytessent= " < < sent ;
peerdata . push_back ( peer ) ;
}
//qDebug() << peerdata;
// Update model data, which updates the table view
peersTableModel - > addData ( peerdata ) ;
} ) ;
}
void RPC : : refreshTransactions ( ) {
if ( conn = = nullptr )
return noConnection ( ) ;
// Show statusBar message
ui - > statusBar - > showMessage ( QObject : : tr ( " Transaction data is loading... " ) ) ;
getTransactions ( [ = ] ( QJsonValue reply ) {
QList < TransactionItem > txdata ;
for ( const auto & it : reply . toArray ( ) ) {
double fee = 0 ;
if ( ! it . toObject ( ) [ " fee " ] . isNull ( ) ) {
fee = it . toObject ( ) [ " fee " ] . toDouble ( ) ;
}
QString address = ( it . toObject ( ) [ " address " ] . isNull ( ) ? " " : it . toObject ( ) [ " address " ] . toString ( ) ) ;
TransactionItem tx {
it . toObject ( ) [ " category " ] . toString ( ) ,
( qint64 ) it . toObject ( ) [ " time " ] . toInt ( ) ,
address ,
it . toObject ( ) [ " txid " ] . toString ( ) ,
it . toObject ( ) [ " amount " ] . toDouble ( ) + fee ,
( unsigned long ) it . toObject ( ) [ " confirmations " ] . toInt ( ) ,
" " , " " } ;
txdata . push_back ( tx ) ;
if ( ! address . isEmpty ( ) )
usedAddresses - > insert ( address , true ) ;
}
// Update model data, which updates the table view
qDebug ( ) < < " refreshTransactions " ;
transactionsTableModel - > addTData ( txdata ) ;
// Update statusBar message
ui - > statusBar - > showMessage ( QObject : : tr ( " Transaction data loaded " ) , 3 * 1000 ) ;
} ) ;
}
// Read sent Z transactions from the file.
void RPC : : refreshSentZTrans ( ) {
if ( conn = = nullptr )
return noConnection ( ) ;
auto sentZTxs = SentTxStore : : readSentTxFile ( ) ;
// If there are no sent z txs, then empty the table.
// This happens when you clear history.
if ( sentZTxs . isEmpty ( ) ) {
transactionsTableModel - > addZSentData ( sentZTxs ) ;
return ;
}
QList < QString > txids ;
for ( auto sentTx : sentZTxs ) {
txids . push_back ( sentTx . txid ) ;
}
// Look up all the txids to get the confirmation count for them.
conn - > doBatchRPC < QString > ( txids ,
[ = ] ( QString txid ) {
QJsonObject payload = {
{ " jsonrpc " , " 1.0 " } ,
{ " id " , " senttxid " } ,
{ " method " , " gettransaction " } ,
{ " params " , QJsonArray { txid } }
} ;
return payload ;
} ,
[ = ] ( QMap < QString , QJsonValue > * txidList ) {
auto newSentZTxs = sentZTxs ;
// Update the original sent list with the confirmation count
// TODO: This whole thing is kinda inefficient. We should probably just update the file
// with the confirmed block number, so we don't have to keep calling gettransaction for the
// sent items.
for ( TransactionItem & sentTx : newSentZTxs ) {
auto j = txidList - > value ( sentTx . txid ) ;
if ( j . isNull ( ) )
continue ;
auto error = j [ " confirmations " ] . isNull ( ) ;
if ( ! error )
sentTx . confirmations = j [ " confirmations " ] . toInt ( ) ;
}
transactionsTableModel - > addZSentData ( newSentZTxs ) ;
delete txidList ;
}
) ;
}
void RPC : : addNewTxToWatch ( const QString & newOpid , WatchedTx wtx ) {
watchingOps . insert ( newOpid , wtx ) ;
watchTxStatus ( ) ;
}
// Execute a transaction!
void RPC : : executeTransaction ( Tx tx ,
const std : : function < void ( QString opid ) > submitted ,
const std : : function < void ( QString opid , QString txid ) > computed ,
const std : : function < void ( QString opid , QString errStr ) > error ) {
// First, create the json params
QJsonArray params ;
fillTxJsonParams ( params , tx ) ;
//std::cout << std::setw(2) << params << std::endl;
sendZTransaction ( params , [ = ] ( const QJsonValue & reply ) {
QString opid = reply . toString ( ) ;
// And then start monitoring the transaction
addNewTxToWatch ( opid , WatchedTx { opid , tx , computed , error } ) ;
submitted ( opid ) ;
} ,
[ = ] ( QString errStr ) {
error ( " " , errStr ) ;
} ) ;
}
void RPC : : watchTxStatus ( ) {
if ( conn = = nullptr )
return noConnection ( ) ;
// Make an RPC to load pending operation statues
conn - > doRPCIgnoreError ( makePayload ( " z_getoperationstatus " ) , [ = ] ( const QJsonValue & reply ) {
// conn->doRPCIgnoreError(payload, [=] (const json& reply) {
// There's an array for each item in the status
for ( const auto & it : reply . toArray ( ) ) {
// If we were watching this Tx and its status became "success", then we'll show a status bar alert
QString id = it . toObject ( ) [ " id " ] . toString ( ) ;
if ( watchingOps . contains ( id ) ) {
// log any txs we are watching
// "creation_time": 1515969376,
// "execution_secs": 50.416337,
// And if it ended up successful
QString status = it . toObject ( ) [ " status " ] . toString ( ) ;
main - > loadingLabel - > setVisible ( false ) ;
if ( status = = " success " ) {
auto txid = it . toObject ( ) [ " result " ] . toObject ( ) [ " txid " ] . toString ( ) ;
SentTxStore : : addToSentTx ( watchingOps [ id ] . tx , txid ) ;
auto wtx = watchingOps [ id ] ;
watchingOps . remove ( id ) ;
wtx . completed ( id , txid ) ;
qDebug ( ) < < " opid " < < id < < " started at " < < QString : : number ( ( unsigned int ) it . toObject ( ) [ " creation_time " ] . toInt ( ) ) < < " took " < < QString : : number ( ( double ) it . toObject ( ) [ " execution_secs " ] . toDouble ( ) ) < < " seconds " ;
// Refresh balances to show unconfirmed balances
refresh ( true ) ;
} else if ( status = = " failed " ) {
// If it failed, then we'll actually show a warning.
auto errorMsg = it . toObject ( ) [ " error " ] . toObject ( ) [ " message " ] . toString ( ) ;
auto wtx = watchingOps [ id ] ;
watchingOps . remove ( id ) ;
wtx . error ( id , errorMsg ) ;
}
}
if ( watchingOps . isEmpty ( ) ) {
// Stop the timer
txTimer - > stop ( ) ;
} else {
// Keep polling for updates
txTimer - > start ( Settings : : quickUpdateSpeed ) ;
}
}
// If there is some op that we are watching, then show the loading bar, otherwise hide it
if ( watchingOps . empty ( ) ) {
main - > loadingLabel - > setVisible ( false ) ;
} else {
main - > loadingLabel - > setVisible ( true ) ;
main - > loadingLabel - > setToolTip ( QString : : number ( watchingOps . size ( ) ) + QObject : : tr ( " transaction computing. " ) ) ;
}
} ) ;
}
void RPC : : checkForUpdate ( bool silent ) {
// Disable update checks for now
// TODO: Use Gitea API
return ;
qDebug ( ) < < " checking for updates " ;
if ( conn = = nullptr )
return noConnection ( ) ;
QUrl cmcURL ( " https://git.hush.is/hush/SilentDragon/releases " ) ;
QNetworkRequest req ;
req . setUrl ( cmcURL ) ;
QNetworkReply * reply = conn - > restclient - > get ( req ) ;
QObject : : connect ( reply , & QNetworkReply : : finished , [ = ] {
reply - > deleteLater ( ) ;
try {
if ( reply - > error ( ) = = QNetworkReply : : NoError ) {
auto releases = QJsonDocument : : fromJson ( reply - > readAll ( ) ) . array ( ) ;
QVersionNumber maxVersion ( 0 , 0 , 0 ) ;
for ( QJsonValue rel : releases ) {
if ( ! rel . toObject ( ) . contains ( " tag_name " ) )
continue ;
QString tag = rel . toObject ( ) [ " tag_name " ] . toString ( ) ;
if ( tag . startsWith ( " v " ) )
tag = tag . right ( tag . length ( ) - 1 ) ;
if ( ! tag . isEmpty ( ) ) {
auto v = QVersionNumber : : fromString ( tag ) ;
if ( v > maxVersion )
maxVersion = v ;
}
}
auto currentVersion = QVersionNumber : : fromString ( APP_VERSION ) ;
// Get the max version that the user has hidden updates for
QSettings s ;
auto maxHiddenVersion = QVersionNumber : : fromString ( s . value ( " update/lastversion " , " 0.0.0 " ) . toString ( ) ) ;
qDebug ( ) < < " Version check: Current " < < currentVersion < < " , Available " < < maxVersion ;
if ( maxVersion > currentVersion & & ( ! silent | | maxVersion > maxHiddenVersion ) ) {
auto ans = QMessageBox : : information ( main , QObject : : tr ( " Update Available " ) ,
QObject : : tr ( " A new release v%1 is available! You have v%2. \n \n Would you like to visit the releases page? " )
. arg ( maxVersion . toString ( ) )
. arg ( currentVersion . toString ( ) ) ,
QMessageBox : : Yes , QMessageBox : : Cancel ) ;
if ( ans = = QMessageBox : : Yes ) {
QDesktopServices : : openUrl ( QUrl ( " https://git.hush.is/hush/SilentDragon/releases " ) ) ;
} else {
// If the user selects cancel, don't bother them again for this version
s . setValue ( " update/lastversion " , maxVersion . toString ( ) ) ;
}
} else {
if ( ! silent ) {
QMessageBox : : information ( main , QObject : : tr ( " No updates available " ) ,
QObject : : tr ( " You already have the latest release v%1 " )
. arg ( currentVersion . toString ( ) ) ) ;
}
}
}
} catch ( const std : : exception & e ) {
// If anything at all goes wrong, move on
qDebug ( ) < < QString ( " Exception checking for updates! " ) ;
}
} ) ;
}
// Get the HUSH prices
void RPC : : refreshPrice ( ) {
if ( conn = = nullptr )
return noConnection ( ) ;
if ( isdragonx ) {
return ;
}
auto s = Settings : : getInstance ( ) ;
if ( s - > getAllowFetchPrices ( ) = = false ) {
qDebug ( ) < < " Price fetching disabled " ;
return ;
}
QString price_feed = " https://api.coingecko.com/api/v3/simple/price?ids=hush&vs_currencies=btc%2Cusd%2Ceur%2Ceth%2Cgbp%2Ccny%2Cjpy%2Cidr%2Crub%2Ccad%2Csgd%2Cchf%2Cinr%2Caud%2Cinr%2Ckrw%2Cthb%2Cnzd%2Czar%2Cvef%2Cxau%2Cxag%2Cvnd%2Csar%2Ctwd%2Caed%2Cars%2Cbdt%2Cbhd%2Cbmd%2Cbrl%2Cclp%2Cczk%2Cdkk%2Chuf%2Cils%2Ckwd%2Clkr%2Cpkr%2Cnok%2Ctry%2Csek%2Cmxn%2Cuah%2Chkd&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true " ;
qDebug ( ) < < " Requesting price feed data via " < < price_feed ;
QUrl cmcURL ( price_feed ) ;
QNetworkRequest req ;
req . setUrl ( cmcURL ) ;
qDebug ( ) < < " Created price request object " ;
QNetworkReply * reply = conn - > restclient - > get ( req ) ;
qDebug ( ) < < " Created QNetworkReply " ;
QObject : : connect ( reply , & QNetworkReply : : finished , [ = ] {
reply - > deleteLater ( ) ;
try {
QByteArray ba_raw_reply = reply - > readAll ( ) ;
QString raw_reply = QString : : fromUtf8 ( ba_raw_reply ) ;
QByteArray unescaped_raw_reply = raw_reply . toUtf8 ( ) ;
QJsonDocument jd_reply = QJsonDocument : : fromJson ( unescaped_raw_reply ) ;
QJsonObject parsed = jd_reply . object ( ) ;
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
qDebug ( ) < < " Parsing price feed response " ;
if ( ! parsed . isEmpty ( ) & & ! parsed [ " error " ] . toObject ( ) [ " message " ] . isNull ( ) ) {
qDebug ( ) < < parsed [ " error " ] . toObject ( ) [ " message " ] . toString ( ) ;
} else {
qDebug ( ) < < reply - > errorString ( ) ;
}
s - > setHUSHPrice ( 0 ) ;
s - > setBTCPrice ( 0 ) ;
return ;
}
qDebug ( ) < < " No network errors " ;
if ( parsed . isEmpty ( ) ) {
s - > setHUSHPrice ( 0 ) ;
s - > setBTCPrice ( 0 ) ;
return ;
}
qDebug ( ) < < " Parsed JSON " ;
const QJsonValue & item = parsed ;
const QJsonValue & hush = item [ " hush " ] . toObject ( ) ;
QString ticker = s - > get_currency_name ( ) ;
ticker = ticker . toLower ( ) ;
fprintf ( stderr , " ticker=%s \n " , ticker . toLocal8Bit ( ) . data ( ) ) ;
//qDebug() << "Ticker = " + ticker;
if ( ! hush [ ticker ] . isUndefined ( ) ) {
qDebug ( ) < < " Found hush key in price json " ;
//QString price = hush["usd"].toString());
qDebug ( ) < < " HUSH = $ " < < QString : : number ( hush [ " usd " ] . toDouble ( ) ) < < " USD " ;
qDebug ( ) < < " HUSH = " < < QString : : number ( hush [ " eur " ] . toDouble ( ) ) < < " EUR " ;
qDebug ( ) < < " HUSH = " < < QString : : number ( ( int ) 100000000 * hush [ " btc " ] . toDouble ( ) ) < < " sat " ;
s - > setHUSHPrice ( hush [ ticker ] . toDouble ( ) ) ;
s - > setBTCPrice ( ( unsigned int ) 100000000 * hush [ " btc " ] . toDouble ( ) ) ;
ticker = ticker . toLower ( ) ;
qDebug ( ) < < " ticker= " < < ticker ;
// TODO: work harder to prevent coredumps!
auto price = hush [ ticker ] . toDouble ( ) ;
auto vol = hush [ ticker + " _24h_vol " ] . toDouble ( ) ;
auto mcap = hush [ ticker + " _market_cap " ] . toDouble ( ) ;
//auto btcprice = hush["btc"].toDouble();
auto btcvol = hush [ " btc_24h_vol " ] . toDouble ( ) ;
auto btcmcap = hush [ " btc_market_cap " ] . toDouble ( ) ;
s - > set_price ( ticker , price ) ;
s - > set_volume ( ticker , vol ) ;
s - > set_volume ( " BTC " , btcvol ) ;
s - > set_marketcap ( ticker , mcap ) ;
qDebug ( ) < < " Volume = " < < ( double ) vol ;
ticker = ticker . toUpper ( ) ;
ui - > volume - > setText ( QString : : number ( ( double ) vol , ' f ' , 2 ) + " " + ticker ) ;
ui - > volumeBTC - > setText ( QString : : number ( ( double ) btcvol , ' f ' , 2 ) + " BTC " ) ;
ticker = ticker . toUpper ( ) ;
// We don't get an actual HUSH volume stat, so we calculate it
if ( price > 0 )
ui - > volumeLocal - > setText ( QString : : number ( ( double ) vol / ( double ) price ) + " HUSH " ) ;
qDebug ( ) < < " Mcap = " < < ( double ) mcap ;
ui - > marketcap - > setText ( QString : : number ( ( double ) mcap , ' f ' , 2 ) + " " + ticker ) ;
ui - > marketcapBTC - > setText ( QString : : number ( ( double ) btcmcap , ' f ' , 2 ) + " BTC " ) ;
//ui->marketcapLocal->setText( QString::number((double) mcap * (double) price) + " " + ticker );
refresh ( true ) ;
return ;
} else {
qDebug ( ) < < " No hush key found in JSON! API might be down or we are rate-limited \n " ;
}
} catch ( const std : : exception & e ) {
// If anything at all goes wrong, just set the price to 0 and move on.
qDebug ( ) < < QString ( " Price feed update failure : " ) ;
}
// If nothing, then set the price to 0;
Settings : : getInstance ( ) - > setHUSHPrice ( 0 ) ;
Settings : : getInstance ( ) - > setBTCPrice ( 0 ) ;
} ) ;
}
void RPC : : shutdownHushd ( ) {
// Shutdown embedded hushd if it was started
if ( ehushd = = nullptr | | ehushd - > processId ( ) = = 0 | | conn = = nullptr ) {
// No hushd running internally, just return
return ;
}
QString method = " stop " ;
conn - > doRPCWithDefaultErrorHandling ( makePayload ( method ) , [ = ] ( auto ) { } ) ;
conn - > shutdown ( ) ;
QDialog d ( main ) ;
d . setWindowFlags ( d . windowFlags ( ) & ~ ( Qt : : WindowCloseButtonHint | Qt : : WindowContextHelpButtonHint ) ) ;
Ui_ConnectionDialog connD ;
connD . setupUi ( & d ) ;
//connD.topIcon->setBasePixmap(QIcon(":/icons/icon.ico").pixmap(256, 256));
if ( isdragonx ) {
d . setWindowTitle ( " SilentDragonX " ) ;
}
QMovie * movie1 = new QMovie ( " :/img/silentdragon-animated-startup-dark.gif " ) ; ;
auto theme = Settings : : getInstance ( ) - > get_theme_name ( ) ;
movie1 - > setScaledSize ( QSize ( 512 , 512 ) ) ;
connD . topIcon - > setMovie ( movie1 ) ;
movie1 - > start ( ) ;
if ( isdragonx ) {
connD . status - > setText ( QObject : : tr ( " Please enhance your calm and wait for SilentDragonX to exit " ) ) ;
connD . statusDetail - > setText ( QObject : : tr ( " Waiting for dragonxd to exit, y'all " ) ) ;
} else {
connD . status - > setText ( QObject : : tr ( " Please enhance your calm and wait for SilentDragon to exit " ) ) ;
connD . statusDetail - > setText ( QObject : : tr ( " Waiting for hushd to exit, y'all " ) ) ;
}
QTimer waiter ( main ) ;
// We capture by reference all the local variables because of the d.exec()
// below, which blocks this function until we exit.
int waitCount = 0 ;
QObject : : connect ( & waiter , & QTimer : : timeout , [ & ] ( ) {
waitCount + + ;
if ( ( ehushd - > atEnd ( ) & & ehushd - > processId ( ) = = 0 ) | |
ehushd - > state ( ) = = QProcess : : NotRunning | |
waitCount > 30 | |
conn - > config - > hushDaemon ) { // If hushd is daemon, then we don't have to do anything else
qDebug ( ) < < " Ended " ;
waiter . stop ( ) ;
QTimer : : singleShot ( 1000 , [ & ] ( ) { d . accept ( ) ; } ) ;
} else {
qDebug ( ) < < " Not ended, continuing to wait... " ;
}
} ) ;
waiter . start ( 1000 ) ;
// Wait for the hush process to exit.
if ( ! Settings : : getInstance ( ) - > isHeadless ( ) ) {
d . exec ( ) ;
} else {
while ( waiter . isActive ( ) ) {
QCoreApplication : : processEvents ( ) ;
QThread : : sleep ( 1 ) ;
}
}
}
/**
* Get a Sapling address from the user ' s wallet
*/
QString RPC : : getDefaultSaplingAddress ( ) {
for ( QString addr : * zaddresses ) {
if ( Settings : : getInstance ( ) - > isSaplingAddress ( addr ) )
return addr ;
}
return QString ( ) ;
}
QString RPC : : getDefaultTAddress ( ) {
if ( getAllTAddresses ( ) - > length ( ) > 0 )
return getAllTAddresses ( ) - > at ( 0 ) ;
else
return QString ( ) ;
}