diff options
Diffstat (limited to 'src')
60 files changed, 991 insertions, 776 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/addrman.h b/src/addrman.h index a0dc134c40..052d364655 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -245,140 +245,142 @@ protected: void Connected_(const CService &addr, int64_t nTime); public: + // serialized format: + // * version byte (currently 0) + // * nKey + // * nNew + // * nTried + // * number of "new" buckets + // * all nNew addrinfos in vvNew + // * all nTried addrinfos in vvTried + // * for each bucket: + // * number of elements + // * for each element: index + // + // Notice that vvTried, mapAddr and vVector are never encoded explicitly; + // they are instead reconstructed from the other information. + // + // vvNew is serialized, but only used if ADDRMAN_UNKOWN_BUCKET_COUNT didn't change, + // otherwise it is reconstructed as well. + // + // This format is more complex, but significantly smaller (at most 1.5 MiB), and supports + // changes to the ADDRMAN_ parameters without breaking the on-disk structure. + // + // We don't use IMPLEMENT_SERIALIZE since the serialization and deserialization code has + // very little in common. + template<typename Stream> + void Serialize(Stream &s, int nType, int nVersionDummy) const + { + LOCK(cs); + + unsigned char nVersion = 0; + s << nVersion; + s << nKey; + s << nNew; + s << nTried; + + int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT; + s << nUBuckets; + std::map<int, int> mapUnkIds; + int nIds = 0; + for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) { + if (nIds == nNew) break; // this means nNew was wrong, oh ow + mapUnkIds[(*it).first] = nIds; + const CAddrInfo &info = (*it).second; + if (info.nRefCount) { + s << info; + nIds++; + } + } + nIds = 0; + for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) { + if (nIds == nTried) break; // this means nTried was wrong, oh ow + const CAddrInfo &info = (*it).second; + if (info.fInTried) { + s << info; + nIds++; + } + } + for (std::vector<std::set<int> >::const_iterator it = vvNew.begin(); it != vvNew.end(); it++) { + const std::set<int> &vNew = (*it); + int nSize = vNew.size(); + s << nSize; + for (std::set<int>::const_iterator it2 = vNew.begin(); it2 != vNew.end(); it2++) { + int nIndex = mapUnkIds[*it2]; + s << nIndex; + } + } + } - IMPLEMENT_SERIALIZE - (({ - // serialized format: - // * version byte (currently 0) - // * nKey - // * nNew - // * nTried - // * number of "new" buckets - // * all nNew addrinfos in vvNew - // * all nTried addrinfos in vvTried - // * for each bucket: - // * number of elements - // * for each element: index - // - // Notice that vvTried, mapAddr and vVector are never encoded explicitly; - // they are instead reconstructed from the other information. - // - // vvNew is serialized, but only used if ADDRMAN_UNKOWN_BUCKET_COUNT didn't change, - // otherwise it is reconstructed as well. - // - // This format is more complex, but significantly smaller (at most 1.5 MiB), and supports - // changes to the ADDRMAN_ parameters without breaking the on-disk structure. - { - LOCK(cs); - unsigned char nVersion = 0; - READWRITE(nVersion); - READWRITE(nKey); - READWRITE(nNew); - READWRITE(nTried); - - CAddrMan *am = const_cast<CAddrMan*>(this); - if (fWrite) - { - int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT; - READWRITE(nUBuckets); - std::map<int, int> mapUnkIds; - int nIds = 0; - for (std::map<int, CAddrInfo>::iterator it = am->mapInfo.begin(); it != am->mapInfo.end(); it++) - { - if (nIds == nNew) break; // this means nNew was wrong, oh ow - mapUnkIds[(*it).first] = nIds; - CAddrInfo &info = (*it).second; - if (info.nRefCount) - { - READWRITE(info); - nIds++; - } - } - nIds = 0; - for (std::map<int, CAddrInfo>::iterator it = am->mapInfo.begin(); it != am->mapInfo.end(); it++) - { - if (nIds == nTried) break; // this means nTried was wrong, oh ow - CAddrInfo &info = (*it).second; - if (info.fInTried) - { - READWRITE(info); - nIds++; - } - } - for (std::vector<std::set<int> >::iterator it = am->vvNew.begin(); it != am->vvNew.end(); it++) - { - const std::set<int> &vNew = (*it); - int nSize = vNew.size(); - READWRITE(nSize); - for (std::set<int>::iterator it2 = vNew.begin(); it2 != vNew.end(); it2++) - { - int nIndex = mapUnkIds[*it2]; - READWRITE(nIndex); - } - } + template<typename Stream> + void Unserialize(Stream& s, int nType, int nVersionDummy) + { + LOCK(cs); + + unsigned char nVersion; + s >> nVersion; + s >> nKey; + s >> nNew; + s >> nTried; + + int nUBuckets = 0; + s >> nUBuckets; + nIdCount = 0; + mapInfo.clear(); + mapAddr.clear(); + vRandom.clear(); + vvTried = std::vector<std::vector<int> >(ADDRMAN_TRIED_BUCKET_COUNT, std::vector<int>(0)); + vvNew = std::vector<std::set<int> >(ADDRMAN_NEW_BUCKET_COUNT, std::set<int>()); + for (int n = 0; n < nNew; n++) { + CAddrInfo &info = mapInfo[n]; + s >> info; + mapAddr[info] = n; + info.nRandomPos = vRandom.size(); + vRandom.push_back(n); + if (nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) { + vvNew[info.GetNewBucket(nKey)].insert(n); + info.nRefCount++; + } + } + nIdCount = nNew; + int nLost = 0; + for (int n = 0; n < nTried; n++) { + CAddrInfo info; + s >> info; + std::vector<int> &vTried = vvTried[info.GetTriedBucket(nKey)]; + if (vTried.size() < ADDRMAN_TRIED_BUCKET_SIZE) { + info.nRandomPos = vRandom.size(); + info.fInTried = true; + vRandom.push_back(nIdCount); + mapInfo[nIdCount] = info; + mapAddr[info] = nIdCount; + vTried.push_back(nIdCount); + nIdCount++; } else { - int nUBuckets = 0; - READWRITE(nUBuckets); - am->nIdCount = 0; - am->mapInfo.clear(); - am->mapAddr.clear(); - am->vRandom.clear(); - am->vvTried = std::vector<std::vector<int> >(ADDRMAN_TRIED_BUCKET_COUNT, std::vector<int>(0)); - am->vvNew = std::vector<std::set<int> >(ADDRMAN_NEW_BUCKET_COUNT, std::set<int>()); - for (int n = 0; n < am->nNew; n++) - { - CAddrInfo &info = am->mapInfo[n]; - READWRITE(info); - am->mapAddr[info] = n; - info.nRandomPos = vRandom.size(); - am->vRandom.push_back(n); - if (nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) - { - am->vvNew[info.GetNewBucket(am->nKey)].insert(n); - info.nRefCount++; - } - } - am->nIdCount = am->nNew; - int nLost = 0; - for (int n = 0; n < am->nTried; n++) - { - CAddrInfo info; - READWRITE(info); - std::vector<int> &vTried = am->vvTried[info.GetTriedBucket(am->nKey)]; - if (vTried.size() < ADDRMAN_TRIED_BUCKET_SIZE) - { - info.nRandomPos = vRandom.size(); - info.fInTried = true; - am->vRandom.push_back(am->nIdCount); - am->mapInfo[am->nIdCount] = info; - am->mapAddr[info] = am->nIdCount; - vTried.push_back(am->nIdCount); - am->nIdCount++; - } else { - nLost++; - } - } - am->nTried -= nLost; - for (int b = 0; b < nUBuckets; b++) - { - std::set<int> &vNew = am->vvNew[b]; - int nSize = 0; - READWRITE(nSize); - for (int n = 0; n < nSize; n++) - { - int nIndex = 0; - READWRITE(nIndex); - CAddrInfo &info = am->mapInfo[nIndex]; - if (nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) - { - info.nRefCount++; - vNew.insert(nIndex); - } - } + nLost++; + } + } + nTried -= nLost; + for (int b = 0; b < nUBuckets; b++) { + std::set<int> &vNew = vvNew[b]; + int nSize = 0; + s >> nSize; + for (int n = 0; n < nSize; n++) { + int nIndex = 0; + s >> nIndex; + CAddrInfo &info = mapInfo[nIndex]; + if (nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) { + info.nRefCount++; + vNew.insert(nIndex); } } } - });) + } + + unsigned int GetSerializeSize(int nType, int nVersion) const + { + return (CSizeComputer(nType, nVersion) << *this).size(); + } CAddrMan() : vRandom(0), vvTried(ADDRMAN_TRIED_BUCKET_COUNT, std::vector<int>(0)), vvNew(ADDRMAN_NEW_BUCKET_COUNT, std::set<int>()) { 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/chainparamsbase.cpp b/src/chainparamsbase.cpp index 19a9e72cc9..720e24c4a8 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -91,3 +91,7 @@ bool SelectBaseParamsFromCommandLine() { } return true; } + +bool AreBaseParamsConfigured() { + return pCurrentBaseParams != NULL; +} diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index 4a3b268909..4398f69548 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -49,4 +49,10 @@ void SelectBaseParams(CBaseChainParams::Network network); */ bool SelectBaseParamsFromCommandLine(); +/** + * Return true if SelectBaseParamsFromCommandLine() has been called to select + * a network. + */ +bool AreBaseParamsConfigured(); + #endif diff --git a/src/coins.cpp b/src/coins.cpp index 13a4ea95cd..fe40911db7 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -4,6 +4,8 @@ #include "coins.h" +#include "random.h" + #include <assert.h> // calculate number of bytes for the bitmask, and its number of non-zero bytes @@ -69,6 +71,8 @@ void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } bool CCoinsViewBacked::BatchWrite(const CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } bool CCoinsViewBacked::GetStats(CCoinsStats &stats) { return base->GetStats(stats); } +CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} + CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), hashBlock(0) { } bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) { @@ -84,8 +88,8 @@ bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) { } CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) { - CCoinsMap::iterator it = cacheCoins.lower_bound(txid); - if (it != cacheCoins.end() && it->first == txid) + CCoinsMap::iterator it = cacheCoins.find(txid); + if (it != cacheCoins.end()) return it; CCoins tmp; if (!base->GetCoins(txid,tmp)) @@ -107,7 +111,12 @@ bool CCoinsViewCache::SetCoins(const uint256 &txid, const CCoins &coins) { } bool CCoinsViewCache::HaveCoins(const uint256 &txid) { - return FetchCoins(txid) != cacheCoins.end(); + CCoinsMap::iterator it = FetchCoins(txid); + // We're using vtx.empty() instead of IsPruned here for performance reasons, + // as we only care about the case where an transaction was replaced entirely + // in a reorganization (which wipes vout entirely, as opposed to spending + // which just cleans individual outputs). + return (it != cacheCoins.end() && !it->second.vout.empty()); } uint256 CCoinsViewCache::GetBestBlock() { diff --git a/src/coins.h b/src/coins.h index c57a5ec722..9f90fe6bd0 100644 --- a/src/coins.h +++ b/src/coins.h @@ -13,6 +13,7 @@ #include <stdint.h> #include <boost/foreach.hpp> +#include <boost/unordered_map.hpp> /** pruned version of CTransaction: only retains metadata and unspent transaction outputs * @@ -239,7 +240,19 @@ public: } }; -typedef std::map<uint256,CCoins> CCoinsMap; +class CCoinsKeyHasher +{ +private: + uint256 salt; + +public: + CCoinsKeyHasher(); + uint64_t operator()(const uint256& key) const { + return key.GetHash(salt); + } +}; + +typedef boost::unordered_map<uint256, CCoins, CCoinsKeyHasher> CCoinsMap; struct CCoinsStats { diff --git a/src/compat.h b/src/compat.h index 8fbafb6cce..1b3a60d11b 100644 --- a/src/compat.h +++ b/src/compat.h @@ -59,19 +59,4 @@ typedef u_int SOCKET; #define SOCKET_ERROR -1 #endif -inline int myclosesocket(SOCKET& hSocket) -{ - if (hSocket == INVALID_SOCKET) - return WSAENOTSOCK; -#ifdef WIN32 - int ret = closesocket(hSocket); -#else - int ret = close(hSocket); -#endif - hSocket = INVALID_SOCKET; - return ret; -} -#define closesocket(s) myclosesocket(s) - - #endif 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 ca30abac90..127529382f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -127,12 +127,14 @@ void Shutdown() StopNode(); UnregisterNodeSignals(GetNodeSignals()); - boost::filesystem::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME; - CAutoFile est_fileout = CAutoFile(fopen(est_path.string().c_str(), "wb"), SER_DISK, CLIENT_VERSION); - if (est_fileout) - mempool.WriteFeeEstimates(est_fileout); - else - LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string()); + { + boost::filesystem::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME; + CAutoFile est_fileout(fopen(est_path.string().c_str(), "wb"), SER_DISK, CLIENT_VERSION); + if (est_fileout) + mempool.WriteFeeEstimates(est_fileout); + else + LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string()); + } { LOCK(cs_main); @@ -246,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"; @@ -675,7 +678,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()) @@ -1224,7 +1230,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 4f6b442f3a..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; } @@ -1918,7 +1855,7 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C // Update the on-disk chain state. bool static WriteChainState(CValidationState &state) { static int64_t nLastWrite = 0; - if (!IsInitialBlockDownload() || pcoinsTip->GetCacheSize() > nCoinCacheSize || GetTimeMicros() > nLastWrite + 600*1000000) { + if (pcoinsTip->GetCacheSize() > nCoinCacheSize || (!IsInitialBlockDownload() && GetTimeMicros() > nLastWrite + 600*1000000)) { // Typical CCoins structures on disk are around 100 bytes in size. // Pushing a new one to the database can cause it to be written // twice (once in the log, and once in the tables). This is already @@ -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; } @@ -2517,7 +2455,7 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, return false; if (!CheckBlock(block, state)) { - if (state.Invalid() && !state.CorruptionPossible()) { + if (state.IsInvalid() && !state.CorruptionPossible()) { pindex->nStatus |= BLOCK_FAILED_VALID; } return false; 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 3b3d91d652..e004fbeb73 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -332,7 +332,7 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha { if (!RecvLine(hSocket, strLine)) { - closesocket(hSocket); + CloseSocket(hSocket); return false; } if (pszKeyword == NULL) @@ -343,7 +343,7 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha break; } } - closesocket(hSocket); + CloseSocket(hSocket); if (strLine.find("<") != string::npos) strLine = strLine.substr(0, strLine.find("<")); strLine = strLine.substr(strspn(strLine.c_str(), " \t\n\r")); @@ -357,7 +357,7 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha return true; } } - closesocket(hSocket); + CloseSocket(hSocket); return error("GetMyExternalIP() : connection closed"); } @@ -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) @@ -501,14 +501,8 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest) addrman.Attempt(addrConnect); // Set to non-blocking -#ifdef WIN32 - u_long nOne = 1; - if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) - LogPrintf("ConnectSocket() : ioctlsocket non-blocking setting failed, error %s\n", NetworkErrorString(WSAGetLastError())); -#else - if (fcntl(hSocket, F_SETFL, O_NONBLOCK) == SOCKET_ERROR) - LogPrintf("ConnectSocket() : fcntl non-blocking setting failed, error %s\n", NetworkErrorString(errno)); -#endif + if (!SetSocketNonBlocking(hSocket, true)) + LogPrintf("ConnectNode: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); // Add node CNode* pnode = new CNode(hSocket, addrConnect, pszDest ? pszDest : "", false); @@ -533,8 +527,7 @@ void CNode::CloseSocketDisconnect() if (hSocket != INVALID_SOCKET) { LogPrint("net", "disconnecting peer=%d\n", id); - closesocket(hSocket); - hSocket = INVALID_SOCKET; + CloseSocket(hSocket); } // in case this fails, we'll empty the recv buffer when the CNode is deleted @@ -975,12 +968,12 @@ void ThreadSocketHandler() } else if (nInbound >= nMaxConnections - MAX_OUTBOUND_CONNECTIONS) { - closesocket(hSocket); + CloseSocket(hSocket); } else if (CNode::IsBanned(addr) && !whitelisted) { LogPrintf("connection from %s dropped (banned)\n", addr.ToString()); - closesocket(hSocket); + CloseSocket(hSocket); } else { @@ -1479,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; @@ -1643,14 +1636,9 @@ bool BindListenPort(const CService &addrBind, string& strError, bool fWhiteliste setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (void*)&nOne, sizeof(int)); #endif -#ifdef WIN32 // Set to non-blocking, incoming connections will also inherit this - if (ioctlsocket(hListenSocket, FIONBIO, (u_long*)&nOne) == SOCKET_ERROR) -#else - if (fcntl(hListenSocket, F_SETFL, O_NONBLOCK) == SOCKET_ERROR) -#endif - { - strError = strprintf("Error: Couldn't set properties on socket for incoming connections (error %s)", NetworkErrorString(WSAGetLastError())); + if (!SetSocketNonBlocking(hListenSocket, true)) { + strError = strprintf("BindListenPort: Setting listening socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError); return false; } @@ -1679,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()); @@ -1688,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; } @@ -1817,11 +1807,11 @@ public: // Close sockets BOOST_FOREACH(CNode* pnode, vNodes) if (pnode->hSocket != INVALID_SOCKET) - closesocket(pnode->hSocket); + CloseSocket(pnode->hSocket); BOOST_FOREACH(ListenSocket& hListenSocket, vhListenSocket) if (hListenSocket.socket != INVALID_SOCKET) - if (closesocket(hListenSocket.socket) == SOCKET_ERROR) - LogPrintf("closesocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); + if (!CloseSocket(hListenSocket.socket)) + LogPrintf("CloseSocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); // clean up some globals (to help leak detection) BOOST_FOREACH(CNode *pnode, vNodes) @@ -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); @@ -357,8 +358,7 @@ public: { if (hSocket != INVALID_SOCKET) { - closesocket(hSocket); - hSocket = INVALID_SOCKET; + CloseSocket(hSocket); } if (pfilter) delete pfilter; diff --git a/src/netbase.cpp b/src/netbase.cpp index 067cfa024b..af6d11f0e2 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -7,10 +7,6 @@ #include "bitcoin-config.h" #endif -#ifdef HAVE_GETADDRINFO_A -#include <netdb.h> -#endif - #include "netbase.h" #include "hash.h" @@ -18,6 +14,10 @@ #include "uint256.h" #include "util.h" +#ifdef HAVE_GETADDRINFO_A +#include <netdb.h> +#endif + #ifndef WIN32 #if HAVE_INET_PTON #include <arpa/inet.h> @@ -218,7 +218,7 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) LogPrintf("SOCKS5 connecting %s\n", strDest); if (strDest.size() > 255) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Hostname too long"); } char pszSocks5Init[] = "\5\1\0"; @@ -227,18 +227,18 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) ssize_t ret = send(hSocket, pszSocks5Init, nSize, MSG_NOSIGNAL); if (ret != nSize) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error sending to proxy"); } char pchRet1[2]; if (recv(hSocket, pchRet1, 2, 0) != 2) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading proxy response"); } if (pchRet1[0] != 0x05 || pchRet1[1] != 0x00) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Proxy failed to initialize"); } string strSocks5("\5\1"); @@ -250,23 +250,23 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) ret = send(hSocket, strSocks5.c_str(), strSocks5.size(), MSG_NOSIGNAL); if (ret != (ssize_t)strSocks5.size()) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error sending to proxy"); } char pchRet2[4]; if (recv(hSocket, pchRet2, 4, 0) != 4) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading proxy response"); } if (pchRet2[0] != 0x05) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Proxy failed to accept request"); } if (pchRet2[1] != 0x00) { - closesocket(hSocket); + CloseSocket(hSocket); switch (pchRet2[1]) { case 0x01: return error("Proxy error: general failure"); @@ -282,7 +282,7 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) } if (pchRet2[2] != 0x00) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error: malformed proxy response"); } char pchRet3[256]; @@ -294,23 +294,23 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) { ret = recv(hSocket, pchRet3, 1, 0) != 1; if (ret) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading from proxy"); } int nRecv = pchRet3[0]; ret = recv(hSocket, pchRet3, nRecv, 0) != nRecv; break; } - default: closesocket(hSocket); return error("Error: malformed proxy response"); + default: CloseSocket(hSocket); return error("Error: malformed proxy response"); } if (ret) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading from proxy"); } if (recv(hSocket, pchRet3, 2, 0) != 2) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading from proxy"); } LogPrintf("SOCKS5 connected %s\n", strDest); @@ -331,22 +331,15 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); if (hSocket == INVALID_SOCKET) return false; + #ifdef SO_NOSIGPIPE int set = 1; setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)); #endif -#ifdef WIN32 - u_long fNonblock = 1; - if (ioctlsocket(hSocket, FIONBIO, &fNonblock) == SOCKET_ERROR) -#else - int fFlags = fcntl(hSocket, F_GETFL, 0); - if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == -1) -#endif - { - closesocket(hSocket); - return false; - } + // Set to non-blocking + if (!SetSocketNonBlocking(hSocket, true)) + return error("ConnectSocketDirectly: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) { @@ -365,13 +358,13 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe if (nRet == 0) { LogPrint("net", "connection to %s timeout\n", addrConnect.ToString()); - closesocket(hSocket); + CloseSocket(hSocket); return false; } if (nRet == SOCKET_ERROR) { LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); - closesocket(hSocket); + CloseSocket(hSocket); return false; } socklen_t nRetSize = sizeof(nRet); @@ -382,13 +375,13 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe #endif { LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); - closesocket(hSocket); + CloseSocket(hSocket); return false; } if (nRet != 0) { LogPrintf("connect() to %s failed after select(): %s\n", addrConnect.ToString(), NetworkErrorString(nRet)); - closesocket(hSocket); + CloseSocket(hSocket); return false; } } @@ -399,25 +392,15 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe #endif { LogPrintf("connect() to %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); - closesocket(hSocket); + CloseSocket(hSocket); return false; } } - // this isn't even strictly necessary - // CNode::ConnectNode immediately turns the socket back to non-blocking - // but we'll turn it back to blocking just in case -#ifdef WIN32 - fNonblock = 0; - if (ioctlsocket(hSocket, FIONBIO, &fNonblock) == SOCKET_ERROR) -#else - fFlags = fcntl(hSocket, F_GETFL, 0); - if (fcntl(hSocket, F_SETFL, fFlags & ~O_NONBLOCK) == SOCKET_ERROR) -#endif - { - closesocket(hSocket); - return false; - } + // This is required when using SOCKS5 proxy! + // CNode::ConnectNode turns the socket back to non-blocking. + if (!SetSocketNonBlocking(hSocket, false)) + return error("ConnectSocketDirectly: Setting socket to blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); hSocketRet = hSocket; return true; @@ -1258,3 +1241,45 @@ std::string NetworkErrorString(int err) return strprintf("%s (%d)", s, err); } #endif + +bool CloseSocket(SOCKET& hSocket) +{ + if (hSocket == INVALID_SOCKET) + return false; +#ifdef WIN32 + int ret = closesocket(hSocket); +#else + int ret = close(hSocket); +#endif + hSocket = INVALID_SOCKET; + return ret != SOCKET_ERROR; +} + +bool SetSocketNonBlocking(SOCKET& hSocket, bool fNonBlocking) +{ + if (fNonBlocking) { +#ifdef WIN32 + u_long nOne = 1; + if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) { +#else + int fFlags = fcntl(hSocket, F_GETFL, 0); + if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == SOCKET_ERROR) { +#endif + CloseSocket(hSocket); + return false; + } + } else { +#ifdef WIN32 + u_long nZero = 0; + if (ioctlsocket(hSocket, FIONBIO, &nZero) == SOCKET_ERROR) { +#else + int fFlags = fcntl(hSocket, F_GETFL, 0); + if (fcntl(hSocket, F_SETFL, fFlags & ~O_NONBLOCK) == SOCKET_ERROR) { +#endif + CloseSocket(hSocket); + return false; + } + } + + return true; +} diff --git a/src/netbase.h b/src/netbase.h index ad1e230834..7d83e35344 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -178,5 +178,9 @@ bool ConnectSocket(const CService &addr, SOCKET& hSocketRet, int nTimeout = nCon bool ConnectSocketByName(CService &addr, SOCKET& hSocketRet, const char *pszDest, int portDefault = 0, int nTimeout = nConnectTimeout); /** Return readable error string for a network error code */ std::string NetworkErrorString(int err); +/** Close socket and set hSocket to INVALID_SOCKET */ +bool CloseSocket(SOCKET& hSocket); +/** Disable or enable blocking-mode for a socket */ +bool SetSocketNonBlocking(SOCKET& hSocket, bool fNonBlocking); #endif 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 7c4af25edf..7bf531f538 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -34,6 +34,7 @@ #include <boost/filesystem/operations.hpp> #include <QApplication> +#include <QDebug> #include <QLibraryInfo> #include <QLocale> #include <QMessageBox> @@ -52,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 @@ -237,7 +244,7 @@ void BitcoinCore::initialize() { try { - LogPrintf("Running AppInit2 in thread\n"); + qDebug() << __func__ << ": Running AppInit2 in thread"; int rv = AppInit2(threadGroup); if(rv) { @@ -258,11 +265,11 @@ void BitcoinCore::shutdown() { try { - LogPrintf("Running Shutdown in thread\n"); + qDebug() << __func__ << ": Running Shutdown in thread"; threadGroup.interrupt_all(); threadGroup.join_all(); Shutdown(); - LogPrintf("Shutdown finished\n"); + qDebug() << __func__ << ": Shutdown finished"; emit shutdownResult(1); } catch (std::exception& e) { handleRunawayException(&e); @@ -285,15 +292,17 @@ BitcoinApplication::BitcoinApplication(int &argc, char **argv): returnValue(0) { setQuitOnLastWindowClosed(false); - startThread(); } BitcoinApplication::~BitcoinApplication() { - LogPrintf("Stopping thread\n"); - emit stopThread(); - coreThread->wait(); - LogPrintf("Stopped thread\n"); + if(coreThread) + { + qDebug() << __func__ << ": Stopping thread"; + emit stopThread(); + coreThread->wait(); + qDebug() << __func__ << ": Stopped thread"; + } delete window; window = 0; @@ -336,6 +345,8 @@ void BitcoinApplication::createSplashScreen(bool isaTestNet) void BitcoinApplication::startThread() { + if(coreThread) + return; coreThread = new QThread(this); BitcoinCore *executor = new BitcoinCore(); executor->moveToThread(coreThread); @@ -355,13 +366,15 @@ void BitcoinApplication::startThread() void BitcoinApplication::requestInitialize() { - LogPrintf("Requesting initialize\n"); + qDebug() << __func__ << ": Requesting initialize"; + startThread(); emit requestedInitialize(); } void BitcoinApplication::requestShutdown() { - LogPrintf("Requesting shutdown\n"); + qDebug() << __func__ << ": Requesting shutdown"; + startThread(); window->hide(); window->setClientModel(0); pollShutdownTimer->stop(); @@ -383,7 +396,7 @@ void BitcoinApplication::requestShutdown() void BitcoinApplication::initializeResult(int retval) { - LogPrintf("Initialization result: %i\n", retval); + qDebug() << __func__ << ": Initialization result: " << retval; // Set exit result: 0 if successful, 1 if failure returnValue = retval ? 0 : 1; if(retval) @@ -393,8 +406,6 @@ void BitcoinApplication::initializeResult(int retval) paymentServer->setOptionsModel(optionsModel); #endif - emit splashFinished(window); - clientModel = new ClientModel(optionsModel); window->setClientModel(clientModel); @@ -411,6 +422,8 @@ void BitcoinApplication::initializeResult(int retval) } #endif + emit splashFinished(window); + // If -min option passed, start window minimized. if(GetBoolArg("-min", false)) { @@ -438,7 +451,7 @@ void BitcoinApplication::initializeResult(int retval) void BitcoinApplication::shutdownResult(int retval) { - LogPrintf("Shutdown result: %i\n", retval); + qDebug() << __func__ << ": Shutdown result: " << retval; quit(); // Exit main loop after shutdown finished } 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 " " + +// 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 " " + +// 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 " " + +// 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 " " + +// 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/rpcnet.cpp b/src/rpcnet.cpp index 680717930b..88e7c4ab07 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -79,6 +79,7 @@ Value getpeerinfo(const Array& params, bool fHelp) "\nbResult:\n" "[\n" " {\n" + " \"id\": n, (numeric) Peer index\n" " \"addr\":\"host:port\", (string) The ip address and port of the peer\n" " \"addrlocal\":\"ip:port\", (string) local address\n" " \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n" @@ -97,8 +98,7 @@ Value getpeerinfo(const Array& params, bool fHelp) " \"syncnode\" : true|false (booleamn) if sync node\n" " }\n" " ,...\n" - "}\n" - + "]\n" "\nExamples:\n" + HelpExampleCli("getpeerinfo", "") + HelpExampleRpc("getpeerinfo", "") @@ -113,6 +113,7 @@ Value getpeerinfo(const Array& params, bool fHelp) Object obj; CNodeStateStats statestats; bool fStateStats = GetNodeStateStats(stats.nodeid, statestats); + obj.push_back(Pair("id", stats.nodeid)); obj.push_back(Pair("addr", stats.addrName)); if (!(stats.addrLocal.empty())) obj.push_back(Pair("addrlocal", stats.addrLocal)); 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/serialize.h b/src/serialize.h index 5ac85554c6..f876efd9b5 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -830,6 +830,35 @@ struct ser_streamplaceholder typedef std::vector<char, zero_after_free_allocator<char> > CSerializeData; +class CSizeComputer +{ +protected: + size_t nSize; + +public: + int nType; + int nVersion; + + CSizeComputer(int nTypeIn, int nVersionIn) : nSize(0), nType(nTypeIn), nVersion(nVersionIn) {} + + CSizeComputer& write(const char *psz, int nSize) + { + this->nSize += nSize; + return *this; + } + + template<typename T> + CSizeComputer& operator<<(const T& obj) + { + ::Serialize(*this, obj, nType, nVersion); + return (*this); + } + + size_t size() const { + return nSize; + } +}; + /** Double ended buffer combining vector and stream-like interfaces. * * >> and << read and write unformatted data using the above serialization templates. 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/uint256.cpp b/src/uint256.cpp index 3392f1e9bc..08c05594fd 100644 --- a/src/uint256.cpp +++ b/src/uint256.cpp @@ -290,3 +290,46 @@ uint32_t uint256::GetCompact(bool fNegative) const nCompact |= (fNegative && (nCompact & 0x007fffff) ? 0x00800000 : 0); return nCompact; } + +static void inline HashMix(uint32_t& a, uint32_t& b, uint32_t& c) +{ + // Taken from lookup3, by Bob Jenkins. + a -= c; a ^= ((c << 4) | (c >> 28)); c += b; + b -= a; b ^= ((a << 6) | (a >> 26)); a += c; + c -= b; c ^= ((b << 8) | (b >> 24)); b += a; + a -= c; a ^= ((c << 16) | (c >> 16)); c += b; + b -= a; b ^= ((a << 19) | (a >> 13)); a += c; + c -= b; c ^= ((b << 4) | (b >> 28)); b += a; +} + +static void inline HashFinal(uint32_t& a, uint32_t& b, uint32_t& c) +{ + // Taken from lookup3, by Bob Jenkins. + c ^= b; c -= ((b << 14) | (b >> 18)); + a ^= c; a -= ((c << 11) | (c >> 21)); + b ^= a; b -= ((a << 25) | (a >> 7)); + c ^= b; c -= ((b << 16) | (b >> 16)); + a ^= c; a -= ((c << 4) | (c >> 28)); + b ^= a; b -= ((a << 14) | (a >> 18)); + c ^= b; c -= ((b << 24) | (b >> 8)); +} + +uint64_t uint256::GetHash(const uint256 &salt) const +{ + uint32_t a, b, c; + a = b = c = 0xdeadbeef + (WIDTH << 2); + + a += pn[0] ^ salt.pn[0]; + b += pn[1] ^ salt.pn[1]; + c += pn[2] ^ salt.pn[2]; + HashMix(a, b, c); + a += pn[3] ^ salt.pn[3]; + b += pn[4] ^ salt.pn[4]; + c += pn[5] ^ salt.pn[5]; + HashMix(a, b, c); + a += pn[6] ^ salt.pn[6]; + b += pn[7] ^ salt.pn[7]; + HashFinal(a, b, c); + + return ((((uint64_t)b) << 32) | c); +} diff --git a/src/uint256.h b/src/uint256.h index 82db7758c9..ad0a56f447 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -21,7 +21,7 @@ public: template<unsigned int BITS> class base_uint { -private: +protected: enum { WIDTH=BITS/32 }; uint32_t pn[WIDTH]; public: @@ -322,6 +322,8 @@ public: // implementation accident. uint256& SetCompact(uint32_t nCompact, bool *pfNegative = NULL, bool *pfOverflow = NULL); uint32_t GetCompact(bool fNegative = false) const; + + uint64_t GetHash(const uint256& salt) const; }; #endif diff --git a/src/util.cpp b/src/util.cpp index ce31619eca..d3fa5182f3 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -205,7 +205,7 @@ int LogPrintStr(const std::string &str) // print to console ret = fwrite(str.data(), 1, str.size(), stdout); } - else if (fPrintToDebugLog) + else if (fPrintToDebugLog && AreBaseParamsConfigured()) { static bool fStartedNewLine = true; boost::call_once(&DebugPrintInit, debugPrintInitFlag); 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; }; |