diff options
35 files changed, 1176 insertions, 138 deletions
diff --git a/doc/gitian-building.md b/doc/gitian-building.md index 544bbc12c1..378a45eda5 100644 --- a/doc/gitian-building.md +++ b/doc/gitian-building.md @@ -34,19 +34,22 @@ Table of Contents - [Signing externally](#signing-externally) - [Uploading signatures](#uploading-signatures) -Create a new VirtualBox VM ---------------------------- +Preparing the Gitian builder host +--------------------------------- -The first step is to create a new Virtual Machine, which will be explained in -this section. This VM will be used to do the Gitian builds. In this guide it -will be explained how to set up the environment, and how to get the builds -started. +The first step is to prepare the host environment that will be used to perform the Gitian builds. +In this guide it is explained how to set up the environment, and how to get the builds started. -Debian Linux was chosen as the host distribution because it has a lightweight install (in -contrast to Ubuntu) and is readily available. We here show the steps for -VirtualBox [1], but any kind of virtualization can be used. You can also install -on actual hardware instead of using a VM, in this case you can skip this section. +Debian Linux was chosen as the host distribution because it has a lightweight install (in contrast to Ubuntu) and is readily available. +Any kind of virtualization can be used, for example: +- [VirtualBox](https://www.virtualbox.org/), covered by this guide +- [KVM](http://www.linux-kvm.org/page/Main_Page) +- [LXC](https://linuxcontainers.org/), see also [Gitian host docker container](https://github.com/gdm85/tenku/tree/master/docker/gitian-bitcoin-host/README.md). +You can also install on actual hardware instead of using virtualization. + +Create a new VirtualBox VM +--------------------------- In the VirtualBox GUI click "Create" and choose the following parameters in the wizard: ![](gitian-building/create_vm_page1.png) @@ -74,11 +77,11 @@ In the VirtualBox GUI click "Create" and choose the following parameters in the - Disk size: at least 40GB; as low as 20GB *may* be possible, but better to err on the safe side - Push the `Create` button -Get the [Debian 7.4 net installer](http://cdimage.debian.org/debian-cd/7.4.0/amd64/iso-cd/debian-7.4.0-amd64-netinst.iso). +Get the [Debian 7.4 net installer](http://ftp.at.debian.org/debian-jigdo/current/amd64/iso-cd/debian-7.4.0-amd64-netinst.iso) (a more recent minor version should also work, see also [Debian Network installation](https://www.debian.org/CD/netinst/)). This DVD image can be validated using a SHA256 hashing tool, for example on Unixy OSes by entering the following in a terminal: - echo "b712a141bc60269db217d3b3e456179bd6b181645f90e4aac9c42ed63de492e9 /home/orion/Downloads/debian-7.4.0-amd64-netinst.iso" | sha256sum -c + echo "b712a141bc60269db217d3b3e456179bd6b181645f90e4aac9c42ed63de492e9 debian-7.4.0-amd64-netinst.iso" | sha256sum -c # (must return OK) After creating the VM, we need to configure it. @@ -106,8 +109,6 @@ Then start the VM. On the first launch you will be asked for a CD or DVD image. ![](gitian-building/select_startup_disk.png) -[1] https://www.virtualbox.org/ - Installing Debian ------------------ @@ -279,11 +280,14 @@ cd .. **Note**: When sudo asks for a password, enter the password for the user *debian* not for *root*. -Clone the git repositories for bitcoin and gitian, +Clone the git repositories for bitcoin and gitian and then checkout the bitcoin version that you are willing to build. ```bash git clone https://github.com/devrandom/gitian-builder.git git clone https://github.com/bitcoin/bitcoin +cd bitcoin +git checkout v${VERSION} +cd .. ``` Setting up gitian images @@ -402,9 +406,6 @@ gitian build. Uploading signatures --------------------- -After building and signing you can push your signatures (both the `.assert` and -`.assert.sig` files) to the -[bitcoin/gitian.sigs](https://github.com/bitcoin/gitian.sigs/) repository, or -if not possible create a pull request. You can also mail the files to me -(laanwj@gmail.com) and I'll commit them. - +After building and signing you can push your signatures (both the `.assert` and `.assert.sig` files) to the +[bitcoin/gitian.sigs](https://github.com/bitcoin/gitian.sigs/) repository, or if not possible create a pull +request. You can also mail the files to me (laanwj@gmail.com) and I'll commit them. diff --git a/src/init.cpp b/src/init.cpp index bc4924b48d..6a21dee634 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -206,6 +206,7 @@ std::string HelpMessage(HelpMessageMode hmm) strUsage += " -dbcache=<n> " + strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache) + "\n"; strUsage += " -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n"; strUsage += " -loadblock=<file> " + _("Imports blocks from external blk000??.dat file") + " " + _("on startup") + "\n"; + strUsage += " -maxorphanblocks=<n> " + strprintf(_("Keep at most <n> unconnectable blocks in memory (default: %u)"), DEFAULT_MAX_ORPHAN_BLOCKS) + "\n"; strUsage += " -par=<n> " + strprintf(_("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)"), -(int)boost::thread::hardware_concurrency(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS) + "\n"; strUsage += " -pid=<file> " + _("Specify pid file (default: bitcoind.pid)") + "\n"; strUsage += " -reindex " + _("Rebuild block chain index from current blk000??.dat files") + " " + _("on startup") + "\n"; @@ -732,12 +733,12 @@ bool AppInit2(boost::thread_group& threadGroup) } // see Step 2: parameter interactions for more information about these - fNoListen = !GetBoolArg("-listen", true); + fListen = GetBoolArg("-listen", true); fDiscover = GetBoolArg("-discover", true); fNameLookup = GetBoolArg("-dns", true); bool fBound = false; - if (!fNoListen) { + if (fListen) { if (mapArgs.count("-bind")) { BOOST_FOREACH(std::string strBind, mapMultiArgs["-bind"]) { CService addrBind; @@ -858,7 +859,7 @@ bool AppInit2(boost::thread_group& threadGroup) } uiInterface.InitMessage(_("Verifying blocks...")); - if (!VerifyDB(GetArg("-checklevel", 3), + if (!CVerifyDB().VerifyDB(GetArg("-checklevel", 3), GetArg("-checkblocks", 288))) { strLoadError = _("Corrupted block database detected"); break; diff --git a/src/main.cpp b/src/main.cpp index 18c00d90ac..fa47bf7f08 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1160,7 +1160,7 @@ uint256 static GetOrphanRoot(const uint256& hash) // Remove a random orphan block (which does not have any dependent orphans). void static PruneOrphanBlocks() { - if (mapOrphanBlocksByPrev.size() <= MAX_ORPHAN_BLOCKS) + if (mapOrphanBlocksByPrev.size() <= (size_t)std::max((int64_t)0, GetArg("-maxorphanblocks", DEFAULT_MAX_ORPHAN_BLOCKS))) return; // Pick a random orphan block. @@ -2968,7 +2968,17 @@ bool static LoadBlockIndexDB() return true; } -bool VerifyDB(int nCheckLevel, int nCheckDepth) +CVerifyDB::CVerifyDB() +{ + uiInterface.ShowProgress(_("Verifying blocks..."), 0); +} + +CVerifyDB::~CVerifyDB() +{ + uiInterface.ShowProgress("", 100); +} + +bool CVerifyDB::VerifyDB(int nCheckLevel, int nCheckDepth) { LOCK(cs_main); if (chainActive.Tip() == NULL || chainActive.Tip()->pprev == NULL) @@ -2989,6 +2999,7 @@ bool VerifyDB(int nCheckLevel, int nCheckDepth) for (CBlockIndex* pindex = chainActive.Tip(); pindex && pindex->pprev; pindex = pindex->pprev) { boost::this_thread::interruption_point(); + uiInterface.ShowProgress(_("Verifying blocks..."), std::max(1, std::min(99, (int)(((double)(chainActive.Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100))))); if (pindex->nHeight < chainActive.Height()-nCheckDepth) break; CBlock block; @@ -3028,6 +3039,7 @@ bool VerifyDB(int nCheckLevel, int nCheckDepth) CBlockIndex *pindex = pindexState; while (pindex != chainActive.Tip()) { boost::this_thread::interruption_point(); + uiInterface.ShowProgress(_("Verifying blocks..."), std::max(1, std::min(99, 100 - (int)(((double)(chainActive.Height() - pindex->nHeight)) / (double)nCheckDepth * 50)))); pindex = chainActive.Next(pindex); CBlock block; if (!ReadBlockFromDisk(block, pindex)) @@ -3550,7 +3562,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!pfrom->fInbound) { // Advertise our address - if (!fNoListen && !IsInitialBlockDownload()) + if (fListen && !IsInitialBlockDownload()) { CAddress addr = GetLocalAddress(&pfrom->addr); if (addr.IsRoutable()) @@ -4318,7 +4330,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle) pnode->setAddrKnown.clear(); // Rebroadcast our address - if (!fNoListen) + if (fListen) { CAddress addr = GetLocalAddress(&pnode->addr); if (addr.IsRoutable()) diff --git a/src/main.h b/src/main.h index 8a05eb60d2..3fccd32a29 100644 --- a/src/main.h +++ b/src/main.h @@ -45,8 +45,8 @@ static const unsigned int MAX_STANDARD_TX_SIZE = 100000; static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50; /** The maximum number of orphan transactions kept in memory */ static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100; -/** The maximum number of orphan blocks kept in memory */ -static const unsigned int MAX_ORPHAN_BLOCKS = 750; +/** Default for -maxorphanblocks, maximum number of orphan blocks kept in memory */ +static const unsigned int DEFAULT_MAX_ORPHAN_BLOCKS = 750; /** The maximum size of a blk?????.dat file (since 0.8) */ static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB /** The pre-allocation chunk size for blk?????.dat files (since 0.8) */ @@ -144,8 +144,6 @@ bool InitBlockIndex(); bool LoadBlockIndex(); /** Unload database information */ void UnloadBlockIndex(); -/** Verify consistency of the block and coin databases */ -bool VerifyDB(int nCheckLevel, int nCheckDepth); /** Print the loaded block tree */ void PrintBlockTree(); /** Process protocol messages received from a given node */ @@ -1024,6 +1022,15 @@ public: std::string GetRejectReason() const { return strRejectReason; } }; +/** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */ +class CVerifyDB { +public: + + CVerifyDB(); + ~CVerifyDB(); + bool VerifyDB(int nCheckLevel, int nCheckDepth); +}; + /** An in-memory indexed chain of blocks. */ class CChain { private: diff --git a/src/net.cpp b/src/net.cpp index c2dde97040..479f77c469 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -45,6 +45,7 @@ static const int MAX_OUTBOUND_CONNECTIONS = 8; // Global state variables // bool fDiscover = true; +bool fListen = true; uint64_t nLocalServices = NODE_NETWORK; CCriticalSection cs_mapLocalHost; map<CNetAddr, LocalServiceInfo> mapLocalHost; @@ -96,7 +97,7 @@ unsigned short GetListenPort() // find 'best' local address for a particular peer bool GetLocal(CService& addr, const CNetAddr *paddrPeer) { - if (fNoListen) + if (!fListen) return false; int nBestScore = -1; @@ -1455,13 +1456,13 @@ bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOu // for now, use a very simple selection metric: the node from which we received // most recently -double static NodeSyncScore(const CNode *pnode) { - return -pnode->nLastRecv; +static int64_t NodeSyncScore(const CNode *pnode) { + return pnode->nLastRecv; } void static StartSync(const vector<CNode*> &vNodes) { CNode *pnodeNewSync = NULL; - double dBestScore = 0; + int64_t nBestScore = 0; int nBestHeight = g_signals.GetHeight().get_value_or(0); @@ -1473,10 +1474,10 @@ void static StartSync(const vector<CNode*> &vNodes) { (pnode->nStartingHeight > (nBestHeight - 144)) && (pnode->nVersion < NOBLKS_VERSION_START || pnode->nVersion >= NOBLKS_VERSION_END)) { // if ok, compare node's score with the best so far - double dScore = NodeSyncScore(pnode); - if (pnodeNewSync == NULL || dScore > dBestScore) { + int64_t nScore = NodeSyncScore(pnode); + if (pnodeNewSync == NULL || nScore > nBestScore) { pnodeNewSync = pnode; - dBestScore = dScore; + nBestScore = nScore; } } } @@ -105,6 +105,7 @@ CAddress GetLocalAddress(const CNetAddr *paddrPeer = NULL); extern bool fDiscover; +extern bool fListen; extern uint64_t nLocalServices; extern uint64_t nLocalHostNonce; extern CAddrMan addrman; diff --git a/src/qt/Makefile.am b/src/qt/Makefile.am index 648971bd8f..1d85113d78 100644 --- a/src/qt/Makefile.am +++ b/src/qt/Makefile.am @@ -126,6 +126,7 @@ QT_MOC_CPP = \ moc_optionsdialog.cpp \ moc_optionsmodel.cpp \ moc_overviewpage.cpp \ + moc_peertablemodel.cpp \ moc_paymentserver.cpp \ moc_qvalidatedlineedit.cpp \ moc_qvaluecombobox.cpp \ @@ -191,6 +192,7 @@ BITCOIN_QT_H = \ overviewpage.h \ paymentrequestplus.h \ paymentserver.h \ + peertablemodel.h \ qvalidatedlineedit.h \ qvaluecombobox.h \ receivecoinsdialog.h \ @@ -294,6 +296,7 @@ BITCOIN_QT_CPP += \ overviewpage.cpp \ paymentrequestplus.cpp \ paymentserver.cpp \ + peertablemodel.cpp \ receivecoinsdialog.cpp \ receiverequestdialog.cpp \ recentrequeststablemodel.cpp \ diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 68ae8b4668..3469f990ac 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -40,6 +40,7 @@ #include <QMessageBox> #include <QMimeData> #include <QProgressBar> +#include <QProgressDialog> #include <QSettings> #include <QStackedWidget> #include <QStatusBar> @@ -409,6 +410,9 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) // Receive and report messages from client model connect(clientModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int))); + // Show progress dialog + connect(clientModel, SIGNAL(showProgress(QString,int)), this, SLOT(showProgress(QString,int))); + rpcConsole->setClientModel(clientModel); #ifdef ENABLE_WALLET if(walletFrame) @@ -949,6 +953,29 @@ void BitcoinGUI::detectShutdown() } } +void BitcoinGUI::showProgress(const QString &title, int nProgress) +{ + if (nProgress == 0) + { + progressDialog = new QProgressDialog(title, "", 0, 100); + progressDialog->setWindowModality(Qt::ApplicationModal); + progressDialog->setMinimumDuration(0); + progressDialog->setCancelButton(0); + progressDialog->setAutoClose(false); + progressDialog->setValue(0); + } + else if (nProgress == 100) + { + if (progressDialog) + { + progressDialog->close(); + progressDialog->deleteLater(); + } + } + else if (progressDialog) + progressDialog->setValue(nProgress); +} + static bool ThreadSafeMessageBox(BitcoinGUI *gui, const std::string& message, const std::string& caption, unsigned int style) { bool modal = (style & CClientUIInterface::MODAL); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index b4675b95ac..275fa35f39 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -26,6 +26,7 @@ QT_BEGIN_NAMESPACE class QAction; class QLabel; class QProgressBar; +class QProgressDialog; QT_END_NAMESPACE /** @@ -73,6 +74,7 @@ private: QLabel *labelBlocksIcon; QLabel *progressBarLabel; QProgressBar *progressBar; + QProgressDialog *progressDialog; QMenuBar *appMenuBar; QAction *overviewAction; @@ -191,6 +193,9 @@ private slots: /** called by a timer to check if fRequestShutdown has been set **/ void detectShutdown(); + + /** Show progress dialog e.g. for verifychain */ + void showProgress(const QString &title, int nProgress); }; #endif // BITCOINGUI_H diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index d1f68ebd22..403b03378f 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -5,6 +5,7 @@ #include "clientmodel.h" #include "guiconstants.h" +#include "peertablemodel.h" #include "alert.h" #include "chainparams.h" @@ -22,11 +23,14 @@ static const int64_t nClientStartupTime = GetTime(); ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) : - QObject(parent), optionsModel(optionsModel), + QObject(parent), + optionsModel(optionsModel), + peerTableModel(0), cachedNumBlocks(0), cachedReindexing(0), cachedImporting(0), numBlocksAtStartup(-1), pollTimer(0) { + peerTableModel = new PeerTableModel(this); pollTimer = new QTimer(this); connect(pollTimer, SIGNAL(timeout()), this, SLOT(updateTimer())); pollTimer->start(MODEL_UPDATE_DELAY); @@ -173,6 +177,11 @@ OptionsModel *ClientModel::getOptionsModel() return optionsModel; } +PeerTableModel *ClientModel::getPeerTableModel() +{ + return peerTableModel; +} + QString ClientModel::formatFullVersion() const { return QString::fromStdString(FormatFullVersion()); @@ -199,6 +208,14 @@ QString ClientModel::formatClientStartupTime() const } // Handlers for core signals +static void ShowProgress(ClientModel *clientmodel, const std::string &title, int nProgress) +{ + // emits signal "showProgress" + QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(title)), + Q_ARG(int, nProgress)); +} + static void NotifyBlocksChanged(ClientModel *clientmodel) { // This notification is too frequent. Don't trigger a signal. @@ -223,6 +240,7 @@ static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, Ch void ClientModel::subscribeToCoreSignals() { // Connect signals to client + uiInterface.ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); uiInterface.NotifyBlocksChanged.connect(boost::bind(NotifyBlocksChanged, this)); uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1)); uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this, _1, _2)); @@ -231,6 +249,7 @@ void ClientModel::subscribeToCoreSignals() void ClientModel::unsubscribeFromCoreSignals() { // Disconnect signals from client + uiInterface.ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); uiInterface.NotifyBlocksChanged.disconnect(boost::bind(NotifyBlocksChanged, this)); uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1)); uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this, _1, _2)); diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index cab853d92d..9c9a35b654 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -9,6 +9,7 @@ class AddressTableModel; class OptionsModel; +class PeerTableModel; class TransactionTableModel; class CWallet; @@ -42,6 +43,7 @@ public: ~ClientModel(); OptionsModel *getOptionsModel(); + PeerTableModel *getPeerTableModel(); //! Return number of connections, default is in- and outbound (total) int getNumConnections(unsigned int flags = CONNECTIONS_ALL) const; @@ -71,6 +73,7 @@ public: private: OptionsModel *optionsModel; + PeerTableModel *peerTableModel; int cachedNumBlocks; bool cachedReindexing; @@ -92,6 +95,9 @@ signals: //! Fired when a message should be reported to the user void message(const QString &title, const QString &message, unsigned int style); + // Show progress dialog e.g. for verifychain + void showProgress(const QString &title, int nProgress); + public slots: void updateTimer(); void updateNumConnections(int numConnections); diff --git a/src/qt/forms/aboutdialog.ui b/src/qt/forms/aboutdialog.ui index 3ab4675bf3..fec63f737a 100644 --- a/src/qt/forms/aboutdialog.ui +++ b/src/qt/forms/aboutdialog.ui @@ -110,9 +110,12 @@ <string> This is experimental software. -Distributed under the MIT/X11 software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php. +Distributed under the MIT/X11 software license, see the accompanying file COPYING or <a href="http://www.opensource.org/licenses/mit-license.php">http://www.opensource.org/licenses/mit-license.php</a>. -This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/) and cryptographic software written by Eric Young (eay@cryptsoft.com) and UPnP software written by Thomas Bernard.</string> +This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (<a href="https://www.openssl.org/">https://www.openssl.org/</a>) and cryptographic software written by Eric Young (<a href="mailto:eay@cryptsoft.com">eay@cryptsoft.com</a>) and UPnP software written by Thomas Bernard.</string> + </property> + <property name="textFormat"> + <enum>Qt::RichText</enum> </property> <property name="wordWrap"> <bool>true</bool> diff --git a/src/qt/forms/rpcconsole.ui b/src/qt/forms/rpcconsole.ui index fcb6bb60bb..bf737d9b99 100644 --- a/src/qt/forms/rpcconsole.ui +++ b/src/qt/forms/rpcconsole.ui @@ -652,6 +652,281 @@ </item> </layout> </widget> + <widget class="QWidget" name="tab_peers"> + <attribute name="title"> + <string>&Peers</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="1"> + <widget class="QLabel" name="peerHeading"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Select a peer to view detailed information.</string> + </property> + <property name="margin"> + <number>3</number> + </property> + </widget> + </item> + <item row="0" column="0" rowspan="2"> + <widget class="QTableView" name="peerWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QWidget" name="detailWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="leftMargin"> + <number>3</number> + </property> + <item row="12" column="0"> + <widget class="QLabel" name="label_21"> + <property name="text"> + <string>Version:</string> + </property> + </widget> + </item> + <item row="11" column="1"> + <widget class="QLabel" name="peerPingTime"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>Last Receive:</string> + </property> + </widget> + </item> + <item row="14" column="0"> + <widget class="QLabel" name="label_28"> + <property name="text"> + <string>User Agent:</string> + </property> + </widget> + </item> + <item row="12" column="1"> + <widget class="QLabel" name="peerVersion"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QLabel" name="peerConnTime"> + <property name="minimumSize"> + <size> + <width>160</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="11" column="0"> + <widget class="QLabel" name="label_26"> + <property name="text"> + <string>Ping Time:</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="peerLastRecv"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_22"> + <property name="text"> + <string>Connection Time:</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLabel" name="peerBytesSent"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="14" column="1"> + <widget class="QLabel" name="peerSubversion"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="15" column="0"> + <widget class="QLabel" name="label_29"> + <property name="text"> + <string>Starting Height:</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLabel" name="peerBytesRecv"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_18"> + <property name="text"> + <string>Bytes Sent:</string> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string>Bytes Received:</string> + </property> + </widget> + </item> + <item row="15" column="1"> + <widget class="QLabel" name="peerHeight"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="16" column="0"> + <widget class="QLabel" name="label_24"> + <property name="text"> + <string>Ban Score:</string> + </property> + </widget> + </item> + <item row="16" column="1"> + <widget class="QLabel" name="peerBanScore"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="17" column="0"> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string>Direction:</string> + </property> + </widget> + </item> + <item row="17" column="1"> + <widget class="QLabel" name="peerDirection"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="19" column="0"> + <widget class="QLabel" name="label_25"> + <property name="text"> + <string>Sync Node:</string> + </property> + </widget> + </item> + <item row="19" column="1"> + <widget class="QLabel" name="peerSyncNode"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Last Send:</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Services:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_27"> + <property name="text"> + <string>IP Address/port:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="peerLastSend"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="peerServices"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="peerAddr"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + <item row="20" column="0"> + <widget class="QWidget" name="widget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> </widget> </item> </layout> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 183fcac4a0..1922d228c5 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -11,6 +11,7 @@ #include "core.h" #include "init.h" +#include "protocol.h" #include "util.h" #ifdef WIN32 @@ -754,4 +755,50 @@ QString boostPathToQString(const boost::filesystem::path &path) } #endif +QString formatDurationStr(int secs) +{ + QStringList strList; + int days = secs / 86400; + int hours = (secs % 86400) / 3600; + int mins = (secs % 3600) / 60; + int seconds = secs % 60; + + if (days) + strList.append(QString(QObject::tr("%1 d")).arg(days)); + if (hours) + strList.append(QString(QObject::tr("%1 h")).arg(hours)); + if (mins) + strList.append(QString(QObject::tr("%1 m")).arg(mins)); + if (seconds || (!days && !hours && !mins)) + strList.append(QString(QObject::tr("%1 s")).arg(seconds)); + + return strList.join(" "); +} + +QString formatServicesStr(uint64_t mask) +{ + QStringList strList; + + // Just scan the last 8 bits for now. + for (int i = 0; i < 8; i++) { + uint64_t check = 1 << i; + if (mask & check) + { + switch (check) + { + case NODE_NETWORK: + strList.append(QObject::tr("NETWORK")); + break; + default: + strList.append(QString("%1[%2]").arg(QObject::tr("UNKNOWN")).arg(check)); + } + } + } + + if (strList.size()) + return strList.join(" & "); + else + return QObject::tr("None"); +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 4f9416d1af..45c78b4e14 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -173,6 +173,11 @@ namespace GUIUtil /* Convert OS specific boost path to QString through UTF-8 */ QString boostPathToQString(const boost::filesystem::path &path); + /* Convert seconds into a QString with days, hours, mins, secs */ + QString formatDurationStr(int secs); + + /* Format CNodeStats.nServices bitmask into a user-readable string */ + QString formatServicesStr(uint64_t mask); } // namespace GUIUtil #endif // GUIUTIL_H diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index ca6ae17990..4c45585685 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -178,6 +178,9 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store) // and the items in savedPaymentRequest will be handled // when uiReady() is called. // +// Warning: ipcSendCommandLine() is called early in init, +// so don't use "emit message()", but "QMessageBox::"! +// bool PaymentServer::ipcParseCommandLine(int argc, char* argv[]) { for (int i = 1; i < argc; i++) @@ -411,7 +414,15 @@ void PaymentServer::handleURIOrFile(const QString& s) { SendCoinsRecipient recipient; if (GUIUtil::parseBitcoinURI(s, &recipient)) - emit receivedPaymentRequest(recipient); + { + CBitcoinAddress address(recipient.address.toStdString()); + if (!address.IsValid()) { + emit message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address), + CClientUIInterface::MSG_ERROR); + } + else + emit receivedPaymentRequest(recipient); + } else emit message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."), @@ -425,12 +436,14 @@ void PaymentServer::handleURIOrFile(const QString& s) { PaymentRequestPlus request; SendCoinsRecipient recipient; - if (readPaymentRequest(s, request) && processPaymentRequest(request, recipient)) - emit receivedPaymentRequest(recipient); - else + if (!readPaymentRequest(s, request)) + { emit message(tr("Payment request file handling"), - tr("Payment request file can not be read or processed! This can be caused by an invalid payment request file."), + tr("Payment request file can not be read! This can be caused by an invalid payment request file."), CClientUIInterface::ICON_WARNING); + } + else if (processPaymentRequest(request, recipient)) + emit receivedPaymentRequest(recipient); return; } @@ -482,6 +495,35 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoins if (!optionsModel) return false; + if (request.IsInitialized()) { + const payments::PaymentDetails& details = request.getDetails(); + + // Payment request network matches client network? + if ((details.network() == "main" && TestNet()) || + (details.network() == "test" && !TestNet())) + { + emit message(tr("Payment request rejected"), tr("Payment request network doesn't match client network."), + CClientUIInterface::MSG_ERROR); + + return false; + } + + // Expired payment request? + if (details.has_expires() && (int64_t)details.expires() < GetTime()) + { + emit message(tr("Payment request rejected"), tr("Payment request has expired."), + CClientUIInterface::MSG_ERROR); + + return false; + } + } + else { + emit message(tr("Payment request error"), tr("Payment request is not initialized."), + CClientUIInterface::MSG_ERROR); + + return false; + } + recipient.paymentRequest = request; recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo()); @@ -497,11 +539,11 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoins // Append destination address addresses.append(QString::fromStdString(CBitcoinAddress(dest).ToString())); } - else if (!recipient.authenticatedMerchant.isEmpty()){ + else if (!recipient.authenticatedMerchant.isEmpty()) { // Insecure payments to custom bitcoin addresses are not supported // (there is no good way to tell the user where they are paying in a way // they'd have a chance of understanding). - emit message(tr("Payment request error"), + emit message(tr("Payment request rejected"), tr("Unverified payment requests to custom payment scripts are unsupported."), CClientUIInterface::MSG_ERROR); return false; @@ -510,11 +552,10 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoins // Extract and check amounts CTxOut txOut(sendingTo.second, sendingTo.first); if (txOut.IsDust(CTransaction::nMinRelayTxFee)) { - QString msg = tr("Requested payment amount of %1 is too small (considered dust).") - .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)); + emit message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).") + .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)), + CClientUIInterface::MSG_ERROR); - qDebug() << "PaymentServer::processPaymentRequest : " << msg; - emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR); return false; } @@ -581,8 +622,8 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien refund_to->set_script(&s[0], s.size()); } else { - // This should never happen, because sending coins should have just unlocked the wallet - // and refilled the keypool + // This should never happen, because sending coins should have + // just unlocked the wallet and refilled the keypool. qDebug() << "PaymentServer::fetchPaymentACK : Error getting refund key, refund_to not set"; } } @@ -594,7 +635,7 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien netManager->post(netRequest, serData); } else { - // This should never happen, either: + // This should never happen, either. qDebug() << "PaymentServer::fetchPaymentACK : Error serializing payment message"; } } @@ -620,17 +661,15 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply) { PaymentRequestPlus request; SendCoinsRecipient recipient; - if (request.parse(data) && processPaymentRequest(request, recipient)) + if (!request.parse(data)) { - emit receivedPaymentRequest(recipient); - } - else - { - qDebug() << "PaymentServer::netRequestFinished : Error processing payment request"; + qDebug() << "PaymentServer::netRequestFinished : Error parsing payment request"; emit message(tr("Payment request error"), - tr("Payment request can not be parsed or processed!"), + tr("Payment request can not be parsed!"), CClientUIInterface::MSG_ERROR); } + else if (processPaymentRequest(request, recipient)) + emit receivedPaymentRequest(recipient); return; } diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp new file mode 100644 index 0000000000..db5ce639b1 --- /dev/null +++ b/src/qt/peertablemodel.cpp @@ -0,0 +1,236 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "peertablemodel.h" + +#include "clientmodel.h" + +#include "net.h" +#include "sync.h" + +#include <QDebug> +#include <QList> +#include <QTimer> + +bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombinedStats &right) const +{ + const CNodeStats *pLeft = &(left.nodestats); + const CNodeStats *pRight = &(right.nodestats); + + if (order == Qt::DescendingOrder) + std::swap(pLeft, pRight); + + switch(column) + { + case PeerTableModel::Address: + return pLeft->addrName.compare(pRight->addrName) < 0; + case PeerTableModel::Subversion: + return pLeft->cleanSubVer.compare(pRight->cleanSubVer) < 0; + case PeerTableModel::Height: + return pLeft->nStartingHeight < pRight->nStartingHeight; + } + + return false; +} + +// private implementation +class PeerTablePriv +{ +public: + /** Local cache of peer information */ + QList<CNodeCombinedStats> cachedNodeStats; + /** Column to sort nodes by */ + int sortColumn; + /** Order (ascending or descending) to sort nodes by */ + Qt::SortOrder sortOrder; + /** Index of rows by node ID */ + std::map<NodeId, int> mapNodeRows; + + /** Pull a full list of peers from vNodes into our cache */ + void refreshPeers() { + TRY_LOCK(cs_vNodes, lockNodes); + { + if (!lockNodes) + { + // skip the refresh if we can't immediately get the lock + return; + } + cachedNodeStats.clear(); +#if QT_VERSION >= 0x040700 + cachedNodeStats.reserve(vNodes.size()); +#endif + BOOST_FOREACH(CNode* pnode, vNodes) + { + CNodeCombinedStats stats; + stats.statestats.nMisbehavior = -1; + pnode->copyStats(stats.nodestats); + cachedNodeStats.append(stats); + } + } + + // if we can, retrieve the CNodeStateStats for each node. + TRY_LOCK(cs_main, lockMain); + { + if (lockMain) + { + BOOST_FOREACH(CNodeCombinedStats &stats, cachedNodeStats) + { + GetNodeStateStats(stats.nodestats.nodeid, stats.statestats); + } + } + } + + if (sortColumn >= 0) + // sort cacheNodeStats (use stable sort to prevent rows jumping around unneceesarily) + qStableSort(cachedNodeStats.begin(), cachedNodeStats.end(), NodeLessThan(sortColumn, sortOrder)); + + // build index map + mapNodeRows.clear(); + int row = 0; + BOOST_FOREACH(CNodeCombinedStats &stats, cachedNodeStats) + { + mapNodeRows.insert(std::pair<NodeId, int>(stats.nodestats.nodeid, row++)); + } + } + + int size() + { + return cachedNodeStats.size(); + } + + CNodeCombinedStats *index(int idx) + { + if(idx >= 0 && idx < cachedNodeStats.size()) { + return &cachedNodeStats[idx]; + } + else + { + return 0; + } + } +}; + +PeerTableModel::PeerTableModel(ClientModel *parent) : + QAbstractTableModel(parent),clientModel(parent),timer(0) +{ + columns << tr("Address") << tr("User Agent") << tr("Start Height"); + priv = new PeerTablePriv(); + // default to unsorted + priv->sortColumn = -1; + + // set up timer for auto refresh + timer = new QTimer(); + connect(timer, SIGNAL(timeout()), SLOT(refresh())); + + // load initial data + refresh(); +} + +void PeerTableModel::startAutoRefresh(int msecs) +{ + timer->setInterval(1000); + timer->start(); +} + +void PeerTableModel::stopAutoRefresh() +{ + timer->stop(); +} + +int PeerTableModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return priv->size(); +} + +int PeerTableModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 3; +} + +QVariant PeerTableModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid()) + return QVariant(); + + CNodeCombinedStats *rec = static_cast<CNodeCombinedStats*>(index.internalPointer()); + + if(role == Qt::DisplayRole) + { + switch(index.column()) + { + case Address: + return QVariant(rec->nodestats.addrName.c_str()); + case Subversion: + return QVariant(rec->nodestats.cleanSubVer.c_str()); + case Height: + return rec->nodestats.nStartingHeight; + } + } + return QVariant(); +} + +QVariant PeerTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation == Qt::Horizontal) + { + if(role == Qt::DisplayRole && section < columns.size()) + { + return columns[section]; + } + } + return QVariant(); +} + +Qt::ItemFlags PeerTableModel::flags(const QModelIndex &index) const +{ + if(!index.isValid()) + return 0; + + Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + return retval; +} + +QModelIndex PeerTableModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + CNodeCombinedStats *data = priv->index(row); + + if (data) + { + return createIndex(row, column, data); + } + else + { + return QModelIndex(); + } +} + +const CNodeCombinedStats *PeerTableModel::getNodeStats(int idx) { + return priv->index(idx); +} + +void PeerTableModel::refresh() +{ + emit layoutAboutToBeChanged(); + priv->refreshPeers(); + emit layoutChanged(); +} + +int PeerTableModel::getRowByNodeId(NodeId nodeid) +{ + std::map<NodeId, int>::iterator it = priv->mapNodeRows.find(nodeid); + if (it == priv->mapNodeRows.end()) + return -1; + + return it->second; +} + +void PeerTableModel::sort(int column, Qt::SortOrder order) +{ + priv->sortColumn = column; + priv->sortOrder = order; + refresh(); +} diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h new file mode 100644 index 0000000000..385bf0e0c1 --- /dev/null +++ b/src/qt/peertablemodel.h @@ -0,0 +1,80 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef PEERTABLEMODEL_H +#define PEERTABLEMODEL_H + +#include "main.h" +#include "net.h" + +#include <QAbstractTableModel> +#include <QStringList> + +class ClientModel; +class PeerTablePriv; + +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + +struct CNodeCombinedStats { + CNodeStats nodestats; + CNodeStateStats statestats; +}; + +class NodeLessThan +{ +public: + NodeLessThan(int nColumn, Qt::SortOrder fOrder) : + column(nColumn), order(fOrder) {} + bool operator()(const CNodeCombinedStats &left, const CNodeCombinedStats &right) const; + +private: + int column; + Qt::SortOrder order; +}; + +/** + Qt model providing information about connected peers, similar to the + "getpeerinfo" RPC call. Used by the rpc console UI. + */ +class PeerTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit PeerTableModel(ClientModel *parent = 0); + const CNodeCombinedStats *getNodeStats(int idx); + int getRowByNodeId(NodeId nodeid); + void startAutoRefresh(int msecs); + void stopAutoRefresh(); + + enum ColumnIndex { + Address = 0, + Subversion = 1, + Height = 2 + }; + + /** @name Methods overridden from QAbstractTableModel + @{*/ + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + void sort(int column, Qt::SortOrder order); + /*@}*/ + +public slots: + void refresh(); + +private: + ClientModel *clientModel; + QStringList columns; + PeerTablePriv *priv; + QTimer *timer; +}; + +#endif // PEERTABLEMODEL_H diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index 062638f2bc..d8dad15c0d 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -13,10 +13,10 @@ #include <QClipboard> #include <QDrag> +#include <QMenu> #include <QMimeData> #include <QMouseEvent> #include <QPixmap> -#include <QMenu> #if QT_VERSION < 0x050000 #include <QUrl> #endif diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index 5614ac635a..9b78e495c3 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -11,10 +11,12 @@ #include <QImage> #include <QLabel> +class OptionsModel; + namespace Ui { class ReceiveRequestDialog; } -class OptionsModel; + QT_BEGIN_NAMESPACE class QMenu; QT_END_NAMESPACE diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 0a46a722e4..3b7d37ff39 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -7,9 +7,12 @@ #include "clientmodel.h" #include "guiutil.h" +#include "peertablemodel.h" +#include "main.h" #include "rpcserver.h" #include "rpcclient.h" +#include "util.h" #include "json/json_spirit_value.h" #include <openssl/crypto.h> @@ -195,6 +198,10 @@ RPCConsole::RPCConsole(QWidget *parent) : clientModel(0), historyPtr(0) { + detailNodeStats = CNodeCombinedStats(); + detailNodeStats.nodestats.nodeid = -1; + detailNodeStats.statestats.nMisbehavior = -1; + ui->setupUi(this); GUIUtil::restoreWindowGeometry("nRPCConsoleWindow", this->size(), this); @@ -214,6 +221,7 @@ RPCConsole::RPCConsole(QWidget *parent) : startExecutor(); setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); + ui->detailWidget->hide(); clear(); } @@ -277,6 +285,21 @@ void RPCConsole::setClientModel(ClientModel *model) updateTrafficStats(model->getTotalBytesRecv(), model->getTotalBytesSent()); connect(model, SIGNAL(bytesChanged(quint64,quint64)), this, SLOT(updateTrafficStats(quint64, quint64))); + // set up peer table + ui->peerWidget->setModel(model->getPeerTableModel()); + ui->peerWidget->verticalHeader()->hide(); + ui->peerWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->peerWidget->setSelectionMode(QAbstractItemView::SingleSelection); + ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH); + columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(ui->peerWidget, MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH); + + // connect the peerWidget's selection model to our peerSelected() handler + QItemSelectionModel *peerSelectModel = ui->peerWidget->selectionModel(); + connect(peerSelectModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + this, SLOT(peerSelected(const QItemSelection &, const QItemSelection &))); + connect(model->getPeerTableModel(), SIGNAL(layoutChanged()), this, SLOT(peerLayoutChanged())); + // Provide initial values ui->clientVersion->setText(model->formatFullVersion()); ui->clientName->setText(model->clientName()); @@ -474,17 +497,7 @@ QString RPCConsole::FormatBytes(quint64 bytes) void RPCConsole::setTrafficGraphRange(int mins) { ui->trafficGraph->setGraphRangeMins(mins); - if(mins < 60) { - ui->lblGraphRange->setText(QString(tr("%1 m")).arg(mins)); - } else { - int hours = mins / 60; - int minsLeft = mins % 60; - if(minsLeft == 0) { - ui->lblGraphRange->setText(QString(tr("%1 h")).arg(hours)); - } else { - ui->lblGraphRange->setText(QString(tr("%1 h %2 m")).arg(hours).arg(minsLeft)); - } - } + ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60)); } void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) @@ -492,3 +505,161 @@ void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) ui->lblBytesIn->setText(FormatBytes(totalBytesIn)); ui->lblBytesOut->setText(FormatBytes(totalBytesOut)); } + +void RPCConsole::peerSelected(const QItemSelection &selected, const QItemSelection &deselected) +{ + Q_UNUSED(deselected); + + if (selected.indexes().isEmpty()) + return; + + // mark the cached banscore as unknown + detailNodeStats.statestats.nMisbehavior = -1; + + const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(selected.indexes().first().row()); + + if (stats) + { + detailNodeStats.nodestats.nodeid = stats->nodestats.nodeid; + updateNodeDetail(stats); + ui->detailWidget->show(); + ui->detailWidget->setDisabled(false); + } +} + +void RPCConsole::peerLayoutChanged() +{ + const CNodeCombinedStats *stats = NULL; + bool fUnselect = false, fReselect = false, fDisconnected = false; + + if (detailNodeStats.nodestats.nodeid == -1) + // no node selected yet + return; + + // find the currently selected row + int selectedRow; + QModelIndexList selectedModelIndex = ui->peerWidget->selectionModel()->selectedIndexes(); + if (selectedModelIndex.isEmpty()) + selectedRow = -1; + else + selectedRow = selectedModelIndex.first().row(); + + // check if our detail node has a row in the table (it may not necessarily + // be at selectedRow since its position can change after a layout change) + int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(detailNodeStats.nodestats.nodeid); + + if (detailNodeRow < 0) + { + // detail node dissapeared from table (node disconnected) + fUnselect = true; + fDisconnected = true; + detailNodeStats.nodestats.nodeid = 0; + } + else + { + if (detailNodeRow != selectedRow) + { + // detail node moved position + fUnselect = true; + fReselect = true; + } + + // get fresh stats on the detail node. + stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); + } + + if (fUnselect && selectedRow >= 0) + { + ui->peerWidget->selectionModel()->select(QItemSelection(selectedModelIndex.first(), selectedModelIndex.last()), + QItemSelectionModel::Deselect); + } + + if (fReselect) + { + ui->peerWidget->selectRow(detailNodeRow); + } + + if (stats) + updateNodeDetail(stats); + + if (fDisconnected) + { + ui->peerHeading->setText(QString(tr("Peer Disconnected"))); + ui->detailWidget->setDisabled(true); + QDateTime dt = QDateTime::fromTime_t(detailNodeStats.nodestats.nLastSend); + if (detailNodeStats.nodestats.nLastSend) + ui->peerLastSend->setText(dt.toString("yyyy-MM-dd hh:mm:ss")); + dt.setTime_t(detailNodeStats.nodestats.nLastRecv); + if (detailNodeStats.nodestats.nLastRecv) + ui->peerLastRecv->setText(dt.toString("yyyy-MM-dd hh:mm:ss")); + dt.setTime_t(detailNodeStats.nodestats.nTimeConnected); + ui->peerConnTime->setText(dt.toString("yyyy-MM-dd hh:mm:ss")); + } +} + +void RPCConsole::updateNodeDetail(const CNodeCombinedStats *combinedStats) +{ + CNodeStats stats = combinedStats->nodestats; + + // keep a copy of timestamps, used to display dates upon disconnect + detailNodeStats.nodestats.nLastSend = stats.nLastSend; + detailNodeStats.nodestats.nLastRecv = stats.nLastRecv; + detailNodeStats.nodestats.nTimeConnected = stats.nTimeConnected; + + // update the detail ui with latest node information + ui->peerHeading->setText(QString("<b>%1</b>").arg(tr("Node Detail"))); + ui->peerAddr->setText(QString(stats.addrName.c_str())); + ui->peerServices->setText(GUIUtil::formatServicesStr(stats.nServices)); + ui->peerLastSend->setText(stats.nLastSend ? GUIUtil::formatDurationStr(GetTime() - stats.nLastSend) : tr("never")); + ui->peerLastRecv->setText(stats.nLastRecv ? GUIUtil::formatDurationStr(GetTime() - stats.nLastRecv) : tr("never")); + ui->peerBytesSent->setText(FormatBytes(stats.nSendBytes)); + ui->peerBytesRecv->setText(FormatBytes(stats.nRecvBytes)); + ui->peerConnTime->setText(GUIUtil::formatDurationStr(GetTime() - stats.nTimeConnected)); + ui->peerPingTime->setText(stats.dPingTime == 0 ? tr("N/A") : QString(tr("%1 secs")).arg(QString::number(stats.dPingTime, 'f', 3))); + ui->peerVersion->setText(QString("%1").arg(stats.nVersion)); + ui->peerSubversion->setText(QString(stats.cleanSubVer.c_str())); + ui->peerDirection->setText(stats.fInbound ? tr("Inbound") : tr("Outbound")); + ui->peerHeight->setText(QString("%1").arg(stats.nStartingHeight)); + ui->peerSyncNode->setText(stats.fSyncNode ? tr("Yes") : tr("No")); + + // if we can, display the peer's ban score + CNodeStateStats statestats = combinedStats->statestats; + if (statestats.nMisbehavior >= 0) + { + // we have a new nMisbehavor value - update the cache + detailNodeStats.statestats.nMisbehavior = statestats.nMisbehavior; + } + + // pull the ban score from cache. -1 means it hasn't been retrieved yet (lock busy). + if (detailNodeStats.statestats.nMisbehavior >= 0) + ui->peerBanScore->setText(QString("%1").arg(detailNodeStats.statestats.nMisbehavior)); + else + ui->peerBanScore->setText(tr("Fetching...")); +} + +// We override the virtual resizeEvent of the QWidget to adjust tables column +// sizes as the tables width is proportional to the dialogs width. +void RPCConsole::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + columnResizingFixer->stretchColumnWidth(PeerTableModel::Address); +} + +void RPCConsole::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + // peerWidget needs a resize in case the dialog has non-default geometry + columnResizingFixer->stretchColumnWidth(PeerTableModel::Address); + + // start PeerTableModel auto refresh + clientModel->getPeerTableModel()->startAutoRefresh(1000); +} + +void RPCConsole::hideEvent(QHideEvent *event) +{ + QWidget::hideEvent(event); + + // stop PeerTableModel auto refresh + clientModel->getPeerTableModel()->stopAutoRefresh(); +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 091a6d294f..3fee34d00e 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -5,9 +5,19 @@ #ifndef RPCCONSOLE_H #define RPCCONSOLE_H +#include "guiutil.h" +#include "peertablemodel.h" + +#include "net.h" + #include <QDialog> class ClientModel; +class CNodeCombinedStats; + +QT_BEGIN_NAMESPACE +class QItemSelection; +QT_END_NAMESPACE namespace Ui { class RPCConsole; @@ -35,6 +45,19 @@ public: protected: virtual bool eventFilter(QObject* obj, QEvent *event); +private: + /** show detailed information on ui about selected node */ + void updateNodeDetail(const CNodeCombinedStats *combinedStats); + + enum ColumnWidths + { + ADDRESS_COLUMN_WIDTH = 250, + MINIMUM_COLUMN_WIDTH = 120 + }; + + /** track the node that we are currently viewing detail on in the peers tab */ + CNodeCombinedStats detailNodeStats; + private slots: void on_lineEdit_returnPressed(); void on_tabWidget_currentChanged(int index); @@ -44,6 +67,9 @@ private slots: void on_sldGraphRange_valueChanged(int value); /** update traffic statistics */ void updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut); + void resizeEvent(QResizeEvent *event); + void showEvent(QShowEvent *event); + void hideEvent(QHideEvent *event); public slots: void clear(); @@ -57,6 +83,10 @@ public slots: void browseHistory(int offset); /** Scroll console view to end */ void scrollToEnd(); + /** Handle selection of peer in peers list */ + void peerSelected(const QItemSelection &selected, const QItemSelection &deselected); + /** Handle updated peer information */ + void peerLayoutChanged(); signals: // For RPC command executor @@ -70,6 +100,7 @@ private: Ui::RPCConsole *ui; ClientModel *clientModel; QStringList history; + GUIUtil::TableViewLastColumnResizingFixer *columnResizingFixer; int historyPtr; void startExecutor(); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 33621e54b0..23b8ef83e2 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -377,26 +377,8 @@ void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) { - QString strSendCoins = tr("Send Coins"); - if (rv.paymentRequest.IsInitialized()) { - // Expired payment request? - const payments::PaymentDetails& details = rv.paymentRequest.getDetails(); - if (details.has_expires() && (int64_t)details.expires() < GetTime()) - { - emit message(strSendCoins, tr("Payment request expired"), - CClientUIInterface::MSG_WARNING); - return false; - } - } - else { - CBitcoinAddress address(rv.address.toStdString()); - if (!address.IsValid()) { - emit message(strSendCoins, tr("Invalid payment address %1").arg(rv.address), - CClientUIInterface::MSG_WARNING); - return false; - } - } - + // Just paste the entry, all pre-checks + // are done in paymentserver.cpp. pasteEntry(rv); return true; } diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 7c79b0efd0..1162e2d87f 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -129,6 +129,7 @@ void SplashScreen::subscribeToCoreSignals() { // Connect signals to client uiInterface.InitMessage.connect(boost::bind(InitMessage, this, _1)); + uiInterface.ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); #ifdef ENABLE_WALLET uiInterface.LoadWallet.connect(boost::bind(ConnectWallet, this, _1)); #endif @@ -138,6 +139,7 @@ void SplashScreen::unsubscribeFromCoreSignals() { // Disconnect signals from client uiInterface.InitMessage.disconnect(boost::bind(InitMessage, this, _1)); + uiInterface.ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); #ifdef ENABLE_WALLET if(pwalletMain) pwalletMain->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 45fb3d40c2..0cfcb048c8 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -42,7 +42,7 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx) } } -QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, int vout, int unit) +QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionRecord *rec, int unit) { QString strHTML; @@ -86,26 +86,19 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, int vout, int u if (nNet > 0) { // Credit - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if (CBitcoinAddress(rec->address).IsValid()) { - if (wallet->IsMine(txout)) + CTxDestination address = CBitcoinAddress(rec->address).Get(); + if (wallet->mapAddressBook.count(address)) { - CTxDestination address; - if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) - { - if (wallet->mapAddressBook.count(address)) - { - strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>"; - strHTML += "<b>" + tr("To") + ":</b> "; - strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString()); - if (!wallet->mapAddressBook[address].name.empty()) - strHTML += " (" + tr("own address") + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + ")"; - else - strHTML += " (" + tr("own address") + ")"; - strHTML += "<br>"; - } - } - break; + strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>"; + strHTML += "<b>" + tr("To") + ":</b> "; + strHTML += GUIUtil::HtmlEscape(rec->address); + if (!wallet->mapAddressBook[address].name.empty()) + strHTML += " (" + tr("own address") + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + ")"; + else + strHTML += " (" + tr("own address") + ")"; + strHTML += "<br>"; } } } @@ -224,7 +217,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, int vout, int u if (wtx.mapValue.count("comment") && !wtx.mapValue["comment"].empty()) strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "<br>"; - strHTML += "<b>" + tr("Transaction ID") + ":</b> " + TransactionRecord::formatSubTxId(wtx.GetHash(), vout) + "<br>"; + strHTML += "<b>" + tr("Transaction ID") + ":</b> " + TransactionRecord::formatSubTxId(wtx.GetHash(), rec->idx) + "<br>"; // Message from normal bitcoin:URI (bitcoin:123...?message=example) foreach (const PAIRTYPE(string, string)& r, wtx.vOrderForm) diff --git a/src/qt/transactiondesc.h b/src/qt/transactiondesc.h index 92d093b3eb..4bd4293210 100644 --- a/src/qt/transactiondesc.h +++ b/src/qt/transactiondesc.h @@ -8,6 +8,8 @@ #include <QObject> #include <QString> +class TransactionRecord; + class CWallet; class CWalletTx; @@ -18,7 +20,7 @@ class TransactionDesc: public QObject Q_OBJECT public: - static QString toHTML(CWallet *wallet, CWalletTx &wtx, int vout, int unit); + static QString toHTML(CWallet *wallet, CWalletTx &wtx, TransactionRecord *rec, int unit); private: TransactionDesc() {} diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 8cf2b0a1b1..b9fcd0d6b0 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -222,7 +222,7 @@ public: std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash); if(mi != wallet->mapWallet.end()) { - return TransactionDesc::toHTML(wallet, mi->second, rec->idx, unit); + return TransactionDesc::toHTML(wallet, mi->second, rec, unit); } } return QString(""); diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index a303b5d3ef..ff5057b526 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -427,7 +427,7 @@ Value verifychain(const Array& params, bool fHelp) if (params.size() > 1) nCheckDepth = params[1].get_int(); - return VerifyDB(nCheckLevel, nCheckDepth); + return CVerifyDB().VerifyDB(nCheckLevel, nCheckDepth); } Value getblockchaininfo(const Array& params, bool fHelp) diff --git a/src/script.cpp b/src/script.cpp index ac6d4b316f..381e84d0b7 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -208,14 +208,13 @@ const char* GetOpName(opcodetype opcode) case OP_NOP9 : return "OP_NOP9"; case OP_NOP10 : return "OP_NOP10"; + case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; + // Note: + // The template matching params OP_SMALLDATA/etc are defined in opcodetype enum + // as kind of implementation hack, they are *NOT* real opcodes. If found in real + // Script, just let the default: case deal with them. - // template matching params - case OP_PUBKEYHASH : return "OP_PUBKEYHASH"; - case OP_PUBKEY : return "OP_PUBKEY"; - case OP_SMALLDATA : return "OP_SMALLDATA"; - - case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; default: return "OP_UNKNOWN"; } diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index 40275cd194..aa8e5ca6c3 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -16,7 +16,7 @@ ["It is an OP_CHECKMULTISIG with an arbitrary extra byte stuffed into the signature at pos length - 2"], ["The dummy byte is fine however, so the NULLDUMMY flag should be happy"], [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], -"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004A0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH,NULLDUMMY"], +"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH,NULLDUMMY"], ["The following is a tweaked form of 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"], ["It is an OP_CHECKMULTISIG with the dummy value set to something other than an empty string"], diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index eea249b114..1dc2a3d82f 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -67,6 +67,31 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) LOCK2(cs_main, pwalletMain->cs_wallet); + CPubKey demoPubkey = pwalletMain->GenerateNewKey(); + CBitcoinAddress demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID())); + Value retValue; + string strAccount = "walletDemoAccount"; + string strPurpose = "receive"; + BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */ + CWalletDB walletdb(pwalletMain->strWalletFile); + CAccount account; + account.vchPubKey = demoPubkey; + pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, strPurpose); + walletdb.WriteAccount(strAccount, account); + }); + + + /********************************* + * setaccount + *********************************/ + BOOST_CHECK_NO_THROW(CallRPC("setaccount 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ nullaccount")); + BOOST_CHECK_THROW(CallRPC("setaccount"), runtime_error); + /* 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4X (33 chars) is an illegal address (should be 34 chars) */ + BOOST_CHECK_THROW(CallRPC("setaccount 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4X nullaccount"), runtime_error); + + /********************************* + * listunspent + *********************************/ BOOST_CHECK_NO_THROW(CallRPC("listunspent")); BOOST_CHECK_THROW(CallRPC("listunspent string"), runtime_error); BOOST_CHECK_THROW(CallRPC("listunspent 0 string"), runtime_error); @@ -75,6 +100,9 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) BOOST_CHECK_NO_THROW(r=CallRPC("listunspent 0 1 []")); BOOST_CHECK(r.get_array().empty()); + /********************************* + * listreceivedbyaddress + *********************************/ BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaddress")); BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaddress 0")); BOOST_CHECK_THROW(CallRPC("listreceivedbyaddress not_int"), runtime_error); @@ -82,12 +110,71 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaddress 0 true")); BOOST_CHECK_THROW(CallRPC("listreceivedbyaddress 0 true extra"), runtime_error); + /********************************* + * listreceivedbyaccount + *********************************/ BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaccount")); BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaccount 0")); BOOST_CHECK_THROW(CallRPC("listreceivedbyaccount not_int"), runtime_error); BOOST_CHECK_THROW(CallRPC("listreceivedbyaccount 0 not_bool"), runtime_error); BOOST_CHECK_NO_THROW(CallRPC("listreceivedbyaccount 0 true")); BOOST_CHECK_THROW(CallRPC("listreceivedbyaccount 0 true extra"), runtime_error); + + /********************************* + * getrawchangeaddress + *********************************/ + BOOST_CHECK_NO_THROW(CallRPC("getrawchangeaddress")); + + /********************************* + * getnewaddress + *********************************/ + BOOST_CHECK_NO_THROW(CallRPC("getnewaddress")); + BOOST_CHECK_NO_THROW(CallRPC("getnewaddress getnewaddress_demoaccount")); + + /********************************* + * getaccountaddress + *********************************/ + BOOST_CHECK_NO_THROW(CallRPC("getaccountaddress \"\"")); + BOOST_CHECK_NO_THROW(CallRPC("getaccountaddress accountThatDoesntExists")); // Should generate a new account + BOOST_CHECK_NO_THROW(retValue = CallRPC("getaccountaddress " + strAccount)); + BOOST_CHECK(CBitcoinAddress(retValue.get_str()).Get() == demoAddress.Get()); + + /********************************* + * getaccount + *********************************/ + BOOST_CHECK_THROW(CallRPC("getaccount"), runtime_error); + BOOST_CHECK_NO_THROW(CallRPC("getaccount " + demoAddress.ToString())); + + /********************************* + * signmessage + verifymessage + *********************************/ + BOOST_CHECK_NO_THROW(retValue = CallRPC("signmessage " + demoAddress.ToString() + " mymessage")); + BOOST_CHECK_THROW(CallRPC("signmessage"), runtime_error); + /* Should throw error because this address is not loaded in the wallet */ + BOOST_CHECK_THROW(CallRPC("signmessage 1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ mymessage"), runtime_error); + + /* missing arguments */ + BOOST_CHECK_THROW(CallRPC("verifymessage "+ demoAddress.ToString()), runtime_error); + BOOST_CHECK_THROW(CallRPC("verifymessage "+ demoAddress.ToString() + " " + retValue.get_str()), runtime_error); + /* Illegal address */ + BOOST_CHECK_THROW(CallRPC("verifymessage 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4X " + retValue.get_str() + " mymessage"), runtime_error); + /* wrong address */ + BOOST_CHECK(CallRPC("verifymessage 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ " + retValue.get_str() + " mymessage").get_bool() == false); + /* Correct address and signature but wrong message */ + BOOST_CHECK(CallRPC("verifymessage "+ demoAddress.ToString() + " " + retValue.get_str() + " wrongmessage").get_bool() == false); + /* Correct address, message and signature*/ + BOOST_CHECK(CallRPC("verifymessage "+ demoAddress.ToString() + " " + retValue.get_str() + " mymessage").get_bool() == true); + + /********************************* + * getaddressesbyaccount + *********************************/ + BOOST_CHECK_THROW(CallRPC("getaddressesbyaccount"), runtime_error); + BOOST_CHECK_NO_THROW(retValue = CallRPC("getaddressesbyaccount " + strAccount)); + Array arr = retValue.get_array(); + BOOST_CHECK(arr.size() > 0); + BOOST_CHECK(CBitcoinAddress(arr[0].get_str()).Get() == demoAddress.Get()); + } + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/ui_interface.h b/src/ui_interface.h index 7b655ac951..e1a3e04e12 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -94,6 +94,9 @@ public: /** A wallet has been loaded. */ boost::signals2::signal<void (CWallet* wallet)> LoadWallet; + + /** Show progress e.g. for verifychain */ + boost::signals2::signal<void (const std::string &title, int nProgress)> ShowProgress; }; extern CClientUIInterface uiInterface; diff --git a/src/util.cpp b/src/util.cpp index 336ef31725..d106b03230 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -94,7 +94,6 @@ bool fPrintToDebugLog = true; bool fDaemon = false; bool fServer = false; string strMiscWarning; -bool fNoListen = false; bool fLogTimestamps = false; volatile bool fReopenDebugLog = false; CClientUIInterface uiInterface; diff --git a/src/util.h b/src/util.h index ffcb20d822..b09f9bb15e 100644 --- a/src/util.h +++ b/src/util.h @@ -100,7 +100,6 @@ extern bool fPrintToConsole; extern bool fPrintToDebugLog; extern bool fServer; extern std::string strMiscWarning; -extern bool fNoListen; extern bool fLogTimestamps; extern volatile bool fReopenDebugLog; diff --git a/src/wallet.h b/src/wallet.h index 96074151ad..8e2917188a 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -143,26 +143,26 @@ public: CWallet() { - nWalletVersion = FEATURE_BASE; - nWalletMaxVersion = FEATURE_BASE; - fFileBacked = false; - nMasterKeyMaxID = 0; - pwalletdbEncryption = NULL; - nOrderPosNext = 0; - nNextResend = 0; - nLastResend = 0; + SetNull(); } CWallet(std::string strWalletFileIn) { - nWalletVersion = FEATURE_BASE; - nWalletMaxVersion = FEATURE_BASE; + SetNull(); + strWalletFile = strWalletFileIn; fFileBacked = true; + } + void SetNull() + { + nWalletVersion = FEATURE_BASE; + nWalletMaxVersion = FEATURE_BASE; + fFileBacked = false; nMasterKeyMaxID = 0; pwalletdbEncryption = NULL; nOrderPosNext = 0; nNextResend = 0; nLastResend = 0; + nTimeFirstKey = 0; } std::map<uint256, CWalletTx> mapWallet; |