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.include1
-rw-r--r--src/allocators.h2
-rw-r--r--src/bloom.cpp7
-rw-r--r--src/bloom.h2
-rw-r--r--src/core.cpp16
-rw-r--r--src/core.h3
-rw-r--r--src/init.cpp7
-rw-r--r--src/m4/bitcoin_qt.m4110
-rw-r--r--src/main.cpp114
-rw-r--r--src/main.h4
-rw-r--r--src/net.cpp6
-rw-r--r--src/net.h1
-rw-r--r--src/qt/addressbookpage.cpp2
-rw-r--r--src/qt/bitcoin.cpp10
-rw-r--r--src/qt/bitcoinamountfield.cpp257
-rw-r--r--src/qt/bitcoinamountfield.h14
-rw-r--r--src/qt/bitcoinunits.cpp86
-rw-r--r--src/qt/bitcoinunits.h60
-rw-r--r--src/qt/coincontroldialog.cpp2
-rw-r--r--src/qt/guiconstants.h4
-rw-r--r--src/qt/guiutil.cpp2
-rw-r--r--src/qt/optionsdialog.cpp4
-rw-r--r--src/qt/overviewpage.cpp18
-rw-r--r--src/qt/recentrequeststablemodel.cpp7
-rw-r--r--src/qt/sendcoinsdialog.cpp8
-rw-r--r--src/qt/sendcoinsentry.cpp18
-rw-r--r--src/qt/transactiondesc.cpp28
-rw-r--r--src/qt/transactionfilterproxy.cpp4
-rw-r--r--src/qt/transactionrecord.cpp10
-rw-r--r--src/qt/transactionrecord.h24
-rw-r--r--src/qt/transactiontablemodel.cpp28
-rw-r--r--src/qt/transactiontablemodel.h4
-rw-r--r--src/qt/transactionview.cpp21
-rw-r--r--src/qt/transactionview.h3
-rw-r--r--src/qt/walletmodel.cpp21
-rw-r--r--src/qt/walletmodel.h4
-rw-r--r--src/qt/walletview.cpp2
-rw-r--r--src/rpcdump.cpp26
-rw-r--r--src/rpcwallet.cpp79
-rw-r--r--src/test/bloom_tests.cpp4
-rw-r--r--src/timedata.cpp18
-rw-r--r--src/txmempool.cpp8
-rw-r--r--src/ui_interface.h3
-rw-r--r--src/version.h4
-rw-r--r--src/wallet.cpp73
-rw-r--r--src/wallet.h25
47 files changed, 626 insertions, 530 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 90e0a43beb..ff23747592 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -276,7 +276,7 @@ EXTRA_DIST = leveldb secp256k1
clean-local:
-$(MAKE) -C leveldb clean
- -$(MAKE) -C secp256k1 clean
+ -$(MAKE) -C secp256k1 clean 2>/dev/null
rm -f leveldb/*/*.gcno leveldb/helpers/memenv/*.gcno
-rm -f config.h
diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index 75b7b683dd..2772bc753a 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -145,6 +145,7 @@ BITCOIN_MM = \
QT_MOC = \
qt/bitcoin.moc \
+ qt/bitcoinamountfield.moc \
qt/intro.moc \
qt/overviewpage.moc \
qt/rpcconsole.moc
diff --git a/src/allocators.h b/src/allocators.h
index 7012ef7e2a..be0be7ab96 100644
--- a/src/allocators.h
+++ b/src/allocators.h
@@ -131,7 +131,7 @@ public:
* Due to the unpredictable order of static initializers, we have to make sure the
* LockedPageManager instance exists before any other STL-based objects that use
* secure_allocator are created. So instead of having LockedPageManager also be
- * static-intialized, it is created on demand.
+ * static-initialized, it is created on demand.
*/
class LockedPageManager: public LockedPageManagerBase<MemoryPageLocker>
{
diff --git a/src/bloom.cpp b/src/bloom.cpp
index 85a2ddc189..26e366179c 100644
--- a/src/bloom.cpp
+++ b/src/bloom.cpp
@@ -94,13 +94,6 @@ bool CBloomFilter::contains(const uint256& hash) const
return contains(data);
}
-void CBloomFilter::clear()
-{
- vData.assign(vData.size(),0);
- isFull = false;
- isEmpty = true;
-}
-
bool CBloomFilter::IsWithinSizeConstraints() const
{
return vData.size() <= MAX_BLOOM_FILTER_SIZE && nHashFuncs <= MAX_HASH_FUNCS;
diff --git a/src/bloom.h b/src/bloom.h
index d0caf9e9fa..956bead87f 100644
--- a/src/bloom.h
+++ b/src/bloom.h
@@ -78,8 +78,6 @@ public:
bool contains(const COutPoint& outpoint) const;
bool contains(const uint256& hash) const;
- void clear();
-
// True if the size is <= MAX_BLOOM_FILTER_SIZE and the number of hash functions is <= MAX_HASH_FUNCS
// (catch a filter which was just deserialized which was too big)
bool IsWithinSizeConstraints() const;
diff --git a/src/core.cpp b/src/core.cpp
index b56994ecf3..149b3532a1 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -124,22 +124,6 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
return *this;
}
-bool CTransaction::IsEquivalentTo(const CTransaction& tx) const
-{
- if (nVersion != tx.nVersion ||
- nLockTime != tx.nLockTime ||
- vin.size() != tx.vin.size() ||
- vout != tx.vout)
- return false;
- for (unsigned int i = 0; i < vin.size(); i++)
- {
- if (vin[i].nSequence != tx.vin[i].nSequence ||
- vin[i].prevout != tx.vin[i].prevout)
- return false;
- }
- return true;
-}
-
int64_t CTransaction::GetValueOut() const
{
int64_t nValueOut = 0;
diff --git a/src/core.h b/src/core.h
index 0387336c98..fb64e6c08e 100644
--- a/src/core.h
+++ b/src/core.h
@@ -255,9 +255,6 @@ public:
return hash;
}
- // True if only scriptSigs are different
- bool IsEquivalentTo(const CTransaction& tx) const;
-
// Return sum of txouts.
int64_t GetValueOut() const;
// GetValueIn() is a method on CCoinsViewCache, because
diff --git a/src/init.cpp b/src/init.cpp
index b80d718f01..3488a8bedf 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -248,6 +248,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += " -maxsendbuffer=<n> " + _("Maximum per-connection send buffer, <n>*1000 bytes (default: 1000)") + "\n";
strUsage += " -onion=<ip:port> " + _("Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: -proxy)") + "\n";
strUsage += " -onlynet=<net> " + _("Only connect to nodes in network <net> (IPv4, IPv6 or Tor)") + "\n";
+ strUsage += " -permitbaremultisig " + _("Relay non-P2SH multisig (default: 1)") + "\n";
strUsage += " -port=<port> " + _("Listen for connections on <port> (default: 8333 or testnet: 18333)") + "\n";
strUsage += " -proxy=<ip:port> " + _("Connect through SOCKS5 proxy") + "\n";
strUsage += " -seednode=<ip> " + _("Connect to a node to retrieve peer addresses, and disconnect") + "\n";
@@ -676,7 +677,10 @@ bool AppInit2(boost::thread_group& threadGroup)
bSpendZeroConfChange = GetArg("-spendzeroconfchange", true);
std::string strWalletFile = GetArg("-wallet", "wallet.dat");
-#endif
+#endif // ENABLE_WALLET
+
+ fIsBareMultisigStd = GetArg("-permitbaremultisig", true);
+
// ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log
// Sanity check
if (!InitSanityCheck())
@@ -1225,7 +1229,6 @@ bool AppInit2(boost::thread_group& threadGroup)
LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0);
#endif
- InitRespendFilter();
StartNode(threadGroup);
if (fServer)
StartRPCThreads();
diff --git a/src/m4/bitcoin_qt.m4 b/src/m4/bitcoin_qt.m4
index 244b03a5c2..9356aac37f 100644
--- a/src/m4/bitcoin_qt.m4
+++ b/src/m4/bitcoin_qt.m4
@@ -86,14 +86,68 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[
fi
if test x$use_pkgconfig = xyes; then
- if test x$PKG_CONFIG == x; then
- AC_MSG_ERROR(pkg-config not found.)
- fi
BITCOIN_QT_CHECK([_BITCOIN_QT_FIND_LIBS_WITH_PKGCONFIG([$2])])
else
BITCOIN_QT_CHECK([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG])
fi
+ dnl This is ugly and complicated. Yuck. Works as follows:
+ dnl We can't discern whether Qt4 builds are static or not. For Qt5, we can
+ dnl check a header to find out. When Qt is built statically, some plugins must
+ dnl be linked into the final binary as well. These plugins have changed between
+ dnl Qt4 and Qt5. With Qt5, languages moved into core and the WindowsIntegration
+ dnl plugin was added. Since we can't tell if Qt4 is static or not, it is
+ dnl assumed for windows builds.
+ dnl _BITCOIN_QT_CHECK_STATIC_PLUGINS does a quick link-check and appends the
+ dnl results to QT_LIBS.
+ BITCOIN_QT_CHECK([
+ TEMP_CPPFLAGS=$CPPFLAGS
+ CPPFLAGS=$QT_INCLUDES
+ if test x$bitcoin_qt_got_major_vers == x5; then
+ _BITCOIN_QT_IS_STATIC
+ if test x$bitcoin_cv_static_qt == xyes; then
+ AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static])
+ if test x$qt_plugin_path != x; then
+ QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible"
+ if test x$bitcoin_qt_got_major_vers == x5; then
+ QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms"
+ else
+ QT_LIBS="$QT_LIBS -L$qt_plugin_path/codecs"
+ fi
+ fi
+ if test x$use_pkgconfig = xyes; then
+ PKG_CHECK_MODULES([QTPLATFORM], [Qt5PlatformSupport], [QT_LIBS="$QTPLATFORM_LIBS $QT_LIBS"])
+ fi
+ _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(AccessibleFactory)], [-lqtaccessiblewidgets])
+ if test x$TARGET_OS == xwindows; then
+ _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)],[-lqwindows])
+ AC_DEFINE(QT_QPA_PLATFORM_WINDOWS, 1, [Define this symbol if the qt platform is windows])
+ elif test x$TARGET_OS == xlinux; then
+ _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)],[-lqxcb -lxcb-static -lxcb])
+ AC_DEFINE(QT_QPA_PLATFORM_XCB, 1, [Define this symbol if the qt platform is xcb])
+ elif test x$TARGET_OS == xdarwin; then
+ if test x$use_pkgconfig = xyes; then
+ PKG_CHECK_MODULES([QTPRINT], [Qt5PrintSupport], [QT_LIBS="$QTPRINT_LIBS $QT_LIBS"])
+ fi
+ AX_CHECK_LINK_FLAG([[-framework IOKit]],[QT_LIBS="$QT_LIBS -framework IOKit"],[AC_MSG_ERROR(could not iokit framework)])
+ _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)],[-lqcocoa])
+ AC_DEFINE(QT_QPA_PLATFORM_COCOA, 1, [Define this symbol if the qt platform is cocoa])
+ fi
+ fi
+ else
+ if test x$TARGET_OS == xwindows; then
+ AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static])
+ _BITCOIN_QT_CHECK_STATIC_PLUGINS([
+ Q_IMPORT_PLUGIN(qcncodecs)
+ Q_IMPORT_PLUGIN(qjpcodecs)
+ Q_IMPORT_PLUGIN(qtwcodecs)
+ Q_IMPORT_PLUGIN(qkrcodecs)
+ Q_IMPORT_PLUGIN(AccessibleFactory)],
+ [-lqcncodecs -lqjpcodecs -lqtwcodecs -lqkrcodecs -lqtaccessiblewidgets])
+ fi
+ fi
+ CPPFLAGS=$TEMP_CPPFLAGS
+ ])
BITCOIN_QT_PATH_PROGS([MOC], [moc-qt${bitcoin_qt_got_major_vers} moc${bitcoin_qt_got_major_vers} moc], $qt_bin_path)
BITCOIN_QT_PATH_PROGS([UIC], [uic-qt${bitcoin_qt_got_major_vers} uic${bitcoin_qt_got_major_vers} uic], $qt_bin_path)
BITCOIN_QT_PATH_PROGS([RCC], [rcc-qt${bitcoin_qt_got_major_vers} rcc${bitcoin_qt_got_major_vers} rcc], $qt_bin_path)
@@ -303,26 +357,15 @@ AC_DEFUN([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG],[
])
BITCOIN_QT_CHECK([
- LIBS=
- if test x$qt_lib_path != x; then
- LIBS="$LIBS -L$qt_lib_path"
- fi
- if test x$qt_plugin_path != x; then
- LIBS="$LIBS -L$qt_plugin_path/accessible"
- if test x$bitcoin_qt_got_major_vers == x5; then
- LIBS="$LIBS -L$qt_plugin_path/platforms"
- else
- LIBS="$LIBS -L$qt_plugin_path/codecs"
- fi
- fi
-
if test x$TARGET_OS == xwindows; then
AC_CHECK_LIB([imm32], [main],, BITCOIN_QT_FAIL(libimm32 not found))
fi
])
- BITCOIN_QT_CHECK(AC_CHECK_LIB([z] ,[main],,BITCOIN_QT_FAIL(zlib not found)))
- BITCOIN_QT_CHECK(AC_CHECK_LIB([png] ,[main],,BITCOIN_QT_FAIL(png not found)))
+ BITCOIN_QT_CHECK(AC_CHECK_LIB([z] ,[main],,AC_MSG_WARN([zlib not found. Assuming qt has it built-in])))
+ BITCOIN_QT_CHECK(AC_CHECK_LIB([png] ,[main],,AC_MSG_WARN([libpng not found. Assuming qt has it built-in])))
+ BITCOIN_QT_CHECK(AC_CHECK_LIB([jpeg] ,[main],,AC_MSG_WARN([libjpeg not found. Assuming qt has it built-in])))
+ BITCOIN_QT_CHECK(AC_CHECK_LIB([pcre] ,[main],,AC_MSG_WARN([libpcre not found. Assuming qt has it built-in])))
BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Core] ,[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXCore not found)))
BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Gui] ,[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXGui not found)))
BITCOIN_QT_CHECK(AC_CHECK_LIB([${QT_LIB_PREFIX}Network],[main],,BITCOIN_QT_FAIL(lib$QT_LIB_PREFIXNetwork not found)))
@@ -332,37 +375,6 @@ AC_DEFUN([_BITCOIN_QT_FIND_LIBS_WITHOUT_PKGCONFIG],[
QT_LIBS="$LIBS"
LIBS="$TEMP_LIBS"
- dnl This is ugly and complicated. Yuck. Works as follows:
- dnl We can't discern whether Qt4 builds are static or not. For Qt5, we can
- dnl check a header to find out. When Qt is built statically, some plugins must
- dnl be linked into the final binary as well. These plugins have changed between
- dnl Qt4 and Qt5. With Qt5, languages moved into core and the WindowsIntegration
- dnl plugin was added. Since we can't tell if Qt4 is static or not, it is
- dnl assumed for all non-pkg-config builds.
- dnl _BITCOIN_QT_CHECK_STATIC_PLUGINS does a quick link-check and appends the
- dnl results to QT_LIBS.
- BITCOIN_QT_CHECK([
- if test x$bitcoin_qt_got_major_vers == x5; then
- _BITCOIN_QT_IS_STATIC
- if test x$bitcoin_cv_static_qt == xyes; then
- AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static])
- _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(AccessibleFactory)], [-lqtaccessiblewidgets])
- if test x$TARGET_OS == xwindows; then
- _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)],[-lqwindows])
- fi
- fi
- else
- AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static])
- _BITCOIN_QT_CHECK_STATIC_PLUGINS([
- Q_IMPORT_PLUGIN(qcncodecs)
- Q_IMPORT_PLUGIN(qjpcodecs)
- Q_IMPORT_PLUGIN(qtwcodecs)
- Q_IMPORT_PLUGIN(qkrcodecs)
- Q_IMPORT_PLUGIN(AccessibleFactory)],
- [-lqcncodecs -lqjpcodecs -lqtwcodecs -lqkrcodecs -lqtaccessiblewidgets])
- fi
- ])
-
BITCOIN_QT_CHECK([
LIBS=
if test x$qt_lib_path != x; then
diff --git a/src/main.cpp b/src/main.cpp
index 766f0970f0..06ce15b5b3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -7,7 +7,6 @@
#include "addrman.h"
#include "alert.h"
-#include "bloom.h"
#include "chainparams.h"
#include "checkpoints.h"
#include "checkqueue.h"
@@ -48,6 +47,7 @@ bool fImporting = false;
bool fReindex = false;
bool fBenchmark = false;
bool fTxIndex = false;
+bool fIsBareMultisigStd = true;
unsigned int nCoinCacheSize = 5000;
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
@@ -125,15 +125,6 @@ namespace {
} // anon namespace
-// Bloom filter to limit respend relays to one
-static const unsigned int MAX_DOUBLESPEND_BLOOM = 1000;
-static CBloomFilter doubleSpendFilter;
-void InitRespendFilter() {
- seed_insecure_rand();
- doubleSpendFilter = CBloomFilter(MAX_DOUBLESPEND_BLOOM, 0.01, insecure_rand(), BLOOM_UPDATE_NONE);
-}
-
-
//////////////////////////////////////////////////////////////////////////////
//
// dispatching functions
@@ -160,7 +151,6 @@ struct CMainSignals {
} // anon namespace
-
void RegisterWallet(CWalletInterface* pwalletIn) {
g_signals.SyncTransaction.connect(boost::bind(&CWalletInterface::SyncTransaction, pwalletIn, _1, _2));
g_signals.EraseTransaction.connect(boost::bind(&CWalletInterface::EraseFromWallet, pwalletIn, _1));
@@ -604,9 +594,13 @@ bool IsStandardTx(const CTransaction& tx, string& reason)
reason = "scriptpubkey";
return false;
}
+
if (whichType == TX_NULL_DATA)
nDataOut++;
- else if (txout.IsDust(::minRelayTxFee)) {
+ else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) {
+ reason = "bare-multisig";
+ return false;
+ } else if (txout.IsDust(::minRelayTxFee)) {
reason = "dust";
return false;
}
@@ -876,60 +870,6 @@ int64_t GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowF
return nMinFee;
}
-// Exponentially limit the rate of nSize flow to nLimit. nLimit unit is thousands-per-minute.
-bool RateLimitExceeded(double& dCount, int64_t& nLastTime, int64_t nLimit, unsigned int nSize)
-{
- static CCriticalSection csLimiter;
- int64_t nNow = GetTime();
-
- LOCK(csLimiter);
-
- dCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime));
- nLastTime = nNow;
- if (dCount >= nLimit*10*1000)
- return true;
- dCount += nSize;
- return false;
-}
-
-static bool RelayableRespend(const COutPoint& outPoint, const CTransaction& doubleSpend, bool fInBlock, CBloomFilter& filter)
-{
- // Relaying double-spend attempts to our peers lets them detect when
- // somebody might be trying to cheat them. However, blindly relaying
- // every double-spend across the entire network gives attackers
- // a denial-of-service attack: just generate a stream of double-spends
- // re-spending the same (limited) set of outpoints owned by the attacker.
- // So, we use a bloom filter and only relay (at most) the first double
- // spend for each outpoint. False-positives ("we have already relayed")
- // are OK, because if the peer doesn't hear about the double-spend
- // from us they are very likely to hear about it from another peer, since
- // each peer uses a different, randomized bloom filter.
-
- if (fInBlock || filter.contains(outPoint)) return false;
-
- // Apply an independent rate limit to double-spend relays
- static double dRespendCount;
- static int64_t nLastRespendTime;
- static int64_t nRespendLimit = GetArg("-limitrespendrelay", 100);
- unsigned int nSize = ::GetSerializeSize(doubleSpend, SER_NETWORK, PROTOCOL_VERSION);
-
- if (RateLimitExceeded(dRespendCount, nLastRespendTime, nRespendLimit, nSize))
- {
- LogPrint("mempool", "Double-spend relay rejected by rate limiter\n");
- return false;
- }
-
- LogPrint("mempool", "Rate limit dRespendCount: %g => %g\n", dRespendCount, dRespendCount+nSize);
-
- // Clear the filter on average every MAX_DOUBLE_SPEND_BLOOM
- // insertions
- if (insecure_rand()%MAX_DOUBLESPEND_BLOOM == 0)
- filter.clear();
-
- filter.insert(outPoint);
-
- return true;
-}
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
bool* pfMissingInputs, bool fRejectInsaneFee)
@@ -959,18 +899,15 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return false;
// Check for conflicts with in-memory transactions
- bool relayableRespend = false;
{
LOCK(pool.cs); // protect pool.mapNextTx
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
COutPoint outpoint = tx.vin[i].prevout;
- // Does tx conflict with a member of the pool, and is it not equivalent to that member?
- if (pool.mapNextTx.count(outpoint) && !tx.IsEquivalentTo(*pool.mapNextTx[outpoint].ptx))
+ if (pool.mapNextTx.count(outpoint))
{
- relayableRespend = RelayableRespend(outpoint, tx, false, doubleSpendFilter);
- if (!relayableRespend)
- return false;
+ // Disable replacement feature for now
+ return false;
}
}
}
@@ -1041,15 +978,23 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// be annoying or make others' transactions take longer to confirm.
if (fLimitFree && nFees < ::minRelayTxFee.GetFee(nSize))
{
+ static CCriticalSection csFreeLimiter;
static double dFreeCount;
- static int64_t nLastFreeTime;
- static int64_t nFreeLimit = GetArg("-limitfreerelay", 15);
+ static int64_t nLastTime;
+ int64_t nNow = GetTime();
- if (RateLimitExceeded(dFreeCount, nLastFreeTime, nFreeLimit, nSize))
+ LOCK(csFreeLimiter);
+
+ // Use an exponentially decaying ~10-minute window:
+ dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime));
+ nLastTime = nNow;
+ // -limitfreerelay unit is thousand-bytes-per-minute
+ // At default rate it would take over a month to fill 1GB
+ if (dFreeCount >= GetArg("-limitfreerelay", 15)*10*1000)
return state.DoS(0, error("AcceptToMemoryPool : free transaction rejected by rate limiter"),
REJECT_INSUFFICIENTFEE, "insufficient priority");
-
LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
+ dFreeCount += nSize;
}
if (fRejectInsaneFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000)
@@ -1063,21 +1008,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
{
return error("AcceptToMemoryPool: : ConnectInputs failed %s", hash.ToString());
}
-
- if (relayableRespend)
- {
- RelayTransaction(tx);
- }
- else
- {
- // Store transaction in memory
- pool.addUnchecked(hash, entry);
- }
+ // Store transaction in memory
+ pool.addUnchecked(hash, entry);
}
g_signals.SyncTransaction(tx, NULL);
- return !relayableRespend;
+ return true;
}
@@ -2079,7 +2016,7 @@ static CBlockIndex* FindMostWorkChain() {
CBlockIndex *pindexTest = pindexNew;
bool fInvalidAncestor = false;
while (pindexTest && !chainActive.Contains(pindexTest)) {
- if (!pindexTest->IsValid(BLOCK_VALID_TRANSACTIONS) || !(pindexTest->nStatus & BLOCK_HAVE_DATA)) {
+ if (pindexTest->nStatus & BLOCK_FAILED_MASK) {
// Candidate has an invalid ancestor, remove entire chain from the set.
if (pindexBestInvalid == NULL || pindexNew->nChainWork > pindexBestInvalid->nChainWork)
pindexBestInvalid = pindexNew;
@@ -2089,6 +2026,7 @@ static CBlockIndex* FindMostWorkChain() {
setBlockIndexValid.erase(pindexFailed);
pindexFailed = pindexFailed->pprev;
}
+ setBlockIndexValid.erase(pindexTest);
fInvalidAncestor = true;
break;
}
diff --git a/src/main.h b/src/main.h
index a683412571..5f231fa45b 100644
--- a/src/main.h
+++ b/src/main.h
@@ -94,6 +94,7 @@ extern bool fReindex;
extern bool fBenchmark;
extern int nScriptCheckThreads;
extern bool fTxIndex;
+extern bool fIsBareMultisigStd;
extern unsigned int nCoinCacheSize;
extern CFeeRate minRelayTxFee;
@@ -111,9 +112,6 @@ struct CNodeStateStats;
struct CBlockTemplate;
-/** Initialize respend bloom filter **/
-void InitRespendFilter();
-
/** Register a wallet to receive updates from core */
void RegisterWallet(CWalletInterface* pwalletIn);
/** Unregister a wallet from core */
diff --git a/src/net.cpp b/src/net.cpp
index e0f0f14f2b..e004fbeb73 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -456,7 +456,7 @@ CNode* FindNode(const CNetAddr& ip)
return NULL;
}
-CNode* FindNode(std::string addrName)
+CNode* FindNode(const std::string& addrName)
{
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes)
@@ -1472,7 +1472,7 @@ bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOu
if (!pszDest) {
if (IsLocal(addrConnect) ||
FindNode((CNetAddr)addrConnect) || CNode::IsBanned(addrConnect) ||
- FindNode(addrConnect.ToStringIPPort().c_str()))
+ FindNode(addrConnect.ToStringIPPort()))
return false;
} else if (FindNode(pszDest))
return false;
@@ -1667,6 +1667,7 @@ bool BindListenPort(const CService &addrBind, string& strError, bool fWhiteliste
else
strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr));
LogPrintf("%s\n", strError);
+ CloseSocket(hListenSocket);
return false;
}
LogPrintf("Bound to %s\n", addrBind.ToString());
@@ -1676,6 +1677,7 @@ bool BindListenPort(const CService &addrBind, string& strError, bool fWhiteliste
{
strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError);
+ CloseSocket(hListenSocket);
return false;
}
diff --git a/src/net.h b/src/net.h
index f029a8d935..2d9325abf9 100644
--- a/src/net.h
+++ b/src/net.h
@@ -59,6 +59,7 @@ bool RecvLine(SOCKET hSocket, std::string& strLine);
bool GetMyExternalIP(CNetAddr& ipRet);
void AddressCurrentlyConnected(const CService& addr);
CNode* FindNode(const CNetAddr& ip);
+CNode* FindNode(const std::string& addrName);
CNode* FindNode(const CService& ip);
CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL);
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false);
diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp
index 5df8f19729..f336d47e83 100644
--- a/src/qt/addressbookpage.cpp
+++ b/src/qt/addressbookpage.cpp
@@ -282,7 +282,7 @@ void AddressBookPage::on_exportButton_clicked()
if(!writer.write()) {
QMessageBox::critical(this, tr("Exporting Failed"),
- tr("There was an error trying to save the address list to %1.").arg(filename));
+ tr("There was an error trying to save the address list to %1. Please try again.").arg(filename));
}
}
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 43466663fa..7bf531f538 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -53,7 +53,13 @@ Q_IMPORT_PLUGIN(qkrcodecs)
Q_IMPORT_PLUGIN(qtaccessiblewidgets)
#else
Q_IMPORT_PLUGIN(AccessibleFactory)
+#if defined(QT_QPA_PLATFORM_XCB)
+Q_IMPORT_PLUGIN(QXcbIntegrationPlugin);
+#elif defined(QT_QPA_PLATFORM_WINDOWS)
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
+#elif defined(QT_QPA_PLATFORM_COCOA)
+Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin);
+#endif
#endif
#endif
@@ -400,8 +406,6 @@ void BitcoinApplication::initializeResult(int retval)
paymentServer->setOptionsModel(optionsModel);
#endif
- emit splashFinished(window);
-
clientModel = new ClientModel(optionsModel);
window->setClientModel(clientModel);
@@ -418,6 +422,8 @@ void BitcoinApplication::initializeResult(int retval)
}
#endif
+ emit splashFinished(window);
+
// If -min option passed, start window minimized.
if(GetBoolArg("-min", false))
{
diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp
index 25ad0c66af..6466039013 100644
--- a/src/qt/bitcoinamountfield.cpp
+++ b/src/qt/bitcoinamountfield.cpp
@@ -9,19 +9,186 @@
#include "qvaluecombobox.h"
#include <QApplication>
-#include <QDoubleSpinBox>
+#include <QAbstractSpinBox>
#include <QHBoxLayout>
#include <QKeyEvent>
-#include <qmath.h> // for qPow()
+#include <QLineEdit>
+
+/** QSpinBox that uses fixed-point numbers internally and uses our own
+ * formatting/parsing functions.
+ */
+class AmountSpinBox: public QAbstractSpinBox
+{
+ Q_OBJECT
+public:
+ explicit AmountSpinBox(QWidget *parent):
+ QAbstractSpinBox(parent),
+ currentUnit(BitcoinUnits::BTC),
+ singleStep(100000) // satoshis
+ {
+ setAlignment(Qt::AlignRight);
+
+ connect(lineEdit(), SIGNAL(textEdited(QString)), this, SIGNAL(valueChanged()));
+ }
+
+ QValidator::State validate(QString &text, int &pos) const
+ {
+ if(text.isEmpty())
+ return QValidator::Intermediate;
+ bool valid = false;
+ parse(text, &valid);
+ /* Make sure we return Intermediate so that fixup() is called on defocus */
+ return valid ? QValidator::Intermediate : QValidator::Invalid;
+ }
+
+ void fixup(QString &input) const
+ {
+ bool valid = false;
+ qint64 val = parse(input, &valid);
+ if(valid)
+ {
+ input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways);
+ lineEdit()->setText(input);
+ }
+ }
+
+ qint64 value(bool *valid_out=0) const
+ {
+ return parse(text(), valid_out);
+ }
+
+ void setValue(qint64 value)
+ {
+ lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, BitcoinUnits::separatorAlways));
+ emit valueChanged();
+ }
+
+ void stepBy(int steps)
+ {
+ bool valid = false;
+ qint64 val = value(&valid);
+ val = val + steps * singleStep;
+ val = qMin(qMax(val, Q_INT64_C(0)), BitcoinUnits::maxMoney());
+ setValue(val);
+ }
+
+ StepEnabled stepEnabled() const
+ {
+ StepEnabled rv = 0;
+ if(text().isEmpty()) // Allow step-up with empty field
+ return StepUpEnabled;
+ bool valid = false;
+ qint64 val = value(&valid);
+ if(valid)
+ {
+ if(val > 0)
+ rv |= StepDownEnabled;
+ if(val < BitcoinUnits::maxMoney())
+ rv |= StepUpEnabled;
+ }
+ return rv;
+ }
+
+ void setDisplayUnit(int unit)
+ {
+ bool valid = false;
+ qint64 val = value(&valid);
+
+ currentUnit = unit;
+
+ if(valid)
+ setValue(val);
+ else
+ clear();
+ }
+
+ void setSingleStep(qint64 step)
+ {
+ singleStep = step;
+ }
+
+ QSize minimumSizeHint() const
+ {
+ if(cachedMinimumSizeHint.isEmpty())
+ {
+ ensurePolished();
+
+ const QFontMetrics fm(fontMetrics());
+ int h = lineEdit()->minimumSizeHint().height();
+ int w = fm.width(BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways));
+ w += 2; // cursor blinking space
+
+ QStyleOptionSpinBox opt;
+ initStyleOption(&opt);
+ QSize hint(w, h);
+ QSize extra(35, 6);
+ opt.rect.setSize(hint + extra);
+ extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
+ QStyle::SC_SpinBoxEditField, this).size();
+ // get closer to final result by repeating the calculation
+ opt.rect.setSize(hint + extra);
+ extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &opt,
+ QStyle::SC_SpinBoxEditField, this).size();
+ hint += extra;
+
+ opt.rect = rect();
+
+ cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this)
+ .expandedTo(QApplication::globalStrut());
+ }
+ return cachedMinimumSizeHint;
+ }
+private:
+ int currentUnit;
+ qint64 singleStep;
+ mutable QSize cachedMinimumSizeHint;
+
+ /**
+ * Parse a string into a number of base monetary units and
+ * return validity.
+ * @note Must return 0 if !valid.
+ */
+ qint64 parse(const QString &text, bool *valid_out=0) const
+ {
+ qint64 val = 0;
+ bool valid = BitcoinUnits::parse(currentUnit, text, &val);
+ if(valid)
+ {
+ if(val < 0 || val > BitcoinUnits::maxMoney())
+ valid = false;
+ }
+ if(valid_out)
+ *valid_out = valid;
+ return valid ? val : 0;
+ }
+
+protected:
+ bool event(QEvent *event)
+ {
+ if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
+ {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ if (keyEvent->key() == Qt::Key_Comma)
+ {
+ // Translate a comma into a period
+ QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
+ return QAbstractSpinBox::event(&periodKeyEvent);
+ }
+ }
+ return QAbstractSpinBox::event(event);
+ }
+
+signals:
+ void valueChanged();
+};
+
+#include "bitcoinamountfield.moc"
BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
QWidget(parent),
- amount(0),
- currentUnit(-1)
+ amount(0)
{
- nSingleStep = 100000; // satoshis
-
- amount = new QDoubleSpinBox(this);
+ amount = new AmountSpinBox(this);
amount->setLocale(QLocale::c());
amount->installEventFilter(this);
amount->setMaximumWidth(170);
@@ -40,21 +207,13 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
setFocusProxy(amount);
// If one if the widgets changes, the combined content changes as well
- connect(amount, SIGNAL(valueChanged(QString)), this, SIGNAL(textChanged()));
+ connect(amount, SIGNAL(valueChanged()), this, SIGNAL(valueChanged()));
connect(unit, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int)));
// Set default based on configuration
unitChanged(unit->currentIndex());
}
-void BitcoinAmountField::setText(const QString &text)
-{
- if (text.isEmpty())
- amount->clear();
- else
- amount->setValue(text.toDouble());
-}
-
void BitcoinAmountField::clear()
{
amount->clear();
@@ -63,16 +222,9 @@ void BitcoinAmountField::clear()
bool BitcoinAmountField::validate()
{
- bool valid = true;
- if (amount->value() == 0.0)
- valid = false;
- else if (!BitcoinUnits::parse(currentUnit, text(), 0))
- valid = false;
- else if (amount->value() > BitcoinUnits::maxAmount(currentUnit))
- valid = false;
-
+ bool valid = false;
+ value(&valid);
setValid(valid);
-
return valid;
}
@@ -84,14 +236,6 @@ void BitcoinAmountField::setValid(bool valid)
amount->setStyleSheet(STYLE_INVALID);
}
-QString BitcoinAmountField::text() const
-{
- if (amount->text().isEmpty())
- return QString();
- else
- return amount->text();
-}
-
bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::FocusIn)
@@ -99,17 +243,6 @@ bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
// Clear invalid flag on focus
setValid(true);
}
- else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
- {
- QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
- if (keyEvent->key() == Qt::Key_Comma)
- {
- // Translate a comma into a period
- QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
- QApplication::sendEvent(object, &periodKeyEvent);
- return true;
- }
- }
return QWidget::eventFilter(object, event);
}
@@ -122,18 +255,12 @@ QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
qint64 BitcoinAmountField::value(bool *valid_out) const
{
- qint64 val_out = 0;
- bool valid = BitcoinUnits::parse(currentUnit, text(), &val_out);
- if (valid_out)
- {
- *valid_out = valid;
- }
- return val_out;
+ return amount->value(valid_out);
}
void BitcoinAmountField::setValue(qint64 value)
{
- setText(BitcoinUnits::format(currentUnit, value));
+ amount->setValue(value);
}
void BitcoinAmountField::setReadOnly(bool fReadOnly)
@@ -150,28 +277,7 @@ void BitcoinAmountField::unitChanged(int idx)
// Determine new unit ID
int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt();
- // Parse current value and convert to new unit
- bool valid = false;
- qint64 currentValue = value(&valid);
-
- currentUnit = newUnit;
-
- // Set max length after retrieving the value, to prevent truncation
- amount->setDecimals(BitcoinUnits::decimals(currentUnit));
- amount->setMaximum(qPow(10, BitcoinUnits::amountDigits(currentUnit)) - qPow(10, -amount->decimals()));
- amount->setSingleStep((double)nSingleStep / (double)BitcoinUnits::factor(currentUnit));
-
- if (valid)
- {
- // If value was valid, re-place it in the widget with the new unit
- setValue(currentValue);
- }
- else
- {
- // If current value is invalid, just clear field
- setText("");
- }
- setValid(true);
+ amount->setDisplayUnit(newUnit);
}
void BitcoinAmountField::setDisplayUnit(int newUnit)
@@ -181,6 +287,5 @@ void BitcoinAmountField::setDisplayUnit(int newUnit)
void BitcoinAmountField::setSingleStep(qint64 step)
{
- nSingleStep = step;
- unitChanged(unit->currentIndex());
+ amount->setSingleStep(step);
}
diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h
index 521a9ed561..c713f5d687 100644
--- a/src/qt/bitcoinamountfield.h
+++ b/src/qt/bitcoinamountfield.h
@@ -8,17 +8,18 @@
#include <QWidget>
QT_BEGIN_NAMESPACE
-class QDoubleSpinBox;
class QValueComboBox;
QT_END_NAMESPACE
+class AmountSpinBox;
+
/** Widget for entering bitcoin amounts.
*/
class BitcoinAmountField: public QWidget
{
Q_OBJECT
- Q_PROPERTY(qint64 value READ value WRITE setValue NOTIFY textChanged USER true)
+ Q_PROPERTY(qint64 value READ value WRITE setValue NOTIFY valueChanged USER true)
public:
explicit BitcoinAmountField(QWidget *parent = 0);
@@ -49,20 +50,15 @@ public:
QWidget *setupTabChain(QWidget *prev);
signals:
- void textChanged();
+ void valueChanged();
protected:
/** Intercept focus-in event and ',' key presses */
bool eventFilter(QObject *object, QEvent *event);
private:
- QDoubleSpinBox *amount;
+ AmountSpinBox *amount;
QValueComboBox *unit;
- int currentUnit;
- qint64 nSingleStep;
-
- void setText(const QString &text);
- QString text() const;
private slots:
void unitChanged(int idx);
diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp
index bbc9b2e5af..6f506d3f25 100644
--- a/src/qt/bitcoinunits.cpp
+++ b/src/qt/bitcoinunits.cpp
@@ -4,6 +4,8 @@
#include "bitcoinunits.h"
+#include "core.h"
+
#include <QStringList>
BitcoinUnits::BitcoinUnits(QObject *parent):
@@ -61,8 +63,8 @@ QString BitcoinUnits::description(int unit)
switch(unit)
{
case BTC: return QString("Bitcoins");
- case mBTC: return QString("Milli-Bitcoins (1 / 1,000)");
- case uBTC: return QString("Micro-Bitcoins (1 / 1,000,000)");
+ case mBTC: return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)");
+ case uBTC: return QString("Micro-Bitcoins (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)");
default: return QString("???");
}
}
@@ -78,28 +80,6 @@ qint64 BitcoinUnits::factor(int unit)
}
}
-qint64 BitcoinUnits::maxAmount(int unit)
-{
- switch(unit)
- {
- case BTC: return Q_INT64_C(21000000);
- case mBTC: return Q_INT64_C(21000000000);
- case uBTC: return Q_INT64_C(21000000000000);
- default: return 0;
- }
-}
-
-int BitcoinUnits::amountDigits(int unit)
-{
- switch(unit)
- {
- case BTC: return 8; // 21,000,000 (# digits, without commas)
- case mBTC: return 11; // 21,000,000,000
- case uBTC: return 14; // 21,000,000,000,000
- default: return 0;
- }
-}
-
int BitcoinUnits::decimals(int unit)
{
switch(unit)
@@ -111,7 +91,7 @@ int BitcoinUnits::decimals(int unit)
}
}
-QString BitcoinUnits::format(int unit, qint64 n, bool fPlus)
+QString BitcoinUnits::format(int unit, qint64 n, bool fPlus, SeparatorStyle separators)
{
// Note: not using straight sprintf here because we do NOT want
// localized number formatting.
@@ -125,11 +105,20 @@ QString BitcoinUnits::format(int unit, qint64 n, bool fPlus)
QString quotient_str = QString::number(quotient);
QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0');
- // Right-trim excess zeros after the decimal point
- int nTrim = 0;
- for (int i = remainder_str.size()-1; i>=2 && (remainder_str.at(i) == '0'); --i)
- ++nTrim;
- remainder_str.chop(nTrim);
+ // Use SI-stule separators as these are locale indendent and can't be
+ // confused with the decimal marker. Rule is to use a thin space every
+ // three digits on *both* sides of the decimal point - but only if there
+ // are five or more digits
+ QChar thin_sp(THIN_SP_CP);
+ int q_size = quotient_str.size();
+ if (separators == separatorAlways || (separators == separatorStandard && q_size > 4))
+ for (int i = 3; i < q_size; i += 3)
+ quotient_str.insert(q_size - i, thin_sp);
+
+ int r_size = remainder_str.size();
+ if (separators == separatorAlways || (separators == separatorStandard && r_size > 4))
+ for (int i = 3, adj = 0; i < r_size ; i += 3, adj++)
+ remainder_str.insert(i + adj, thin_sp);
if (n < 0)
quotient_str.insert(0, '-');
@@ -138,17 +127,43 @@ QString BitcoinUnits::format(int unit, qint64 n, bool fPlus)
return quotient_str + QString(".") + remainder_str;
}
-QString BitcoinUnits::formatWithUnit(int unit, qint64 amount, bool plussign)
+
+// TODO: Review all remaining calls to BitcoinUnits::formatWithUnit to
+// TODO: determine whether the output is used in a plain text context
+// TODO: or an HTML context (and replace with
+// TODO: BtcoinUnits::formatHtmlWithUnit in the latter case). Hopefully
+// TODO: there aren't instances where the result could be used in
+// TODO: either context.
+
+// NOTE: Using formatWithUnit in an HTML context risks wrapping
+// quantities at the thousands separator. More subtly, it also results
+// in a standard space rather than a thin space, due to a bug in Qt's
+// XML whitespace canonicalisation
+//
+// Please take care to use formatHtmlWithUnit instead, when
+// appropriate.
+
+QString BitcoinUnits::formatWithUnit(int unit, qint64 amount, bool plussign, SeparatorStyle separators)
+{
+ return format(unit, amount, plussign, separators) + QString(" ") + name(unit);
+}
+
+QString BitcoinUnits::formatHtmlWithUnit(int unit, qint64 amount, bool plussign, SeparatorStyle separators)
{
- return format(unit, amount, plussign) + QString(" ") + name(unit);
+ QString str(formatWithUnit(unit, amount, plussign, separators));
+ str.replace(QChar(THIN_SP_CP), QString(THIN_SP_HTML));
+ return QString("<span style='white-space: nowrap;'>%1</span>").arg(str);
}
+
bool BitcoinUnits::parse(int unit, const QString &value, qint64 *val_out)
{
if(!valid(unit) || value.isEmpty())
return false; // Refuse to parse invalid unit or empty string
int num_decimals = decimals(unit);
- QStringList parts = value.split(".");
+
+ // Ignore spaces and thin spaces when parsing
+ QStringList parts = removeSpaces(value).split(".");
if(parts.size() > 2)
{
@@ -215,3 +230,8 @@ QVariant BitcoinUnits::data(const QModelIndex &index, int role) const
}
return QVariant();
}
+
+qint64 BitcoinUnits::maxMoney()
+{
+ return MAX_MONEY;
+}
diff --git a/src/qt/bitcoinunits.h b/src/qt/bitcoinunits.h
index da34ed8976..be9dca6012 100644
--- a/src/qt/bitcoinunits.h
+++ b/src/qt/bitcoinunits.h
@@ -8,6 +8,37 @@
#include <QAbstractListModel>
#include <QString>
+// U+2009 THIN SPACE = UTF-8 E2 80 89
+#define REAL_THIN_SP_CP 0x2009
+#define REAL_THIN_SP_UTF8 "\xE2\x80\x89"
+#define REAL_THIN_SP_HTML "&thinsp;"
+
+// U+200A HAIR SPACE = UTF-8 E2 80 8A
+#define HAIR_SP_CP 0x200A
+#define HAIR_SP_UTF8 "\xE2\x80\x8A"
+#define HAIR_SP_HTML "&#8202;"
+
+// U+2006 SIX-PER-EM SPACE = UTF-8 E2 80 86
+#define SIXPEREM_SP_CP 0x2006
+#define SIXPEREM_SP_UTF8 "\xE2\x80\x86"
+#define SIXPEREM_SP_HTML "&#8198;"
+
+// U+2007 FIGURE SPACE = UTF-8 E2 80 87
+#define FIGURE_SP_CP 0x2007
+#define FIGURE_SP_UTF8 "\xE2\x80\x87"
+#define FIGURE_SP_HTML "&#8199;"
+
+// QMessageBox seems to have a bug whereby it doesn't display thin/hair spaces
+// correctly. Workaround is to display a space in a small font. If you
+// change this, please test that it doesn't cause the parent span to start
+// wrapping.
+#define HTML_HACK_SP "<span style='white-space: nowrap; font-size: 6pt'> </span>"
+
+// Define THIN_SP_* variables to be our preferred type of thin space
+#define THIN_SP_CP REAL_THIN_SP_CP
+#define THIN_SP_UTF8 REAL_THIN_SP_UTF8
+#define THIN_SP_HTML HTML_HACK_SP
+
/** Bitcoin unit definitions. Encapsulates parsing and formatting
and serves as list model for drop-down selection boxes.
*/
@@ -28,6 +59,13 @@ public:
uBTC
};
+ enum SeparatorStyle
+ {
+ separatorNever,
+ separatorStandard,
+ separatorAlways
+ };
+
//! @name Static API
//! Unit conversion and formatting
///@{
@@ -44,16 +82,13 @@ public:
static QString description(int unit);
//! Number of Satoshis (1e-8) per unit
static qint64 factor(int unit);
- //! Max amount per unit
- static qint64 maxAmount(int unit);
- //! Number of amount digits (to represent max number of coins)
- static int amountDigits(int unit);
//! Number of decimals left
static int decimals(int unit);
//! Format as string
- static QString format(int unit, qint64 amount, bool plussign=false);
+ static QString format(int unit, qint64 amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
//! Format as string (with unit)
- static QString formatWithUnit(int unit, qint64 amount, bool plussign=false);
+ static QString formatWithUnit(int unit, qint64 amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
+ static QString formatHtmlWithUnit(int unit, qint64 amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
//! Parse string to coin amount
static bool parse(int unit, const QString &value, qint64 *val_out);
//! Gets title for amount column including current display unit if optionsModel reference available */
@@ -71,6 +106,19 @@ public:
QVariant data(const QModelIndex &index, int role) const;
///@}
+ static QString removeSpaces(QString text)
+ {
+ text.remove(' ');
+ text.remove(QChar(THIN_SP_CP));
+#if (THIN_SP_CP != REAL_THIN_SP_CP)
+ text.remove(QChar(REAL_THIN_SP_CP));
+#endif
+ return text;
+ }
+
+ //! Return maximum number of base units (Satoshis)
+ static qint64 maxMoney();
+
private:
QList<BitcoinUnits::Unit> unitlist;
};
diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp
index b4ddda3eaa..7b30f8de09 100644
--- a/src/qt/coincontroldialog.cpp
+++ b/src/qt/coincontroldialog.cpp
@@ -225,7 +225,7 @@ void CoinControlDialog::showMenu(const QPoint &point)
// context menu action: copy amount
void CoinControlDialog::copyAmount()
{
- GUIUtil::setClipboard(contextMenuItem->text(COLUMN_AMOUNT));
+ GUIUtil::setClipboard(BitcoinUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT)));
}
// context menu action: copy label
diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h
index 696761e234..5ae4bc833d 100644
--- a/src/qt/guiconstants.h
+++ b/src/qt/guiconstants.h
@@ -23,10 +23,6 @@ static const int STATUSBAR_ICONSIZE = 16;
#define COLOR_NEGATIVE QColor(255, 0, 0)
/* Transaction list -- bare address (without label) */
#define COLOR_BAREADDRESS QColor(140, 140, 140)
-/* Transaction list -- has conflicting transactions */
-#define COLOR_HASCONFLICTING QColor(255, 255, 255)
-/* Transaction list -- has conflicting transactions - background */
-#define COLOR_HASCONFLICTING_BG QColor(192, 0, 0)
/* Tooltips longer than this (in characters) are converted into rich text,
so that they can be word-wrapped.
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 60a131df7e..33a50a078d 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -187,7 +187,7 @@ QString formatBitcoinURI(const SendCoinsRecipient &info)
if (info.amount)
{
- ret += QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::BTC, info.amount));
+ ret += QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::BTC, info.amount, false, BitcoinUnits::separatorNever));
paramCount++;
}
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 597be40abd..0117d2e633 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -15,11 +15,11 @@
#include "optionsmodel.h"
#include "main.h" // for MAX_SCRIPTCHECK_THREADS
+#include "netbase.h"
+#include "txdb.h" // for -dbcache defaults
#ifdef ENABLE_WALLET
#include "wallet.h" // for CWallet::minTxFee
#endif
-#include "netbase.h"
-#include "txdb.h" // for -dbcache defaults
#include <QDir>
#include <QIntValidator>
diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp
index 1278f368cf..1c700b37ff 100644
--- a/src/qt/overviewpage.cpp
+++ b/src/qt/overviewpage.cpp
@@ -72,7 +72,7 @@ public:
foreground = option.palette.color(QPalette::Text);
}
painter->setPen(foreground);
- QString amountText = BitcoinUnits::formatWithUnit(unit, amount, true);
+ QString amountText = BitcoinUnits::formatWithUnit(unit, amount, true, BitcoinUnits::separatorAlways);
if(!confirmed)
{
amountText = QString("[") + amountText + QString("]");
@@ -147,14 +147,14 @@ void OverviewPage::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64
currentWatchOnlyBalance = watchOnlyBalance;
currentWatchUnconfBalance = watchUnconfBalance;
currentWatchImmatureBalance = watchImmatureBalance;
- ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance));
- ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, unconfirmedBalance));
- ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, immatureBalance));
- ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balance + unconfirmedBalance + immatureBalance));
- ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance));
- ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, watchUnconfBalance));
- ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, watchImmatureBalance));
- ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance + watchUnconfBalance + watchImmatureBalance));
+ ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::separatorAlways));
+ ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, unconfirmedBalance, false, BitcoinUnits::separatorAlways));
+ ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, immatureBalance, false, BitcoinUnits::separatorAlways));
+ ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balance + unconfirmedBalance + immatureBalance, false, BitcoinUnits::separatorAlways));
+ ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance, false, BitcoinUnits::separatorAlways));
+ ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, watchUnconfBalance, false, BitcoinUnits::separatorAlways));
+ ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, watchImmatureBalance, false, BitcoinUnits::separatorAlways));
+ ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, watchOnlyBalance + watchUnconfBalance + watchImmatureBalance, false, BitcoinUnits::separatorAlways));
// only show immature (newly mined) balance if it's non-zero, so as not to complicate things
// for the non-mining users
diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp
index b5a998f9f5..9e3976644e 100644
--- a/src/qt/recentrequeststablemodel.cpp
+++ b/src/qt/recentrequeststablemodel.cpp
@@ -79,10 +79,17 @@ QVariant RecentRequestsTableModel::data(const QModelIndex &index, int role) cons
case Amount:
if (rec->recipient.amount == 0 && role == Qt::DisplayRole)
return tr("(no amount)");
+ else if (role == Qt::EditRole)
+ return BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), rec->recipient.amount, false, BitcoinUnits::separatorNever);
else
return BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), rec->recipient.amount);
}
}
+ else if (role == Qt::TextAlignmentRole)
+ {
+ if (index.column() == Amount)
+ return (int)(Qt::AlignRight|Qt::AlignVCenter);
+ }
return QVariant();
}
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 6f10ed5b0b..25e3d2a0dc 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -143,7 +143,7 @@ void SendCoinsDialog::on_sendButton_clicked()
foreach(const SendCoinsRecipient &rcp, recipients)
{
// generate bold amount string
- QString amount = "<b>" + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
+ QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
amount.append("</b>");
// generate monospace address string
QString address = "<span style='font-family: monospace;'>" + rcp.address;
@@ -211,7 +211,7 @@ void SendCoinsDialog::on_sendButton_clicked()
{
// append fee string if a fee is required
questionString.append("<hr /><span style='color:#aa0000;'>");
- questionString.append(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
+ questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
questionString.append("</span> ");
questionString.append(tr("added as transaction fee"));
}
@@ -223,10 +223,10 @@ void SendCoinsDialog::on_sendButton_clicked()
foreach(BitcoinUnits::Unit u, BitcoinUnits::availableUnits())
{
if(u != model->getOptionsModel()->getDisplayUnit())
- alternativeUnits.append(BitcoinUnits::formatWithUnit(u, totalAmount));
+ alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
}
questionString.append(tr("Total Amount %1 (= %2)")
- .arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))
+ .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))
.arg(alternativeUnits.join(" " + tr("or") + " ")));
QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"),
diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp
index e0f56f8cd2..52545c3857 100644
--- a/src/qt/sendcoinsentry.cpp
+++ b/src/qt/sendcoinsentry.cpp
@@ -34,6 +34,12 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
GUIUtil::setupAddressWidget(ui->payTo, this);
// just a label for displaying bitcoin address(es)
ui->payTo_is->setFont(GUIUtil::bitcoinAddressFont());
+
+ // Connect signals
+ connect(ui->payAmount, SIGNAL(valueChanged()), this, SIGNAL(payAmountChanged()));
+ connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked()));
+ connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked()));
+ connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked()));
}
SendCoinsEntry::~SendCoinsEntry()
@@ -72,11 +78,6 @@ void SendCoinsEntry::setModel(WalletModel *model)
if (model && model->getOptionsModel())
connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
- connect(ui->payAmount, SIGNAL(textChanged()), this, SIGNAL(payAmountChanged()));
- connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked()));
- connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked()));
- connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked()));
-
clear();
}
@@ -130,6 +131,13 @@ bool SendCoinsEntry::validate()
retval = false;
}
+ // Sending a zero amount is invalid
+ if (ui->payAmount->value(0) <= 0)
+ {
+ ui->payAmount->setValid(false);
+ retval = false;
+ }
+
// Reject dust outputs:
if (retval && GUIUtil::isDust(ui->payTo->text(), ui->payAmount->value())) {
ui->payAmount->setValid(false);
diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp
index 36e4d50e30..4f6e3169f5 100644
--- a/src/qt/transactiondesc.cpp
+++ b/src/qt/transactiondesc.cpp
@@ -11,11 +11,11 @@
#include "db.h"
#include "main.h"
#include "paymentserver.h"
+#include "script.h"
#include "transactionrecord.h"
#include "timedata.h"
#include "ui_interface.h"
#include "wallet.h"
-#include "script.h"
#include <stdint.h>
#include <string>
@@ -136,7 +136,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
nUnmatured += wallet->GetCredit(txout, ISMINE_ALL);
strHTML += "<b>" + tr("Credit") + ":</b> ";
if (wtx.IsInMainChain())
- strHTML += BitcoinUnits::formatWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")";
+ strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")";
else
strHTML += "(" + tr("not accepted") + ")";
strHTML += "<br>";
@@ -146,7 +146,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
//
// Credit
//
- strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nNet) + "<br>";
+ strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
}
else
{
@@ -197,9 +197,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
}
}
- strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -txout.nValue) + "<br>";
+ strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
if(toSelf)
- strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, txout.nValue) + "<br>";
+ strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
}
if (fAllToMe)
@@ -207,13 +207,13 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
// Payment to self
int64_t nChange = wtx.GetChange();
int64_t nValue = nCredit - nChange;
- strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -nValue) + "<br>";
- strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nValue) + "<br>";
+ strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
+ strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
}
int64_t nTxFee = nDebit - wtx.GetValueOut();
if (nTxFee > 0)
- strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -nTxFee) + "<br>";
+ strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
}
else
{
@@ -222,14 +222,14 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
//
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
if (wallet->IsMine(txin))
- strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
+ strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
if (wallet->IsMine(txout))
- strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
+ strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
}
}
- strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nNet, true) + "<br>";
+ strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
//
// Message
@@ -275,10 +275,10 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
if(wallet->IsMine(txin))
- strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
+ strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
if(wallet->IsMine(txout))
- strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
+ strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true);
@@ -304,7 +304,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " ";
strHTML += QString::fromStdString(CBitcoinAddress(address).ToString());
}
- strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatWithUnit(unit, vout.nValue);
+ strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
strHTML = strHTML + " IsWatchOnly=" + (wallet->IsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
}
diff --git a/src/qt/transactionfilterproxy.cpp b/src/qt/transactionfilterproxy.cpp
index 7293029787..f9546fddb5 100644
--- a/src/qt/transactionfilterproxy.cpp
+++ b/src/qt/transactionfilterproxy.cpp
@@ -24,7 +24,7 @@ TransactionFilterProxy::TransactionFilterProxy(QObject *parent) :
typeFilter(ALL_TYPES),
minAmount(0),
limitRows(-1),
- showInactive(false)
+ showInactive(true)
{
}
@@ -39,7 +39,7 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
qint64 amount = llabs(index.data(TransactionTableModel::AmountRole).toLongLong());
int status = index.data(TransactionTableModel::StatusRole).toInt();
- if(!showInactive && status == TransactionStatus::Conflicted && type == TransactionRecord::Other)
+ if(!showInactive && status == TransactionStatus::Conflicted)
return false;
if(!(TYPE(type) & typeFilter))
return false;
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp
index 7d29c212b3..d7bd25e08b 100644
--- a/src/qt/transactionrecord.cpp
+++ b/src/qt/transactionrecord.cpp
@@ -184,8 +184,6 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
status.depth = wtx.GetDepthInMainChain();
status.cur_num_blocks = chainActive.Height();
- status.hasConflicting = false;
-
if (!IsFinalTx(wtx, chainActive.Height() + 1))
{
if (wtx.nLockTime < LOCKTIME_THRESHOLD)
@@ -229,7 +227,6 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
if (status.depth < 0)
{
status.status = TransactionStatus::Conflicted;
- status.hasConflicting = !(wtx.GetConflicts(false).empty());
}
else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
{
@@ -238,7 +235,6 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
else if (status.depth == 0)
{
status.status = TransactionStatus::Unconfirmed;
- status.hasConflicting = !(wtx.GetConflicts(false).empty());
}
else if (status.depth < RecommendedNumConfirmations)
{
@@ -249,13 +245,13 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
status.status = TransactionStatus::Confirmed;
}
}
+
}
-bool TransactionRecord::statusUpdateNeeded(int64_t nConflictsReceived)
+bool TransactionRecord::statusUpdateNeeded()
{
AssertLockHeld(cs_main);
- return (status.cur_num_blocks != chainActive.Height() ||
- status.cur_num_conflicts != nConflictsReceived);
+ return status.cur_num_blocks != chainActive.Height();
}
QString TransactionRecord::getTxID() const
diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h
index d3cfa77d97..626b7654c6 100644
--- a/src/qt/transactionrecord.h
+++ b/src/qt/transactionrecord.h
@@ -19,17 +19,9 @@ class TransactionStatus
{
public:
TransactionStatus():
- countsForBalance(false),
- sortKey(""),
- matures_in(0),
- status(Offline),
- hasConflicting(false),
- depth(0),
- open_for(0),
- cur_num_blocks(-1),
- cur_num_conflicts(-1)
- {
- }
+ countsForBalance(false), sortKey(""),
+ matures_in(0), status(Offline), depth(0), open_for(0), cur_num_blocks(-1)
+ { }
enum Status {
Confirmed, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/
@@ -59,10 +51,6 @@ public:
/** @name Reported status
@{*/
Status status;
-
- // Has conflicting transactions spending same prevout
- bool hasConflicting;
-
qint64 depth;
qint64 open_for; /**< Timestamp if status==OpenUntilDate, otherwise number
of additional blocks that need to be mined before
@@ -71,10 +59,6 @@ public:
/** Current number of blocks (to know whether cached status is still valid) */
int cur_num_blocks;
-
- /** Number of conflicts received into wallet as of last status update */
- int64_t cur_num_conflicts;
-
};
/** UI model for a transaction. A core transaction can be represented by multiple UI transactions if it has
@@ -152,7 +136,7 @@ public:
/** Return whether a status update is needed.
*/
- bool statusUpdateNeeded(int64_t nConflictsReceived);
+ bool statusUpdateNeeded();
};
#endif // TRANSACTIONRECORD_H
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index 3605cc1bad..7acb0e8871 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -5,7 +5,6 @@
#include "transactiontablemodel.h"
#include "addresstablemodel.h"
-#include "bitcoinunits.h"
#include "guiconstants.h"
#include "guiutil.h"
#include "optionsmodel.h"
@@ -168,7 +167,8 @@ public:
parent->endRemoveRows();
break;
case CT_UPDATED:
- emit parent->dataChanged(parent->index(lowerIndex, parent->Status), parent->index(upperIndex-1, parent->Amount));
+ // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
+ // visible transactions.
break;
}
}
@@ -189,21 +189,20 @@ public:
// stuck if the core is holding the locks for a longer time - for
// example, during a wallet rescan.
//
- // If a status update is needed (blocks or conflicts came in since last check),
- // update the status of this transaction from the wallet. Otherwise,
+ // If a status update is needed (blocks came in since last check),
+ // update the status of this transaction from the wallet. Otherwise,
// simply re-use the cached status.
TRY_LOCK(cs_main, lockMain);
if(lockMain)
{
TRY_LOCK(wallet->cs_wallet, lockWallet);
- if(lockWallet && rec->statusUpdateNeeded(wallet->nConflictsReceived))
+ if(lockWallet && rec->statusUpdateNeeded())
{
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
if(mi != wallet->mapWallet.end())
{
rec->updateStatus(mi->second);
- rec->status.cur_num_conflicts = wallet->nConflictsReceived;
}
}
}
@@ -369,8 +368,6 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
return tr("Payment to yourself");
case TransactionRecord::Generated:
return tr("Mined");
- case TransactionRecord::Other:
- return tr("Other");
default:
return QString();
}
@@ -436,9 +433,9 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
return QVariant();
}
-QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
+QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed, BitcoinUnits::SeparatorStyle separators) const
{
- QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
+ QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit, false, separators);
if(showUnconfirmed)
{
if(!wtx->status.countsForBalance)
@@ -523,7 +520,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
case ToAddress:
return formatTxToAddress(rec, false);
case Amount:
- return formatTxAmount(rec);
+ return formatTxAmount(rec, true, BitcoinUnits::separatorAlways);
}
break;
case Qt::EditRole:
@@ -546,13 +543,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
return formatTooltip(rec);
case Qt::TextAlignmentRole:
return column_alignments[index.column()];
- case Qt::BackgroundColorRole:
- if (rec->status.hasConflicting)
- return COLOR_HASCONFLICTING_BG;
- break;
case Qt::ForegroundRole:
- if (rec->status.hasConflicting)
- return COLOR_HASCONFLICTING;
// Non-confirmed (but not immature) as transactions are grey
if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
{
@@ -586,7 +577,8 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
case ConfirmedRole:
return rec->status.countsForBalance;
case FormattedAmountRole:
- return formatTxAmount(rec, false);
+ // Used for copy/export, so don't include separators
+ return formatTxAmount(rec, false, BitcoinUnits::separatorNever);
case StatusRole:
return rec->status.status;
}
diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h
index e8b6ed065d..2124d3dd1c 100644
--- a/src/qt/transactiontablemodel.h
+++ b/src/qt/transactiontablemodel.h
@@ -5,6 +5,8 @@
#ifndef TRANSACTIONTABLEMODEL_H
#define TRANSACTIONTABLEMODEL_H
+#include "bitcoinunits.h"
+
#include <QAbstractTableModel>
#include <QStringList>
@@ -78,7 +80,7 @@ private:
QString formatTxDate(const TransactionRecord *wtx) const;
QString formatTxType(const TransactionRecord *wtx) const;
QString formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const;
- QString formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed=true) const;
+ QString formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed=true, BitcoinUnits::SeparatorStyle separators=BitcoinUnits::separatorStandard) const;
QString formatTooltip(const TransactionRecord *rec) const;
QVariant txStatusDecoration(const TransactionRecord *wtx) const;
QVariant txAddressDecoration(const TransactionRecord *wtx) const;
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index d6d210a561..7e8b71d8ea 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -123,6 +123,8 @@ TransactionView::TransactionView(QWidget *parent) :
view->setTabKeyNavigation(false);
view->setContextMenuPolicy(Qt::CustomContextMenu);
+ view->installEventFilter(this);
+
transactionView = view;
// Actions
@@ -480,3 +482,22 @@ void TransactionView::resizeEvent(QResizeEvent* event)
QWidget::resizeEvent(event);
columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress);
}
+
+// Need to override default Ctrl+C action for amount as default behaviour is just to copy DisplayRole text
+bool TransactionView::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::KeyPress)
+ {
+ QKeyEvent *ke = static_cast<QKeyEvent *>(event);
+ if (ke->key() == Qt::Key_C && ke->modifiers().testFlag(Qt::ControlModifier))
+ {
+ QModelIndex i = this->transactionView->currentIndex();
+ if (i.isValid() && i.column() == TransactionTableModel::Amount)
+ {
+ GUIUtil::setClipboard(i.data(TransactionTableModel::FormattedAmountRole).toString());
+ return true;
+ }
+ }
+ }
+ return QWidget::eventFilter(obj, event);
+}
diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h
index 7a89fa11ca..618efbc565 100644
--- a/src/qt/transactionview.h
+++ b/src/qt/transactionview.h
@@ -8,6 +8,7 @@
#include "guiutil.h"
#include <QWidget>
+#include <QKeyEvent>
class TransactionFilterProxy;
class WalletModel;
@@ -78,6 +79,8 @@ private:
virtual void resizeEvent(QResizeEvent* event);
+ bool eventFilter(QObject *obj, QEvent *event);
+
private slots:
void contextualMenu(const QPoint &);
void dateRangeChanged();
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 7317c32766..0ad123f39d 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -35,6 +35,8 @@ WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *p
cachedEncryptionStatus(Unencrypted),
cachedNumBlocks(0)
{
+ fProcessingQueuedTransactions = false;
+
addressTableModel = new AddressTableModel(wallet, this);
transactionTableModel = new TransactionTableModel(wallet, this);
recentRequestsTableModel = new RecentRequestsTableModel(wallet, this);
@@ -162,14 +164,6 @@ void WalletModel::checkBalanceChanged()
void WalletModel::updateTransaction(const QString &hash, int status)
{
- if (status == CT_GOT_CONFLICT)
- {
- emit message(tr("Conflict Received"),
- tr("WARNING: Transaction may never be confirmed. Its input was seen being spent by another transaction on the network. Wait for confirmation!"),
- CClientUIInterface::MSG_WARNING);
- return;
- }
-
if(transactionTableModel)
transactionTableModel->updateTransaction(hash, status);
@@ -492,8 +486,15 @@ static void ShowProgress(WalletModel *walletmodel, const std::string &title, int
if (nProgress == 100)
{
fQueueNotifications = false;
- BOOST_FOREACH(const PAIRTYPE(uint256, ChangeType)& notification, vQueueNotifications)
- NotifyTransactionChanged(walletmodel, NULL, notification.first, notification.second);
+ if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons
+ QMetaObject::invokeMethod(walletmodel, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
+ for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
+ {
+ if (vQueueNotifications.size() - i <= 10)
+ QMetaObject::invokeMethod(walletmodel, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
+
+ NotifyTransactionChanged(walletmodel, NULL, vQueueNotifications[i].first, vQueueNotifications[i].second);
+ }
std::vector<std::pair<uint256, ChangeType> >().swap(vQueueNotifications); // clear
}
}
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index 7ad54ff8e6..2bb91d85a9 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -133,6 +133,7 @@ public:
qint64 getWatchImmatureBalance() const;
int getNumTransactions() const;
EncryptionStatus getEncryptionStatus() const;
+ bool processingQueuedTransactions() { return fProcessingQueuedTransactions; }
// Check address for validity
bool validateAddress(const QString &address);
@@ -196,6 +197,7 @@ public:
private:
CWallet *wallet;
+ bool fProcessingQueuedTransactions;
// Wallet has an options model for wallet-specific options
// (transaction fee, for example)
@@ -256,6 +258,8 @@ public slots:
void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status);
/* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */
void pollBalanceChanged();
+ /* Needed to update fProcessingQueuedTransactions through a QueuedConnection */
+ void setProcessingQueuedTransactions(bool value) { fProcessingQueuedTransactions = value; }
};
#endif // WALLETMODEL_H
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp
index 1cef48344f..b40ddc0a2f 100644
--- a/src/qt/walletview.cpp
+++ b/src/qt/walletview.cpp
@@ -137,7 +137,7 @@ void WalletView::setWalletModel(WalletModel *walletModel)
void WalletView::processNewTransaction(const QModelIndex& parent, int start, int /*end*/)
{
// Prevent balloon-spam when initial block download is in progress
- if (!walletModel || !clientModel || clientModel->inInitialBlockDownload())
+ if (!walletModel || walletModel->processingQueuedTransactions() || !clientModel || clientModel->inInitialBlockDownload())
return;
TransactionTableModel *ttm = walletModel->getTransactionTableModel();
diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp
index 4193f41b49..ff2361482b 100644
--- a/src/rpcdump.cpp
+++ b/src/rpcdump.cpp
@@ -72,16 +72,17 @@ Value importprivkey(const Array& params, bool fHelp)
"\nAdds a private key (as returned by dumpprivkey) to your wallet.\n"
"\nArguments:\n"
"1. \"bitcoinprivkey\" (string, required) The private key (see dumpprivkey)\n"
- "2. \"label\" (string, optional) an optional label\n"
+ "2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
+ "\nNote: This call can take minutes to complete if rescan is true.\n"
"\nExamples:\n"
"\nDump a private key\n"
+ HelpExampleCli("dumpprivkey", "\"myaddress\"") +
- "\nImport the private key\n"
+ "\nImport the private key with rescan\n"
+ HelpExampleCli("importprivkey", "\"mykey\"") +
- "\nImport using a label\n"
+ "\nImport using a label and without rescan\n"
+ HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
- "\nAs a json rpc call\n"
+ "\nAs a JSON-RPC call\n"
+ HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
);
@@ -137,8 +138,21 @@ Value importaddress(const Array& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 3)
throw runtime_error(
- "importaddress <address> [label] [rescan=true]\n"
- "Adds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend.");
+ "importaddress \"address\" ( \"label\" rescan )\n"
+ "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n"
+ "\nArguments:\n"
+ "1. \"address\" (string, required) The address\n"
+ "2. \"label\" (string, optional, default=\"\") An optional label\n"
+ "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
+ "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nExamples:\n"
+ "\nImport an address with rescan\n"
+ + HelpExampleCli("importaddress", "\"myaddress\"") +
+ "\nImport using a label without rescan\n"
+ + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false")
+ );
CScript script;
diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp
index e8c62fd37b..5b83fe900e 100644
--- a/src/rpcwallet.cpp
+++ b/src/rpcwallet.cpp
@@ -58,10 +58,6 @@ void WalletTxToJSON(const CWalletTx& wtx, Object& entry)
BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts())
conflicts.push_back(conflict.GetHex());
entry.push_back(Pair("walletconflicts", conflicts));
- Array respends;
- BOOST_FOREACH(const uint256& respend, wtx.GetConflicts(false))
- respends.push_back(respend.GetHex());
- entry.push_back(Pair("respendsobserved", respends));
entry.push_back(Pair("time", wtx.GetTxTime()));
entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived));
BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue)
@@ -641,16 +637,16 @@ Value getbalance(const Array& params, bool fHelp)
int64_t allFee;
string strSentAccount;
- list<pair<CTxDestination, int64_t> > listReceived;
- list<pair<CTxDestination, int64_t> > listSent;
+ list<COutputEntry> listReceived;
+ list<COutputEntry> listSent;
wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter);
if (wtx.GetDepthInMainChain() >= nMinDepth)
{
- BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& r, listReceived)
- nBalance += r.second;
+ BOOST_FOREACH(const COutputEntry& r, listReceived)
+ nBalance += r.amount;
}
- BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& r, listSent)
- nBalance -= r.second;
+ BOOST_FOREACH(const COutputEntry& s, listSent)
+ nBalance -= s.amount;
nBalance -= allFee;
}
return ValueFromAmount(nBalance);
@@ -1133,8 +1129,8 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
{
int64_t nFee;
string strSentAccount;
- list<pair<CTxDestination, int64_t> > listReceived;
- list<pair<CTxDestination, int64_t> > listSent;
+ list<COutputEntry> listReceived;
+ list<COutputEntry> listSent;
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter);
@@ -1144,15 +1140,16 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
// Sent
if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount))
{
- BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& s, listSent)
+ BOOST_FOREACH(const COutputEntry& s, listSent)
{
Object entry;
- if(involvesWatchonly || (::IsMine(*pwalletMain, s.first) & ISMINE_WATCH_ONLY))
+ if(involvesWatchonly || (::IsMine(*pwalletMain, s.destination) & ISMINE_WATCH_ONLY))
entry.push_back(Pair("involvesWatchonly", true));
entry.push_back(Pair("account", strSentAccount));
- MaybePushAddress(entry, s.first);
+ MaybePushAddress(entry, s.destination);
entry.push_back(Pair("category", "send"));
- entry.push_back(Pair("amount", ValueFromAmount(-s.second)));
+ entry.push_back(Pair("amount", ValueFromAmount(-s.amount)));
+ entry.push_back(Pair("vout", s.vout));
entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
if (fLong)
WalletTxToJSON(wtx, entry);
@@ -1163,18 +1160,18 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
// Received
if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth)
{
- BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& r, listReceived)
+ BOOST_FOREACH(const COutputEntry& r, listReceived)
{
string account;
- if (pwalletMain->mapAddressBook.count(r.first))
- account = pwalletMain->mapAddressBook[r.first].name;
+ if (pwalletMain->mapAddressBook.count(r.destination))
+ account = pwalletMain->mapAddressBook[r.destination].name;
if (fAllAccounts || (account == strAccount))
{
Object entry;
- if(involvesWatchonly || (::IsMine(*pwalletMain, r.first) & ISMINE_WATCH_ONLY))
+ if(involvesWatchonly || (::IsMine(*pwalletMain, r.destination) & ISMINE_WATCH_ONLY))
entry.push_back(Pair("involvesWatchonly", true));
entry.push_back(Pair("account", account));
- MaybePushAddress(entry, r.first);
+ MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase())
{
if (wtx.GetDepthInMainChain() < 1)
@@ -1188,7 +1185,8 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
{
entry.push_back(Pair("category", "receive"));
}
- entry.push_back(Pair("amount", ValueFromAmount(r.second)));
+ entry.push_back(Pair("amount", ValueFromAmount(r.amount)));
+ entry.push_back(Pair("vout", r.vout));
if (fLong)
WalletTxToJSON(wtx, entry);
ret.push_back(entry);
@@ -1240,6 +1238,7 @@ Value listtransactions(const Array& params, bool fHelp)
" \"amount\": x.xxx, (numeric) The amount in btc. This is negative for the 'send' category, and for the\n"
" 'move' category for moves outbound. It is positive for the 'receive' category,\n"
" and for the 'move' category for inbound funds.\n"
+ " \"vout\" : n, (numeric) the vout value\n"
" \"fee\": x.xxx, (numeric) The amount of the fee in btc. This is negative and only available for the \n"
" 'send' category of transactions.\n"
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
@@ -1249,12 +1248,6 @@ Value listtransactions(const Array& params, bool fHelp)
" \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive'\n"
" category of transactions.\n"
" \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n"
- " \"walletconflicts\" : [\n"
- " \"conflictid\", (string) Ids of transactions, including equivalent clones, that re-spend a txid input.\n"
- " ],\n"
- " \"respendsobserved\" : [\n"
- " \"respendid\", (string) Ids of transactions, NOT equivalent clones, that re-spend a txid input. \"Double-spends.\"\n"
- " ],\n"
" \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT). Available \n"
" for 'send' and 'receive' category of transactions.\n"
@@ -1375,22 +1368,22 @@ Value listaccounts(const Array& params, bool fHelp)
const CWalletTx& wtx = (*it).second;
int64_t nFee;
string strSentAccount;
- list<pair<CTxDestination, int64_t> > listReceived;
- list<pair<CTxDestination, int64_t> > listSent;
+ list<COutputEntry> listReceived;
+ list<COutputEntry> listSent;
int nDepth = wtx.GetDepthInMainChain();
if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0)
continue;
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, includeWatchonly);
mapAccountBalances[strSentAccount] -= nFee;
- BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& s, listSent)
- mapAccountBalances[strSentAccount] -= s.second;
+ BOOST_FOREACH(const COutputEntry& s, listSent)
+ mapAccountBalances[strSentAccount] -= s.amount;
if (nDepth >= nMinDepth)
{
- BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& r, listReceived)
- if (pwalletMain->mapAddressBook.count(r.first))
- mapAccountBalances[pwalletMain->mapAddressBook[r.first].name] += r.second;
+ BOOST_FOREACH(const COutputEntry& r, listReceived)
+ if (pwalletMain->mapAddressBook.count(r.destination))
+ mapAccountBalances[pwalletMain->mapAddressBook[r.destination].name] += r.amount;
else
- mapAccountBalances[""] += r.second;
+ mapAccountBalances[""] += r.amount;
}
}
@@ -1424,18 +1417,13 @@ Value listsinceblock(const Array& params, bool fHelp)
" \"category\":\"send|receive\", (string) The transaction category. 'send' has negative amounts, 'receive' has positive amounts.\n"
" \"amount\": x.xxx, (numeric) The amount in btc. This is negative for the 'send' category, and for the 'move' category for moves \n"
" outbound. It is positive for the 'receive' category, and for the 'move' category for inbound funds.\n"
+ " \"vout\" : n, (numeric) the vout value\n"
" \"fee\": x.xxx, (numeric) The amount of the fee in btc. This is negative and only available for the 'send' category of transactions.\n"
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and 'receive' category of transactions.\n"
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive' category of transactions.\n"
" \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive' category of transactions.\n"
" \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n"
" \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n"
- " \"walletconflicts\" : [\n"
- " \"conflictid\", (string) Ids of transactions, including equivalent clones, that re-spend a txid input.\n"
- " ],\n"
- " \"respendsobserved\" : [\n"
- " \"respendid\", (string) Ids of transactions, NOT equivalent clones, that re-spend a txid input. \"Double-spends.\"\n"
- " ],\n"
" \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n"
" \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
@@ -1514,12 +1502,6 @@ Value gettransaction(const Array& params, bool fHelp)
" \"blockindex\" : xx, (numeric) The block index\n"
" \"blocktime\" : ttt, (numeric) The time in seconds since epoch (1 Jan 1970 GMT)\n"
" \"txid\" : \"transactionid\", (string) The transaction id.\n"
- " \"walletconflicts\" : [\n"
- " \"conflictid\", (string) Ids of transactions, including equivalent clones, that re-spend a txid input.\n"
- " ],\n"
- " \"respendsobserved\" : [\n"
- " \"respendid\", (string) Ids of transactions, NOT equivalent clones, that re-spend a txid input. \"Double-spends.\"\n"
- " ],\n"
" \"time\" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)\n"
" \"timereceived\" : ttt, (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)\n"
" \"details\" : [\n"
@@ -1528,6 +1510,7 @@ Value gettransaction(const Array& params, bool fHelp)
" \"address\" : \"bitcoinaddress\", (string) The bitcoin address involved in the transaction\n"
" \"category\" : \"send|receive\", (string) The category, either 'send' or 'receive'\n"
" \"amount\" : x.xxx (numeric) The amount in btc\n"
+ " \"vout\" : n, (numeric) the vout value\n"
" }\n"
" ,...\n"
" ],\n"
diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp
index 2cdafa4bdd..69de3b5bb1 100644
--- a/src/test/bloom_tests.cpp
+++ b/src/test/bloom_tests.cpp
@@ -45,10 +45,6 @@ BOOST_AUTO_TEST_CASE(bloom_create_insert_serialize)
expected[i] = (char)vch[i];
BOOST_CHECK_EQUAL_COLLECTIONS(stream.begin(), stream.end(), expected.begin(), expected.end());
-
- BOOST_CHECK_MESSAGE( filter.contains(ParseHex("99108ad8ed9bb6274d3980bab5a85c048f0950c8")), "BloomFilter doesn't contain just-inserted object!");
- filter.clear();
- BOOST_CHECK_MESSAGE( !filter.contains(ParseHex("99108ad8ed9bb6274d3980bab5a85c048f0950c8")), "BloomFilter should be empty!");
}
BOOST_AUTO_TEST_CASE(bloom_create_insert_serialize_with_tweak)
diff --git a/src/timedata.cpp b/src/timedata.cpp
index 8a095d26dc..6c3bd9a48d 100644
--- a/src/timedata.cpp
+++ b/src/timedata.cpp
@@ -49,6 +49,24 @@ void AddTimeData(const CNetAddr& ip, int64_t nTime)
static CMedianFilter<int64_t> vTimeOffsets(200,0);
vTimeOffsets.input(nOffsetSample);
LogPrintf("Added time data, samples %d, offset %+d (%+d minutes)\n", vTimeOffsets.size(), nOffsetSample, nOffsetSample/60);
+
+ // There is a known issue here (see issue #4521):
+ //
+ // - The structure vTimeOffsets contains up to 200 elements, after which
+ // any new element added to it will not increase its size, replacing the
+ // oldest element.
+ //
+ // - The condition to update nTimeOffset includes checking whether the
+ // number of elements in vTimeOffsets is odd, which will never happen after
+ // there are 200 elements.
+ //
+ // But in this case the 'bug' is protective against some attacks, and may
+ // actually explain why we've never seen attacks which manipulate the
+ // clock offset.
+ //
+ // So we should hold off on fixing this and clean it up as part of
+ // a timing cleanup that strengthens it in a number of other ways.
+ //
if (vTimeOffsets.size() >= 5 && vTimeOffsets.size() % 2 == 1)
{
int64_t nMedian = vTimeOffsets.median();
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index a852de5da8..164e2741a2 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -420,6 +420,7 @@ void CTxMemPool::remove(const CTransaction &tx, std::list<CTransaction>& removed
void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed)
{
// Remove transactions which depend on inputs of tx, recursively
+ list<CTransaction> result;
LOCK(cs);
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(txin.prevout);
@@ -601,14 +602,15 @@ void CTxMemPool::ClearPrioritisation(const uint256 hash)
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
bool CCoinsViewMemPool::GetCoins(const uint256 &txid, CCoins &coins) {
- if (base->GetCoins(txid, coins))
- return true;
+ // If an entry in the mempool exists, always return that one, as it's guaranteed to never
+ // conflict with the underlying cache, and it cannot have pruned entries (as it contains full)
+ // transactions. First checking the underlying cache risks returning a pruned entry instead.
CTransaction tx;
if (mempool.lookup(txid, tx)) {
coins = CCoins(tx, MEMPOOL_HEIGHT);
return true;
}
- return false;
+ return (base->GetCoins(txid, coins) && !coins.IsPruned());
}
bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) {
diff --git a/src/ui_interface.h b/src/ui_interface.h
index a48ba237d2..b3df2b5a85 100644
--- a/src/ui_interface.h
+++ b/src/ui_interface.h
@@ -21,8 +21,7 @@ enum ChangeType
{
CT_NEW,
CT_UPDATED,
- CT_DELETED,
- CT_GOT_CONFLICT
+ CT_DELETED
};
/** Signals for UI communication. */
diff --git a/src/version.h b/src/version.h
index 3d1abacb94..85c5dbf8d7 100644
--- a/src/version.h
+++ b/src/version.h
@@ -28,7 +28,7 @@ extern const std::string CLIENT_DATE;
static const int PROTOCOL_VERSION = 70002;
-// intial proto version, to be increased after version/verack negotiation
+// initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;
// disconnect from peers older than this proto version
@@ -45,7 +45,7 @@ static const int NOBLKS_VERSION_END = 32400;
// BIP 0031, pong message, is enabled for all versions AFTER this one
static const int BIP0031_VERSION = 60000;
-// "mempool" command, enhanced "getdata" behavior starts with this version:
+// "mempool" command, enhanced "getdata" behavior starts with this version
static const int MEMPOOL_GD_VERSION = 60002;
#endif
diff --git a/src/wallet.cpp b/src/wallet.cpp
index a754c1cd5f..7c04743c0f 100644
--- a/src/wallet.cpp
+++ b/src/wallet.cpp
@@ -274,7 +274,7 @@ bool CWallet::SetMaxVersion(int nVersion)
return true;
}
-set<uint256> CWallet::GetConflicts(const uint256& txid, bool includeEquivalent) const
+set<uint256> CWallet::GetConflicts(const uint256& txid) const
{
set<uint256> result;
AssertLockHeld(cs_wallet);
@@ -292,8 +292,7 @@ set<uint256> CWallet::GetConflicts(const uint256& txid, bool includeEquivalent)
continue; // No conflict if zero or one spends
range = mapTxSpends.equal_range(txin.prevout);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
- if (includeEquivalent || !wtx.IsEquivalentTo(mapWallet.at(it->second)))
- result.insert(it->second);
+ result.insert(it->second);
}
return result;
}
@@ -322,7 +321,6 @@ void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range)
const uint256& hash = it->second;
CWalletTx* copyTo = &mapWallet[hash];
if (copyFrom == copyTo) continue;
- if (!copyFrom->IsEquivalentTo(*copyTo)) continue;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
// fTimeReceivedIsTxTime not copied on purpose
@@ -610,28 +608,6 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet)
// Notify UI of new or updated transaction
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
- // Notifications for existing transactions that now have conflicts with this one
- if (fInsertedNew)
- {
- BOOST_FOREACH(const uint256& conflictHash, wtxIn.GetConflicts(false))
- {
- CWalletTx& txConflict = mapWallet[conflictHash];
- NotifyTransactionChanged(this, conflictHash, CT_UPDATED); //Updates UI table
- if (IsFromMe(txConflict) || IsMine(txConflict))
- {
- NotifyTransactionChanged(this, conflictHash, CT_GOT_CONFLICT); //Throws dialog
- // external respend notify
- std::string strCmd = GetArg("-respendnotify", "");
- if (!strCmd.empty())
- {
- boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
- boost::replace_all(strCmd, "%t", conflictHash.GetHex());
- boost::thread t(runCommand, strCmd); // thread runs free
- }
- }
- }
- }
-
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = GetArg("-walletnotify", "");
@@ -654,12 +630,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
AssertLockHeld(cs_wallet);
bool fExisted = mapWallet.count(tx.GetHash());
if (fExisted && !fUpdate) return false;
-
- bool fIsConflicting = IsConflicting(tx);
- if (fIsConflicting)
- nConflictsReceived++;
-
- if (fExisted || IsMine(tx) || IsFromMe(tx) || fIsConflicting)
+ if (fExisted || IsMine(tx) || IsFromMe(tx))
{
CWalletTx wtx(this,tx);
// Get merkle branch if transaction was found in a block
@@ -798,8 +769,8 @@ int CWalletTx::GetRequestCount() const
return nRequests;
}
-void CWalletTx::GetAmounts(list<pair<CTxDestination, int64_t> >& listReceived,
- list<pair<CTxDestination, int64_t> >& listSent, int64_t& nFee, string& strSentAccount, const isminefilter& filter) const
+void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
+ list<COutputEntry>& listSent, int64_t& nFee, string& strSentAccount, const isminefilter& filter) const
{
nFee = 0;
listReceived.clear();
@@ -815,10 +786,10 @@ void CWalletTx::GetAmounts(list<pair<CTxDestination, int64_t> >& listReceived,
}
// Sent/received.
- BOOST_FOREACH(const CTxOut& txout, vout)
+ for (unsigned int i = 0; i < vout.size(); ++i)
{
+ const CTxOut& txout = vout[i];
isminetype fIsMine = pwallet->IsMine(txout);
-
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
@@ -840,13 +811,15 @@ void CWalletTx::GetAmounts(list<pair<CTxDestination, int64_t> >& listReceived,
address = CNoDestination();
}
+ COutputEntry output = {address, txout.nValue, (int)i};
+
// If we are debited by the transaction, add the output as a "sent" entry
if (nDebit > 0)
- listSent.push_back(make_pair(address, txout.nValue));
+ listSent.push_back(output);
// If we are receiving the output, add it as a "received" entry
if (fIsMine & filter)
- listReceived.push_back(make_pair(address, txout.nValue));
+ listReceived.push_back(output);
}
}
@@ -858,29 +831,29 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64_t& nReceived,
int64_t allFee;
string strSentAccount;
- list<pair<CTxDestination, int64_t> > listReceived;
- list<pair<CTxDestination, int64_t> > listSent;
+ list<COutputEntry> listReceived;
+ list<COutputEntry> listSent;
GetAmounts(listReceived, listSent, allFee, strSentAccount, filter);
if (strAccount == strSentAccount)
{
- BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& s, listSent)
- nSent += s.second;
+ BOOST_FOREACH(const COutputEntry& s, listSent)
+ nSent += s.amount;
nFee = allFee;
}
{
LOCK(pwallet->cs_wallet);
- BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& r, listReceived)
+ BOOST_FOREACH(const COutputEntry& r, listReceived)
{
- if (pwallet->mapAddressBook.count(r.first))
+ if (pwallet->mapAddressBook.count(r.destination))
{
- map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.first);
+ map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.destination);
if (mi != pwallet->mapAddressBook.end() && (*mi).second.name == strAccount)
- nReceived += r.second;
+ nReceived += r.amount;
}
else if (strAccount.empty())
{
- nReceived += r.second;
+ nReceived += r.amount;
}
}
}
@@ -946,7 +919,7 @@ void CWallet::ReacceptWalletTransactions()
int nDepth = wtx.GetDepthInMainChain();
- if (!wtx.IsCoinBase() && nDepth < 0 && (IsMine(wtx) || IsFromMe(wtx)))
+ if (!wtx.IsCoinBase() && nDepth < 0)
{
// Try to add to memory pool
LOCK(mempool.cs);
@@ -966,13 +939,13 @@ void CWalletTx::RelayWalletTransaction()
}
}
-set<uint256> CWalletTx::GetConflicts(bool includeEquivalent) const
+set<uint256> CWalletTx::GetConflicts() const
{
set<uint256> result;
if (pwallet != NULL)
{
uint256 myHash = GetHash();
- result = pwallet->GetConflicts(myHash, includeEquivalent);
+ result = pwallet->GetConflicts(myHash);
result.erase(myHash);
}
return result;
diff --git a/src/wallet.h b/src/wallet.h
index ab9550a984..73fcfa24e0 100644
--- a/src/wallet.h
+++ b/src/wallet.h
@@ -144,9 +144,6 @@ public:
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID;
- // Increment to cause UI refresh, similar to new block
- int64_t nConflictsReceived;
-
CWallet()
{
SetNull();
@@ -169,7 +166,6 @@ public:
nNextResend = 0;
nLastResend = 0;
nTimeFirstKey = 0;
- nConflictsReceived = 0;
}
std::map<uint256, CWalletTx> mapWallet;
@@ -322,13 +318,6 @@ public:
{
return (GetDebit(tx, ISMINE_ALL) > 0);
}
- bool IsConflicting(const CTransaction& tx) const
- {
- BOOST_FOREACH(const CTxIn& txin, tx.vin)
- if (mapTxSpends.count(txin.prevout))
- return true;
- return false;
- }
int64_t GetDebit(const CTransaction& tx, const isminefilter& filter) const
{
int64_t nDebit = 0;
@@ -401,7 +390,7 @@ public:
int GetVersion() { LOCK(cs_wallet); return nWalletVersion; }
// Get wallet transactions that conflict with given transaction (spend same outputs)
- std::set<uint256> GetConflicts(const uint256& txid, bool includeEquivalent) const;
+ std::set<uint256> GetConflicts(const uint256& txid) const;
/** Address book entry changed.
* @note called with lock cs_wallet held.
@@ -467,6 +456,12 @@ static void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue)
mapValue["n"] = i64tostr(nOrderPos);
}
+struct COutputEntry
+{
+ CTxDestination destination;
+ int64_t amount;
+ int vout;
+};
/** A transaction with a bunch of additional info that only the owner cares about.
* It includes any unrecorded transactions needed to link it back to the block chain.
@@ -761,8 +756,8 @@ public:
return nChangeCached;
}
- void GetAmounts(std::list<std::pair<CTxDestination, int64_t> >& listReceived,
- std::list<std::pair<CTxDestination, int64_t> >& listSent, int64_t& nFee, std::string& strSentAccount, const isminefilter& filter) const;
+ void GetAmounts(std::list<COutputEntry>& listReceived,
+ std::list<COutputEntry>& listSent, int64_t& nFee, std::string& strSentAccount, const isminefilter& filter) const;
void GetAccountAmounts(const std::string& strAccount, int64_t& nReceived,
int64_t& nSent, int64_t& nFee, const isminefilter& filter) const;
@@ -806,7 +801,7 @@ public:
void RelayWalletTransaction();
- std::set<uint256> GetConflicts(bool includeEquivalent=true) const;
+ std::set<uint256> GetConflicts() const;
};