aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/gitian-building.md43
-rw-r--r--src/init.cpp7
-rw-r--r--src/main.cpp20
-rw-r--r--src/main.h15
-rw-r--r--src/net.cpp15
-rw-r--r--src/net.h1
-rw-r--r--src/qt/Makefile.am3
-rw-r--r--src/qt/bitcoingui.cpp27
-rw-r--r--src/qt/bitcoingui.h5
-rw-r--r--src/qt/clientmodel.cpp21
-rw-r--r--src/qt/clientmodel.h6
-rw-r--r--src/qt/forms/aboutdialog.ui7
-rw-r--r--src/qt/forms/rpcconsole.ui275
-rw-r--r--src/qt/guiutil.cpp47
-rw-r--r--src/qt/guiutil.h5
-rw-r--r--src/qt/paymentserver.cpp81
-rw-r--r--src/qt/peertablemodel.cpp236
-rw-r--r--src/qt/peertablemodel.h80
-rw-r--r--src/qt/receiverequestdialog.cpp2
-rw-r--r--src/qt/receiverequestdialog.h4
-rw-r--r--src/qt/rpcconsole.cpp193
-rw-r--r--src/qt/rpcconsole.h31
-rw-r--r--src/qt/sendcoinsdialog.cpp22
-rw-r--r--src/qt/splashscreen.cpp2
-rw-r--r--src/qt/transactiondesc.cpp33
-rw-r--r--src/qt/transactiondesc.h4
-rw-r--r--src/qt/transactiontablemodel.cpp2
-rw-r--r--src/rpcblockchain.cpp2
-rw-r--r--src/script.cpp11
-rw-r--r--src/test/data/tx_valid.json2
-rw-r--r--src/test/rpc_wallet_tests.cpp87
-rw-r--r--src/ui_interface.h3
-rw-r--r--src/util.cpp1
-rw-r--r--src/util.h1
-rw-r--r--src/wallet.h20
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;
}
}
}
diff --git a/src/net.h b/src/net.h
index d3346873bc..bc14069132 100644
--- a/src/net.h
+++ b/src/net.h
@@ -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 &lt;a href=&quot;http://www.opensource.org/licenses/mit-license.php&quot;&gt;http://www.opensource.org/licenses/mit-license.php&lt;/a&gt;.
-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 (&lt;a href=&quot;https://www.openssl.org/&quot;&gt;https://www.openssl.org/&lt;/a&gt;) and cryptographic software written by Eric Young (&lt;a href=&quot;mailto:eay@cryptsoft.com&quot;&gt;eay@cryptsoft.com&lt;/a&gt;) 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>&amp;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;