aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.qt.include3
-rw-r--r--src/qt/bitcoin.qrc1
-rw-r--r--src/qt/guiconstants.h2
-rw-r--r--src/qt/res/icons/transaction_abandoned.pngbin0 -> 1473 bytes
-rw-r--r--src/qt/transactiondesc.cpp2
-rw-r--r--src/qt/transactionrecord.cpp2
-rw-r--r--src/qt/transactionrecord.h1
-rw-r--r--src/qt/transactiontablemodel.cpp10
-rw-r--r--src/qt/transactionview.cpp33
-rw-r--r--src/qt/transactionview.h2
-rw-r--r--src/qt/walletmodel.cpp15
-rw-r--r--src/qt/walletmodel.h3
-rw-r--r--src/rpc/blockchain.cpp16
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/test/rpc_tests.cpp24
16 files changed, 110 insertions, 7 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 1e54512cbd..d91e959cff 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -479,7 +479,7 @@ endif
%.pb.cc %.pb.h: %.proto
@test -f $(PROTOC)
- $(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$(abspath $(<D) $<)
+ $(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$(<D) $<
if ENABLE_TESTS
include Makefile.test.include
diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index 357e4c47ff..8443fe697b 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -268,7 +268,8 @@ RES_ICONS = \
qt/res/icons/tx_output.png \
qt/res/icons/tx_mined.png \
qt/res/icons/warning.png \
- qt/res/icons/verify.png
+ qt/res/icons/verify.png \
+ qt/res/icons/transaction_abandoned.png
BITCOIN_QT_CPP = \
qt/bantablemodel.cpp \
diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc
index dcd3b4ae2c..24b0bae3ec 100644
--- a/src/qt/bitcoin.qrc
+++ b/src/qt/bitcoin.qrc
@@ -49,6 +49,7 @@
<file alias="fontbigger">res/icons/fontbigger.png</file>
<file alias="fontsmaller">res/icons/fontsmaller.png</file>
<file alias="prompticon">res/icons/chevron.png</file>
+ <file alias="transaction_abandoned">res/icons/transaction_abandoned.png</file>
</qresource>
<qresource prefix="/movies">
<file alias="spinner-000">res/movies/spinner-000.png</file>
diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h
index 5ceffcd70a..4b2c10dd48 100644
--- a/src/qt/guiconstants.h
+++ b/src/qt/guiconstants.h
@@ -29,6 +29,8 @@ static const bool DEFAULT_SPLASHSCREEN = true;
#define COLOR_TX_STATUS_OPENUNTILDATE QColor(64, 64, 255)
/* Transaction list -- TX status decoration - offline */
#define COLOR_TX_STATUS_OFFLINE QColor(192, 192, 192)
+/* Transaction list -- TX status decoration - danger, tx needs attention */
+#define COLOR_TX_STATUS_DANGER QColor(200, 100, 100)
/* Transaction list -- TX status decoration - default color */
#define COLOR_BLACK QColor(0, 0, 0)
diff --git a/src/qt/res/icons/transaction_abandoned.png b/src/qt/res/icons/transaction_abandoned.png
new file mode 100644
index 0000000000..8ca6445c20
--- /dev/null
+++ b/src/qt/res/icons/transaction_abandoned.png
Binary files differ
diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp
index 5abefc144e..bae0cbd1c8 100644
--- a/src/qt/transactiondesc.cpp
+++ b/src/qt/transactiondesc.cpp
@@ -39,7 +39,7 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
return tr("%1/offline").arg(nDepth);
else if (nDepth == 0)
- return tr("0/unconfirmed, %1").arg((wtx.InMempool() ? tr("in memory pool") : tr("not in memory pool")));
+ return tr("0/unconfirmed, %1").arg((wtx.InMempool() ? tr("in memory pool") : tr("not in memory pool"))) + (wtx.isAbandoned() ? ", "+tr("abandoned") : "");
else if (nDepth < 6)
return tr("%1/unconfirmed").arg(nDepth);
else
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp
index 97b77cc93d..4fe47181f6 100644
--- a/src/qt/transactionrecord.cpp
+++ b/src/qt/transactionrecord.cpp
@@ -239,6 +239,8 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
else if (status.depth == 0)
{
status.status = TransactionStatus::Unconfirmed;
+ if (wtx.isAbandoned())
+ status.status = TransactionStatus::Abandoned;
}
else if (status.depth < RecommendedNumConfirmations)
{
diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h
index 95ab98c10d..8c754c3aad 100644
--- a/src/qt/transactionrecord.h
+++ b/src/qt/transactionrecord.h
@@ -33,6 +33,7 @@ public:
Unconfirmed, /**< Not yet mined into a block **/
Confirming, /**< Confirmed, but waiting for the recommended number of confirmations **/
Conflicted, /**< Conflicts with other transaction or mempool **/
+ Abandoned, /**< Abandoned from the wallet **/
/// Generated (mined) transactions
Immature, /**< Mined but waiting for maturity */
MaturesWarning, /**< Transaction will likely not mature because no nodes have confirmed */
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index d2a52b3022..b29ecf8348 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -312,6 +312,9 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons
case TransactionStatus::Unconfirmed:
status = tr("Unconfirmed");
break;
+ case TransactionStatus::Abandoned:
+ status = tr("Abandoned");
+ break;
case TransactionStatus::Confirming:
status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations);
break;
@@ -468,6 +471,8 @@ QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx)
return COLOR_TX_STATUS_OFFLINE;
case TransactionStatus::Unconfirmed:
return QIcon(":/icons/transaction_0");
+ case TransactionStatus::Abandoned:
+ return QIcon(":/icons/transaction_abandoned");
case TransactionStatus::Confirming:
switch(wtx->status.depth)
{
@@ -573,6 +578,11 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
case Qt::TextAlignmentRole:
return column_alignments[index.column()];
case Qt::ForegroundRole:
+ // Use the "danger" color for abandoned transactions
+ if(rec->status.status == TransactionStatus::Abandoned)
+ {
+ return COLOR_TX_STATUS_DANGER;
+ }
// Non-confirmed (but not immature) as transactions are grey
if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
{
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index a4d4c7a35f..a352228c36 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -37,7 +37,7 @@
TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) :
QWidget(parent), model(0), transactionProxyModel(0),
- transactionView(0)
+ transactionView(0), abandonAction(0)
{
// Build filter row
setContentsMargins(0,0,0,0);
@@ -137,6 +137,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
transactionView = view;
// Actions
+ abandonAction = new QAction(tr("Abandon transaction"), this);
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
@@ -153,8 +154,10 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
contextMenu->addAction(copyTxIDAction);
contextMenu->addAction(copyTxHexAction);
contextMenu->addAction(copyTxPlainText);
- contextMenu->addAction(editLabelAction);
contextMenu->addAction(showDetailsAction);
+ contextMenu->addSeparator();
+ contextMenu->addAction(abandonAction);
+ contextMenu->addAction(editLabelAction);
mapperThirdPartyTxUrls = new QSignalMapper(this);
@@ -170,6 +173,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
+ connect(abandonAction, SIGNAL(triggered()), this, SLOT(abandonTx()));
connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
@@ -360,12 +364,37 @@ void TransactionView::exportClicked()
void TransactionView::contextualMenu(const QPoint &point)
{
QModelIndex index = transactionView->indexAt(point);
+ QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
+
+ // check if transaction can be abandoned, disable context menu action in case it doesn't
+ uint256 hash;
+ hash.SetHex(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString());
+ abandonAction->setEnabled(model->transactionCanBeAbandoned(hash));
+
if(index.isValid())
{
contextMenu->exec(QCursor::pos());
}
}
+void TransactionView::abandonTx()
+{
+ if(!transactionView || !transactionView->selectionModel())
+ return;
+ QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
+
+ // get the hash from the TxHashRole (QVariant / QString)
+ uint256 hash;
+ QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
+ hash.SetHex(hashQStr.toStdString());
+
+ // Abandon the wallet transaction over the walletModel
+ model->abandonTransaction(hash);
+
+ // Update the table
+ model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false);
+}
+
void TransactionView::copyAddress()
{
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);
diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h
index 2cfbd471b0..e9b9d5b6bc 100644
--- a/src/qt/transactionview.h
+++ b/src/qt/transactionview.h
@@ -75,6 +75,7 @@ private:
QFrame *dateRangeWidget;
QDateTimeEdit *dateFrom;
QDateTimeEdit *dateTo;
+ QAction *abandonAction;
QWidget *createDateRangeWidget();
@@ -97,6 +98,7 @@ private Q_SLOTS:
void copyTxPlainText();
void openThirdPartyTxUrl(QString url);
void updateWatchOnlyColumn(bool fHaveWatchOnly);
+ void abandonTx();
Q_SIGNALS:
void doubleClicked(const QModelIndex&);
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index cf38c64eb0..ce230d6aed 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -668,3 +668,18 @@ bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t
else
return wallet->AddDestData(dest, key, sRequest);
}
+
+bool WalletModel::transactionCanBeAbandoned(uint256 hash) const
+{
+ LOCK2(cs_main, wallet->cs_wallet);
+ const CWalletTx *wtx = wallet->GetWalletTx(hash);
+ if (!wtx || wtx->isAbandoned() || wtx->GetDepthInMainChain() > 0 || wtx->InMempool())
+ return false;
+ return true;
+}
+
+bool WalletModel::abandonTransaction(uint256 hash) const
+{
+ LOCK2(cs_main, wallet->cs_wallet);
+ return wallet->AbandonTransaction(hash);
+}
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index 7a47eda86f..e5470bf618 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -200,6 +200,9 @@ public:
void loadReceiveRequests(std::vector<std::string>& vReceiveRequests);
bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest);
+ bool transactionCanBeAbandoned(uint256 hash) const;
+ bool abandonTransaction(uint256 hash) const;
+
private:
CWallet *wallet;
bool fHaveWatchOnly;
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index f1c9d9f7ab..34637b9f7e 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -70,6 +70,7 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex)
result.push_back(Pair("confirmations", confirmations));
result.push_back(Pair("height", blockindex->nHeight));
result.push_back(Pair("version", blockindex->nVersion));
+ result.push_back(Pair("versionHex", strprintf("%08x", blockindex->nVersion)));
result.push_back(Pair("merkleroot", blockindex->hashMerkleRoot.GetHex()));
result.push_back(Pair("time", (int64_t)blockindex->nTime));
result.push_back(Pair("mediantime", (int64_t)blockindex->GetMedianTimePast()));
@@ -98,6 +99,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION)));
result.push_back(Pair("height", blockindex->nHeight));
result.push_back(Pair("version", block.nVersion));
+ result.push_back(Pair("versionHex", strprintf("%08x", block.nVersion)));
result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex()));
UniValue txs(UniValue::VARR);
BOOST_FOREACH(const CTransaction&tx, block.vtx)
@@ -316,6 +318,7 @@ UniValue getblockheader(const UniValue& params, bool fHelp)
" \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n"
" \"height\" : n, (numeric) The block height or index\n"
" \"version\" : n, (numeric) The block version\n"
+ " \"versionHex\" : \"00000000\", (string) The block version formatted in hexadecimal\n"
" \"merkleroot\" : \"xxxx\", (string) The merkle root\n"
" \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n"
" \"mediantime\" : ttt, (numeric) The median block time in seconds since epoch (Jan 1 1970 GMT)\n"
@@ -375,6 +378,7 @@ UniValue getblock(const UniValue& params, bool fHelp)
" \"size\" : n, (numeric) The block size\n"
" \"height\" : n, (numeric) The block height or index\n"
" \"version\" : n, (numeric) The block version\n"
+ " \"versionHex\" : \"00000000\", (string) The block version formatted in hexadecimal\n"
" \"merkleroot\" : \"xxxx\", (string) The merkle root\n"
" \"tx\" : [ (array of string) The transaction ids\n"
" \"transactionid\" (string) The transaction id\n"
@@ -608,13 +612,20 @@ static UniValue BIP9SoftForkDesc(const std::string& name, const Consensus::Param
{
UniValue rv(UniValue::VOBJ);
rv.push_back(Pair("id", name));
- switch (VersionBitsTipState(consensusParams, id)) {
+ const ThresholdState thresholdState = VersionBitsTipState(consensusParams, id);
+ switch (thresholdState) {
case THRESHOLD_DEFINED: rv.push_back(Pair("status", "defined")); break;
case THRESHOLD_STARTED: rv.push_back(Pair("status", "started")); break;
case THRESHOLD_LOCKED_IN: rv.push_back(Pair("status", "locked_in")); break;
case THRESHOLD_ACTIVE: rv.push_back(Pair("status", "active")); break;
case THRESHOLD_FAILED: rv.push_back(Pair("status", "failed")); break;
}
+ if (THRESHOLD_STARTED == thresholdState)
+ {
+ rv.push_back(Pair("bit", consensusParams.vDeployments[id].bit));
+ }
+ rv.push_back(Pair("startTime", consensusParams.vDeployments[id].nStartTime));
+ rv.push_back(Pair("timeout", consensusParams.vDeployments[id].nTimeout));
return rv;
}
@@ -653,6 +664,9 @@ UniValue getblockchaininfo(const UniValue& params, bool fHelp)
" {\n"
" \"id\": \"xxxx\", (string) name of the softfork\n"
" \"status\": \"xxxx\", (string) one of \"defined\", \"started\", \"lockedin\", \"active\", \"failed\"\n"
+ " \"bit\": xx, (numeric) the bit, 0-28, in the block version field used to signal this soft fork\n"
+ " \"startTime\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n"
+ " \"timeout\": xx (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n"
" }\n"
" ]\n"
"}\n"
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 033987af59..c89af6bfa7 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -30,7 +30,6 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "generate", 0 },
{ "generate", 1 },
{ "generatetoaddress", 0 },
- { "generatetoaddress", 1 },
{ "generatetoaddress", 2 },
{ "getnetworkhashps", 0 },
{ "getnetworkhashps", 1 },
diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp
index 1976ee2cb6..bbda6a48f4 100644
--- a/src/test/rpc_tests.cpp
+++ b/src/test/rpc_tests.cpp
@@ -11,6 +11,7 @@
#include "test/test_bitcoin.h"
#include <boost/algorithm/string.hpp>
+#include <boost/assign/list_of.hpp>
#include <boost/test/unit_test.hpp>
#include <univalue.h>
@@ -308,4 +309,27 @@ BOOST_AUTO_TEST_CASE(rpc_ban)
BOOST_CHECK_EQUAL(adr.get_str(), "2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/128");
}
+BOOST_AUTO_TEST_CASE(rpc_convert_values_generatetoaddress)
+{
+ UniValue result;
+
+ BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", boost::assign::list_of("101")("mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a")));
+ BOOST_CHECK_EQUAL(result[0].get_int(), 101);
+ BOOST_CHECK_EQUAL(result[1].get_str(), "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a");
+
+ BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", boost::assign::list_of("101")("mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU")));
+ BOOST_CHECK_EQUAL(result[0].get_int(), 101);
+ BOOST_CHECK_EQUAL(result[1].get_str(), "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU");
+
+ BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", boost::assign::list_of("1")("mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a")("9")));
+ BOOST_CHECK_EQUAL(result[0].get_int(), 1);
+ BOOST_CHECK_EQUAL(result[1].get_str(), "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a");
+ BOOST_CHECK_EQUAL(result[2].get_int(), 9);
+
+ BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", boost::assign::list_of("1")("mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU")("9")));
+ BOOST_CHECK_EQUAL(result[0].get_int(), 1);
+ BOOST_CHECK_EQUAL(result[1].get_str(), "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU");
+ BOOST_CHECK_EQUAL(result[2].get_int(), 9);
+}
+
BOOST_AUTO_TEST_SUITE_END()