diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index acfe968d3..398dfd2ed 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -1,67 +1,70 @@ # Copyright (c) 2013-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. bin_PROGRAMS += qt/test/test_bitcoin-qt TESTS += qt/test/test_bitcoin-qt TEST_QT_MOC_CPP = \ + qt/test/moc_bitcoinaddressvalidatortests.cpp \ qt/test/moc_compattests.cpp \ qt/test/moc_guiutiltests.cpp \ qt/test/moc_rpcnestedtests.cpp \ qt/test/moc_uritests.cpp if ENABLE_WALLET TEST_QT_MOC_CPP += qt/test/moc_paymentservertests.cpp endif TEST_QT_H = \ + qt/test/bitcoinaddressvalidatortests.h \ qt/test/compattests.h \ qt/test/guiutiltests.h \ qt/test/rpcnestedtests.h \ qt/test/uritests.h \ qt/test/paymentrequestdata.h \ qt/test/paymentservertests.h qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS) qt_test_test_bitcoin_qt_SOURCES = \ + qt/test/bitcoinaddressvalidatortests.cpp \ qt/test/compattests.cpp \ qt/test/guiutiltests.cpp \ qt/test/rpcnestedtests.cpp \ qt/test/test_main.cpp \ qt/test/uritests.cpp \ $(TEST_QT_H) if ENABLE_WALLET qt_test_test_bitcoin_qt_SOURCES += \ qt/test/paymentservertests.cpp endif nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP) qt_test_test_bitcoin_qt_LDADD = $(LIBBITCOINQT) $(LIBBITCOIN_SERVER) if ENABLE_WALLET qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif if ENABLE_ZMQ qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) \ $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) qt_test_test_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) qt_test_test_bitcoin_qt_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS) CLEAN_BITCOIN_QT_TEST = $(TEST_QT_MOC_CPP) qt/test/*.gcda qt/test/*.gcno CLEANFILES += $(CLEAN_BITCOIN_QT_TEST) test_bitcoin_qt : qt/test/test_bitcoin-qt$(EXEEXT) test_bitcoin_qt_check : qt/test/test_bitcoin-qt$(EXEEXT) FORCE $(MAKE) check-TESTS TESTS=$^ test_bitcoin_qt_clean: FORCE rm -f $(CLEAN_BITCOIN_QT_TEST) $(qt_test_test_bitcoin_qt_OBJECTS) diff --git a/src/cashaddr.cpp b/src/cashaddr.cpp index 0ed894016..d1b11148c 100644 --- a/src/cashaddr.cpp +++ b/src/cashaddr.cpp @@ -1,295 +1,300 @@ // Copyright (c) 2017 Pieter Wuille // Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "cashaddr.h" namespace { typedef std::vector data; /** * The cashaddr character set for encoding. */ const char *CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; /** * The cashaddr character set for decoding. */ const int8_t CHARSET_REV[128] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; /** * Concatenate two byte arrays. */ data Cat(data x, const data &y) { x.insert(x.end(), y.begin(), y.end()); return x; } /** * This function will compute what 8 5-bit values to XOR into the last 8 input * values, in order to make the checksum 0. These 8 values are packed together * in a single 40-bit integer. The higher bits correspond to earlier values. */ uint64_t PolyMod(const data &v) { /** * The input is interpreted as a list of coefficients of a polynomial over F * = GF(32), with an implicit 1 in front. If the input is [v0,v1,v2,v3,v4], * that polynomial is v(x) = 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. * The implicit 1 guarantees that [v0,v1,v2,...] has a distinct checksum * from [0,v0,v1,v2,...]. * * The output is a 40-bit integer whose 5-bit groups are the coefficients of * the remainder of v(x) mod g(x), where g(x) is the cashaddr generator, x^8 * + {19}*x^7 + {3}*x^6 + {25}*x^5 + {11}*x^4 + {25}*x^3 + {3}*x^2 + {19}*x * + {1}. g(x) is chosen in such a way that the resulting code is a BCH * code, guaranteeing detection of up to 4 errors within a window of 1025 * characters. Among the various possible BCH codes, one was selected to in * fact guarantee detection of up to 5 errors within a window of 160 * characters and 6 erros within a window of 126 characters. In addition, * the code guarantee the detection of a burst of up to 8 errors. * * Note that the coefficients are elements of GF(32), here represented as * decimal numbers between {}. In this finite field, addition is just XOR of * the corresponding numbers. For example, {27} + {13} = {27 ^ 13} = {22}. * Multiplication is more complicated, and requires treating the bits of * values themselves as coefficients of a polynomial over a smaller field, * GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, * {5} * {26} = (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + * a^3 + a) = a^6 + a^5 + a^4 + a = a^3 + 1 (mod a^5 + a^3 + 1) = {9}. * * During the course of the loop below, `c` contains the bitpacked * coefficients of the polynomial constructed from just the values of v that * were processed so far, mod g(x). In the above example, `c` initially * corresponds to 1 mod (x), and after processing 2 inputs of v, it * corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the * starting value for `c`. */ uint64_t c = 1; for (uint8_t d : v) { /** * We want to update `c` to correspond to a polynomial with one extra * term. If the initial value of `c` consists of the coefficients of * c(x) = f(x) mod g(x), we modify it to correspond to * c'(x) = (f(x) * x + d) mod g(x), where d is the next input to * process. * * Simplifying: * c'(x) = (f(x) * x + d) mod g(x) * ((f(x) mod g(x)) * x + d) mod g(x) * (c(x) * x + d) mod g(x) * If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to * compute * c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + d * mod g(x) * = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + d * mod g(x) * = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + * c5*x + d * If we call (x^6 mod g(x)) = k(x), this can be written as * c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + d) + c0*k(x) */ // First, determine the value of c0: uint8_t c0 = c >> 35; // Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + d: c = ((c & 0x07ffffffff) << 5) ^ d; // Finally, for each set bit n in c0, conditionally add {2^n}k(x): if (c0 & 0x01) { // k(x) = {19}*x^7 + {3}*x^6 + {25}*x^5 + {11}*x^4 + {25}*x^3 + // {3}*x^2 + {19}*x + {1} c ^= 0x98f2bc8e61; } if (c0 & 0x02) { // {2}k(x) = {15}*x^7 + {6}*x^6 + {27}*x^5 + {22}*x^4 + {27}*x^3 + // {6}*x^2 + {15}*x + {2} c ^= 0x79b76d99e2; } if (c0 & 0x04) { // {4}k(x) = {30}*x^7 + {12}*x^6 + {31}*x^5 + {5}*x^4 + {31}*x^3 + // {12}*x^2 + {30}*x + {4} c ^= 0xf33e5fb3c4; } if (c0 & 0x08) { // {8}k(x) = {21}*x^7 + {24}*x^6 + {23}*x^5 + {10}*x^4 + {23}*x^3 + // {24}*x^2 + {21}*x + {8} c ^= 0xae2eabe2a8; } if (c0 & 0x10) { // {16}k(x) = {3}*x^7 + {25}*x^6 + {7}*x^5 + {20}*x^4 + {7}*x^3 + // {25}*x^2 + {3}*x + {16} c ^= 0x1e4f43e470; } } /** * PolyMod computes what value to xor into the final values to make the * checksum 0. However, if we required that the checksum was 0, it would be * the case that appending a 0 to a valid list of values would result in a * new valid list. For that reason, cashaddr requires the resulting checksum * to be 1 instead. */ return c ^ 1; } /** * Convert to lower case. * * Assume the input is a character. */ inline uint8_t LowerCase(uint8_t c) { // ASCII black magic. return c | 0x20; } /** * Expand the address prefix for the checksum computation. */ data ExpandPrefix(const std::string &prefix) { data ret; ret.resize(prefix.size() + 1); for (size_t i = 0; i < prefix.size(); ++i) { ret[i] = prefix[i] & 0x1f; } ret[prefix.size()] = 0; return ret; } /** * Verify a checksum. */ bool VerifyChecksum(const std::string &prefix, const data &payload) { return PolyMod(Cat(ExpandPrefix(prefix), payload)) == 0; } /** * Create a checksum. */ data CreateChecksum(const std::string &prefix, const data &payload) { data enc = Cat(ExpandPrefix(prefix), payload); // Append 8 zeroes. enc.resize(enc.size() + 8); // Determine what to XOR into those 8 zeroes. uint64_t mod = PolyMod(enc); data ret(8); for (size_t i = 0; i < 8; ++i) { // Convert the 5-bit groups in mod to checksum values. ret[i] = (mod >> (5 * (7 - i))) & 0x1f; } return ret; } } // namespace namespace cashaddr { /** * Encode a cashaddr string. */ std::string Encode(const std::string &prefix, const data &payload) { data checksum = CreateChecksum(prefix, payload); data combined = Cat(payload, checksum); std::string ret = prefix + ':'; ret.reserve(ret.size() + combined.size()); for (uint8_t c : combined) { ret += CHARSET[c]; } return ret; } /** * Decode a cashaddr string. */ std::pair Decode(const std::string &str) { // Go over the string and do some sanity checks. bool lower = false, upper = false; size_t prefixSize = 0; for (size_t i = 0; i < str.size(); ++i) { uint8_t c = str[i]; if (c >= 'a' && c <= 'z') { lower = true; continue; } if (c >= 'A' && c <= 'Z') { upper = true; continue; } if (c >= '0' && c <= '9') { // We cannot have numbers in the prefix. if (prefixSize == 0) { return {}; } continue; } if (c == ':') { // The separator must not be the first character, and there must not // be 2 separators. if (i == 0 || prefixSize != 0) { return {}; } prefixSize = i; continue; } // We have an unexpected character. return {}; } // We must have a prefix and a data part and we can't have both uppercase // and lowercase. if ((prefixSize == 0) || (upper && lower)) { return {}; } // Get the prefix. std::string prefix; prefix.reserve(prefixSize); for (size_t i = 0; i < prefixSize; ++i) { prefix += LowerCase(str[i]); } // Decode values. const size_t valuesSize = str.size() - 1 - prefixSize; data values(valuesSize); for (size_t i = 0; i < valuesSize; ++i) { uint8_t c = str[i + prefixSize + 1]; // We have an invalid char in there. if (c > 127 || CHARSET_REV[c] == -1) { return {}; } values[i] = CHARSET_REV[c]; } // Verify the checksum. if (!VerifyChecksum(prefix, values)) { return {}; } return {prefix, data(values.begin(), values.end() - 8)}; } +std::vector EncodingCharset() { + const size_t size = 32; + return std::vector(CHARSET, CHARSET + size); +} + } // namespace cashaddr diff --git a/src/cashaddr.h b/src/cashaddr.h index 385f3a39e..11d6acaeb 100644 --- a/src/cashaddr.h +++ b/src/cashaddr.h @@ -1,25 +1,27 @@ // Copyright (c) 2017 Pieter Wuille // Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // Cashaddr is an address format inspired by bech32. #include #include #include namespace cashaddr { /** * Encode a cashaddr string. Returns the empty string in case of failure. */ std::string Encode(const std::string &prefix, const std::vector &values); /** * Decode a cashaddr string. Returns (prefix, data). Empty prefix means failure. */ std::pair> Decode(const std::string &str); +std::vector EncodingCharset(); + } // namespace cashaddr diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp index 080d1d52e..0b90f8a35 100644 --- a/src/qt/bitcoinaddressvalidator.cpp +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -1,84 +1,111 @@ // Copyright (c) 2011-2014 The Bitcoin Core developers +// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "bitcoinaddressvalidator.h" +#include "cashaddr.h" #include "dstencode.h" /* Base58 characters are: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" This is: - All numbers except for '0' - All upper-case letters except for 'I' and 'O' - All lower-case letters except for 'l' */ +static bool ValidLegacyInput(const QString &input) { + // Alphanumeric and not a 'forbidden' character + for (QChar ch : input) { + if (!(((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z')) && + ch != 'l' && ch != 'I' && ch != '0' && ch != 'O')) + return false; + } + return true; +} -BitcoinAddressEntryValidator::BitcoinAddressEntryValidator(QObject *parent) - : QValidator(parent) {} +static bool ValidCashaddrInput(const QString &prefix, const QString &input) { + + std::vector charset = cashaddr::EncodingCharset(); + + // Input may be incomplete. We're checking if it so far looks good. + + for (int i = 0; i < input.size(); ++i) { + char ch = std::tolower(input[i].toLatin1()); + + // Does the input have the right prefix? + if (i < prefix.size()) { + if (ch != prefix[i].toLatin1()) { + return false; + } + continue; + } + + // Payload, must use cashaddr charset. + if (std::find(begin(charset), end(charset), ch) == end(charset)) { + return false; + } + } + return true; +} +BitcoinAddressEntryValidator::BitcoinAddressEntryValidator( + const std::string &cashaddrprefix, QObject *parent) + : QValidator(parent), cashaddrprefix(cashaddrprefix) {} QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); // Empty address is "intermediate" input if (input.isEmpty()) return QValidator::Intermediate; // Correction for (int idx = 0; idx < input.size();) { bool removeChar = false; QChar ch = input.at(idx); // Corrections made are very conservative on purpose, to avoid // users unexpectedly getting away with typos that would normally // be detected, and thus sending to the wrong address. switch (ch.unicode()) { // Qt categorizes these as "Other_Format" not "Separator_Space" case 0x200B: // ZERO WIDTH SPACE case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE removeChar = true; break; default: break; } // Remove whitespace if (ch.isSpace()) removeChar = true; // To next character if (removeChar) input.remove(idx, 1); else ++idx; } // Validation - QValidator::State state = QValidator::Acceptable; - for (int idx = 0; idx < input.size(); ++idx) { - int ch = input.at(idx).unicode(); - - if (((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || - (ch >= 'A' && ch <= 'Z')) && - ch != 'l' && ch != 'I' && ch != '0' && ch != 'O') { - // Alphanumeric and not a 'forbidden' character - } else { - state = QValidator::Invalid; - } - } - - return state; + const QString cashPrefix = QString::fromStdString(cashaddrprefix) + ":"; + return (ValidLegacyInput(input) || ValidCashaddrInput(cashPrefix, input)) + ? QValidator::Acceptable + : QValidator::Invalid; } BitcoinAddressCheckValidator::BitcoinAddressCheckValidator(QObject *parent) : QValidator(parent) {} QValidator::State BitcoinAddressCheckValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); // Validate the passed Bitcoin address if (IsValidDestinationString(input.toStdString())) { return QValidator::Acceptable; } return QValidator::Invalid; } diff --git a/src/qt/bitcoinaddressvalidator.h b/src/qt/bitcoinaddressvalidator.h index 241e50c86..255e79e23 100644 --- a/src/qt/bitcoinaddressvalidator.h +++ b/src/qt/bitcoinaddressvalidator.h @@ -1,33 +1,39 @@ // Copyright (c) 2011-2014 The Bitcoin Core developers +// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_BITCOINADDRESSVALIDATOR_H #define BITCOIN_QT_BITCOINADDRESSVALIDATOR_H #include -/** Base58 entry widget validator, checks for valid characters and +/** + * Bitcoin address entry widget validator, checks for valid characters and * removes some whitespace. */ class BitcoinAddressEntryValidator : public QValidator { Q_OBJECT public: - explicit BitcoinAddressEntryValidator(QObject *parent); + explicit BitcoinAddressEntryValidator(const std::string &cashaddrprefix, + QObject *parent); State validate(QString &input, int &pos) const; + +private: + std::string cashaddrprefix; }; /** Bitcoin address widget validator, checks for a valid bitcoin address. */ class BitcoinAddressCheckValidator : public QValidator { Q_OBJECT public: explicit BitcoinAddressCheckValidator(QObject *parent); State validate(QString &input, int &pos) const; }; #endif // BITCOIN_QT_BITCOINADDRESSVALIDATOR_H diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 54fed4c7c..7e2e4df2c 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1,996 +1,998 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "guiutil.h" #include "bitcoinaddressvalidator.h" #include "bitcoinunits.h" #include "dstencode.h" #include "qvalidatedlineedit.h" #include "walletmodel.h" #include "config.h" #include "dstencode.h" #include "init.h" #include "policy/policy.h" #include "primitives/transaction.h" #include "protocol.h" #include "script/script.h" #include "script/standard.h" #include "util.h" #include "utilstrencodings.h" #ifdef WIN32 #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0501 #ifdef _WIN32_IE #undef _WIN32_IE #endif #define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX #endif #include "shellapi.h" #include "shlobj.h" #include "shlwapi.h" #endif #include #include #if BOOST_FILESYSTEM_VERSION >= 3 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::mightBeRichText #include #if QT_VERSION < 0x050000 #include #else #include #endif #if QT_VERSION >= 0x50200 #include #endif #if BOOST_FILESYSTEM_VERSION >= 3 static boost::filesystem::detail::utf8_codecvt_facet utf8; #endif #if defined(Q_OS_MAC) // These Mac includes must be done in the global namespace #include #include extern double NSAppKitVersionNumber; #if !defined(NSAppKitVersionNumber10_8) #define NSAppKitVersionNumber10_8 1187 #endif #if !defined(NSAppKitVersionNumber10_9) #define NSAppKitVersionNumber10_9 1265 #endif #endif namespace GUIUtil { const QString URI_SCHEME("bitcoincash"); QString dateTimeStr(const QDateTime &date) { return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm"); } QString dateTimeStr(qint64 nTime) { return dateTimeStr(QDateTime::fromTime_t((qint32)nTime)); } QFont fixedPitchFont() { #if QT_VERSION >= 0x50200 return QFontDatabase::systemFont(QFontDatabase::FixedFont); #else QFont font("Monospace"); #if QT_VERSION >= 0x040800 font.setStyleHint(QFont::Monospace); #else font.setStyleHint(QFont::TypeWriter); #endif return font; #endif } static std::string MakeAddrInvalid(std::string addr) { if (addr.size() < 2) { return ""; } // Checksum is at the end of the address. Swapping chars to make it invalid. std::swap(addr[addr.size() - 1], addr[addr.size() - 2]); if (!IsValidDestinationString(addr)) { return addr; } return ""; } std::string DummyAddress(const CChainParams ¶ms, const Config &cfg) { // Just some dummy data to generate an convincing random-looking (but // consistent) address static const std::vector dummydata = { 0xeb, 0x15, 0x23, 0x1d, 0xfc, 0xeb, 0x60, 0x92, 0x58, 0x86, 0xb6, 0x7d, 0x06, 0x52, 0x99, 0x92, 0x59, 0x15, 0xae, 0xb1}; const CTxDestination dstKey = CKeyID(uint160(dummydata)); return MakeAddrInvalid(EncodeDestination(dstKey, params, cfg)); } void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) { parent->setFocusProxy(widget); widget->setFont(fixedPitchFont()); + const CChainParams ¶ms = Params(); #if QT_VERSION >= 0x040700 // We don't want translators to use own addresses in translations // and this is the only place, where this address is supplied. - widget->setPlaceholderText(QObject::tr("Enter a Bitcoin address (e.g. %1)") - .arg(QString::fromStdString(DummyAddress( - Params(), GlobalConfig())))); + widget->setPlaceholderText( + QObject::tr("Enter a Bitcoin address (e.g. %1)") + .arg(QString::fromStdString(DummyAddress(params, GetConfig())))); #endif - widget->setValidator(new BitcoinAddressEntryValidator(parent)); + widget->setValidator( + new BitcoinAddressEntryValidator(params.CashAddrPrefix(), parent)); widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); } void setupAmountWidget(QLineEdit *widget, QWidget *parent) { QDoubleValidator *amountValidator = new QDoubleValidator(parent); amountValidator->setDecimals(8); amountValidator->setBottom(0.0); widget->setValidator(amountValidator); widget->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) { // return if URI is not valid or is no bitcoincash: URI if (!uri.isValid() || uri.scheme() != URI_SCHEME) return false; SendCoinsRecipient rv; rv.address = uri.path(); // Trim any following forward slash which may have been added by the OS if (rv.address.endsWith("/")) { rv.address.truncate(rv.address.length() - 1); } rv.amount = 0; #if QT_VERSION < 0x050000 QList> items = uri.queryItems(); #else QUrlQuery uriQuery(uri); QList> items = uriQuery.queryItems(); #endif for (QList>::iterator i = items.begin(); i != items.end(); i++) { bool fShouldReturnFalse = false; if (i->first.startsWith("req-")) { i->first.remove(0, 4); fShouldReturnFalse = true; } if (i->first == "label") { rv.label = i->second; fShouldReturnFalse = false; } if (i->first == "message") { rv.message = i->second; fShouldReturnFalse = false; } else if (i->first == "amount") { if (!i->second.isEmpty()) { if (!BitcoinUnits::parse(BitcoinUnits::BCH, i->second, &rv.amount)) { return false; } } fShouldReturnFalse = false; } if (fShouldReturnFalse) { return false; } } if (out) { *out = rv; } return true; } bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) { // Convert bitcoincash:// to bitcoincash: // // Cannot handle this later, because bitcoincash:// // will cause Qt to see the part after // as host, // which will lower-case it (and thus invalidate the address). if (uri.startsWith(URI_SCHEME + "://", Qt::CaseInsensitive)) { uri.replace(0, URI_SCHEME.length() + 3, URI_SCHEME + ":"); } QUrl uriInstance(uri); return parseBitcoinURI(uriInstance, out); } QString formatBitcoinURI(const SendCoinsRecipient &info) { QString ret = (URI_SCHEME + ":%1").arg(info.address); int paramCount = 0; if (info.amount) { ret += QString("?amount=%1") .arg(BitcoinUnits::format(BitcoinUnits::BCH, info.amount, false, BitcoinUnits::separatorNever)); paramCount++; } if (!info.label.isEmpty()) { QString lbl(QUrl::toPercentEncoding(info.label)); ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl); paramCount++; } if (!info.message.isEmpty()) { QString msg(QUrl::toPercentEncoding(info.message)); ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg); paramCount++; } return ret; } bool isDust(const QString &address, const CAmount &amount) { CTxDestination dest = DecodeDestination(address.toStdString()); CScript script = GetScriptForDestination(dest); CTxOut txOut(Amount(amount), script); return txOut.IsDust(dustRelayFee); } QString HtmlEscape(const QString &str, bool fMultiLine) { #if QT_VERSION < 0x050000 QString escaped = Qt::escape(str); #else QString escaped = str.toHtmlEscaped(); #endif if (fMultiLine) { escaped = escaped.replace("\n", "
\n"); } return escaped; } QString HtmlEscape(const std::string &str, bool fMultiLine) { return HtmlEscape(QString::fromStdString(str), fMultiLine); } void copyEntryData(QAbstractItemView *view, int column, int role) { if (!view || !view->selectionModel()) return; QModelIndexList selection = view->selectionModel()->selectedRows(column); if (!selection.isEmpty()) { // Copy first item setClipboard(selection.at(0).data(role).toString()); } } QList getEntryData(QAbstractItemView *view, int column) { if (!view || !view->selectionModel()) return QList(); return view->selectionModel()->selectedRows(column); } QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) { QString selectedFilter; QString myDir; // Default to user documents location if (dir.isEmpty()) { #if QT_VERSION < 0x050000 myDir = QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation); #else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); #endif } else { myDir = dir; } /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName( parent, caption, myDir, filter, &selectedFilter)); /* Extract first suffix from filter pattern "Description (*.foo)" or * "Description (*.foo *.bar ...) */ QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); QString selectedSuffix; if (filter_re.exactMatch(selectedFilter)) { selectedSuffix = filter_re.cap(1); } /* Add suffix if needed */ QFileInfo info(result); if (!result.isEmpty()) { if (info.suffix().isEmpty() && !selectedSuffix.isEmpty()) { /* No suffix specified, add selected suffix */ if (!result.endsWith(".")) result.append("."); result.append(selectedSuffix); } } /* Return selected suffix if asked to */ if (selectedSuffixOut) { *selectedSuffixOut = selectedSuffix; } return result; } QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) { QString selectedFilter; QString myDir; if (dir.isEmpty()) // Default to user documents location { #if QT_VERSION < 0x050000 myDir = QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation); #else myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); #endif } else { myDir = dir; } /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName( parent, caption, myDir, filter, &selectedFilter)); if (selectedSuffixOut) { /* Extract first suffix from filter pattern "Description (*.foo)" or * "Description (*.foo *.bar ...) */ QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); QString selectedSuffix; if (filter_re.exactMatch(selectedFilter)) { selectedSuffix = filter_re.cap(1); } *selectedSuffixOut = selectedSuffix; } return result; } Qt::ConnectionType blockingGUIThreadConnection() { if (QThread::currentThread() != qApp->thread()) { return Qt::BlockingQueuedConnection; } else { return Qt::DirectConnection; } } bool checkPoint(const QPoint &p, const QWidget *w) { QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p)); if (!atW) return false; return atW->topLevelWidget() == w; } bool isObscured(QWidget *w) { return !(checkPoint(QPoint(0, 0), w) && checkPoint(QPoint(w->width() - 1, 0), w) && checkPoint(QPoint(0, w->height() - 1), w) && checkPoint(QPoint(w->width() - 1, w->height() - 1), w) && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); } void openDebugLogfile() { boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; /* Open debug.log with the associated application */ if (boost::filesystem::exists(pathDebug)) QDesktopServices::openUrl( QUrl::fromLocalFile(boostPathToQString(pathDebug))); } void SubstituteFonts(const QString &language) { #if defined(Q_OS_MAC) // Background: // OSX's default font changed in 10.9 and Qt is unable to find it with its // usual fallback methods when building against the 10.7 sdk or lower. // The 10.8 SDK added a function to let it find the correct fallback font. // If this fallback is not properly loaded, some characters may fail to // render correctly. // // The same thing happened with 10.10. .Helvetica Neue DeskInterface is now // default. // // Solution: If building with the 10.7 SDK or lower and the user's platform // is 10.9 or higher at runtime, substitute the correct font. This needs to // happen before the QApplication is created. #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8) { if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) /* On a 10.9 - 10.9.x system */ QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); else { /* 10.10 or later system */ if (language == "zh_CN" || language == "zh_TW" || language == "zh_HK") // traditional or simplified Chinese QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Heiti SC"); else if (language == "ja") // Japanesee QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Songti SC"); else QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Lucida Grande"); } } #endif #endif } ToolTipToRichTextFilter::ToolTipToRichTextFilter(int _size_threshold, QObject *parent) : QObject(parent), size_threshold(_size_threshold) {} bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt) { if (evt->type() == QEvent::ToolTipChange) { QWidget *widget = static_cast(obj); QString tooltip = widget->toolTip(); if (tooltip.size() > size_threshold && !tooltip.startsWith(" to make sure Qt detects this as rich text // Escape the current message as HTML and replace \n by
tooltip = "" + HtmlEscape(tooltip, true) + ""; widget->setToolTip(tooltip); return true; } } return QObject::eventFilter(obj, evt); } void TableViewLastColumnResizingFixer::connectViewHeadersSignals() { connect(tableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(on_sectionResized(int, int, int))); connect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); } // We need to disconnect these while handling the resize events, otherwise we // can enter infinite loops. void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals() { disconnect(tableView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(on_sectionResized(int, int, int))); disconnect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); } // Setup the resize mode, handles compatibility for Qt5 and below as the method // signatures changed. // Refactored here for readability. void TableViewLastColumnResizingFixer::setViewHeaderResizeMode( int logicalIndex, QHeaderView::ResizeMode resizeMode) { #if QT_VERSION < 0x050000 tableView->horizontalHeader()->setResizeMode(logicalIndex, resizeMode); #else tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode); #endif } void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width) { tableView->setColumnWidth(nColumnIndex, width); tableView->horizontalHeader()->resizeSection(nColumnIndex, width); } int TableViewLastColumnResizingFixer::getColumnsWidth() { int nColumnsWidthSum = 0; for (int i = 0; i < columnCount; i++) { nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i); } return nColumnsWidthSum; } int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column) { int nResult = lastColumnMinimumWidth; int nTableWidth = tableView->horizontalHeader()->width(); if (nTableWidth > 0) { int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column); nResult = std::max(nResult, nTableWidth - nOtherColsWidth); } return nResult; } // Make sure we don't make the columns wider than the table's viewport width. void TableViewLastColumnResizingFixer::adjustTableColumnsWidth() { disconnectViewHeadersSignals(); resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex)); connectViewHeadersSignals(); int nTableWidth = tableView->horizontalHeader()->width(); int nColsWidth = getColumnsWidth(); if (nColsWidth > nTableWidth) { resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); } } // Make column use all the space available, useful during window resizing. void TableViewLastColumnResizingFixer::stretchColumnWidth(int column) { disconnectViewHeadersSignals(); resizeColumn(column, getAvailableWidthForColumn(column)); connectViewHeadersSignals(); } // When a section is resized this is a slot-proxy for ajustAmountColumnWidth(). void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize) { adjustTableColumnsWidth(); int remainingWidth = getAvailableWidthForColumn(logicalIndex); if (newSize > remainingWidth) { resizeColumn(logicalIndex, remainingWidth); } } // When the table's geometry is ready, we manually perform the stretch of the // "Message" column, // as the "Stretch" resize mode does not allow for interactive resizing. void TableViewLastColumnResizingFixer::on_geometriesChanged() { if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0) { disconnectViewHeadersSignals(); resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); connectViewHeadersSignals(); } } /** * Initializes all internal variables and prepares the * the resize modes of the last 2 columns of the table and */ TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer( QTableView *table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent) : QObject(parent), tableView(table), lastColumnMinimumWidth(lastColMinimumWidth), allColumnsMinimumWidth(allColsMinimumWidth) { columnCount = tableView->horizontalHeader()->count(); lastColumnIndex = columnCount - 1; secondToLastColumnIndex = columnCount - 2; tableView->horizontalHeader()->setMinimumSectionSize( allColumnsMinimumWidth); setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive); setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive); } #ifdef WIN32 static boost::filesystem::path StartupShortcutPath() { std::string chain = ChainNameFromCommandLine(); if (chain == CBaseChainParams::MAIN) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; // Remove this special case when CBaseChainParams::TESTNET = "testnet4" if (chain == CBaseChainParams::TESTNET) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin (testnet).lnk"; return GetSpecialFolderPath(CSIDL_STARTUP) / strprintf("Bitcoin (%s).lnk", chain); } bool GetStartOnSystemStartup() { // check for Bitcoin*.lnk return boost::filesystem::exists(StartupShortcutPath()); } bool SetStartOnSystemStartup(bool fAutoStart) { // If the shortcut exists already, remove it for updating boost::filesystem::remove(StartupShortcutPath()); if (fAutoStart) { CoInitialize(nullptr); // Get a pointer to the IShellLink interface. IShellLink *psl = nullptr; HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&psl)); if (SUCCEEDED(hres)) { // Get the current executable path TCHAR pszExePath[MAX_PATH]; GetModuleFileName(nullptr, pszExePath, sizeof(pszExePath)); // Start client minimized QString strArgs = "-min"; // Set -testnet /-regtest options strArgs += QString::fromStdString(strprintf( " -testnet=%d -regtest=%d", GetBoolArg("-testnet", false), GetBoolArg("-regtest", false))); #ifdef UNICODE boost::scoped_array args(new TCHAR[strArgs.length() + 1]); // Convert the QString to TCHAR* strArgs.toWCharArray(args.get()); // Add missing '\0'-termination to string args[strArgs.length()] = '\0'; #endif // Set the path to the shortcut target psl->SetPath(pszExePath); PathRemoveFileSpec(pszExePath); psl->SetWorkingDirectory(pszExePath); psl->SetShowCmd(SW_SHOWMINNOACTIVE); #ifndef UNICODE psl->SetArguments(strArgs.toStdString().c_str()); #else psl->SetArguments(args.get()); #endif // Query IShellLink for the IPersistFile interface for // saving the shortcut in persistent storage. IPersistFile *ppf = nullptr; hres = psl->QueryInterface(IID_IPersistFile, reinterpret_cast(&ppf)); if (SUCCEEDED(hres)) { WCHAR pwsz[MAX_PATH]; // Ensure that the string is ANSI. MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH); // Save the link by calling IPersistFile::Save. hres = ppf->Save(pwsz, TRUE); ppf->Release(); psl->Release(); CoUninitialize(); return true; } psl->Release(); } CoUninitialize(); return false; } return true; } #elif defined(Q_OS_LINUX) // Follow the Desktop Application Autostart Spec: // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html static boost::filesystem::path GetAutostartDir() { namespace fs = boost::filesystem; char *pszConfigHome = getenv("XDG_CONFIG_HOME"); if (pszConfigHome) return fs::path(pszConfigHome) / "autostart"; char *pszHome = getenv("HOME"); if (pszHome) return fs::path(pszHome) / ".config" / "autostart"; return fs::path(); } static boost::filesystem::path GetAutostartFilePath() { std::string chain = ChainNameFromCommandLine(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain); } bool GetStartOnSystemStartup() { boost::filesystem::ifstream optionFile(GetAutostartFilePath()); if (!optionFile.good()) return false; // Scan through file for "Hidden=true": std::string line; while (!optionFile.eof()) { getline(optionFile, line); if (line.find("Hidden") != std::string::npos && line.find("true") != std::string::npos) return false; } optionFile.close(); return true; } bool SetStartOnSystemStartup(bool fAutoStart) { if (!fAutoStart) boost::filesystem::remove(GetAutostartFilePath()); else { char pszExePath[MAX_PATH + 1]; memset(pszExePath, 0, sizeof(pszExePath)); if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1) == -1) return false; boost::filesystem::create_directories(GetAutostartDir()); boost::filesystem::ofstream optionFile( GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc); if (!optionFile.good()) return false; std::string chain = ChainNameFromCommandLine(); // Write a bitcoin.desktop file to the autostart directory: optionFile << "[Desktop Entry]\n"; optionFile << "Type=Application\n"; if (chain == CBaseChainParams::MAIN) optionFile << "Name=Bitcoin\n"; else optionFile << strprintf("Name=Bitcoin (%s)\n", chain); optionFile << "Exec=" << pszExePath << strprintf(" -min -testnet=%d -regtest=%d\n", GetBoolArg("-testnet", false), GetBoolArg("-regtest", false)); optionFile << "Terminal=false\n"; optionFile << "Hidden=false\n"; optionFile.close(); } return true; } #elif defined(Q_OS_MAC) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // based on: // https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m // NB: caller must release returned ref if it's not NULL LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) { LSSharedFileListItemRef foundItem = nullptr; // loop through the list of startup items and try to find the bitcoin app CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, nullptr); for (int i = 0; !foundItem && i < CFArrayGetCount(listSnapshot); ++i) { LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(listSnapshot, i); UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; CFURLRef currentItemURL = nullptr; #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ MAC_OS_X_VERSION_MAX_ALLOWED >= 10100 if (&LSSharedFileListItemCopyResolvedURL) currentItemURL = LSSharedFileListItemCopyResolvedURL( item, resolutionFlags, nullptr); #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ MAC_OS_X_VERSION_MIN_REQUIRED < 10100 else LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, nullptr); #endif #else LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, nullptr); #endif if (currentItemURL && CFEqual(currentItemURL, findUrl)) { // found CFRetain(foundItem = item); } if (currentItemURL) { CFRelease(currentItemURL); } } CFRelease(listSnapshot); return foundItem; } bool GetStartOnSystemStartup() { CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); LSSharedFileListRef loginItems = LSSharedFileListCreate( nullptr, kLSSharedFileListSessionLoginItems, nullptr); LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); // findStartupItemInList retains the item it returned, need to release if (foundItem) { CFRelease(foundItem); } CFRelease(loginItems); CFRelease(bitcoinAppUrl); return foundItem; } bool SetStartOnSystemStartup(bool fAutoStart) { CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); LSSharedFileListRef loginItems = LSSharedFileListCreate( nullptr, kLSSharedFileListSessionLoginItems, nullptr); LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); if (fAutoStart && !foundItem) { // add bitcoin app to startup item list LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, nullptr, nullptr, bitcoinAppUrl, nullptr, nullptr); } else if (!fAutoStart && foundItem) { // remove item LSSharedFileListItemRemove(loginItems, foundItem); } // findStartupItemInList retains the item it returned, need to release if (foundItem) { CFRelease(foundItem); } CFRelease(loginItems); CFRelease(bitcoinAppUrl); return true; } #pragma GCC diagnostic pop #else bool GetStartOnSystemStartup() { return false; } bool SetStartOnSystemStartup(bool fAutoStart) { return false; } #endif void saveWindowGeometry(const QString &strSetting, QWidget *parent) { QSettings settings; settings.setValue(strSetting + "Pos", parent->pos()); settings.setValue(strSetting + "Size", parent->size()); } void restoreWindowGeometry(const QString &strSetting, const QSize &defaultSize, QWidget *parent) { QSettings settings; QPoint pos = settings.value(strSetting + "Pos").toPoint(); QSize size = settings.value(strSetting + "Size", defaultSize).toSize(); if (!pos.x() && !pos.y()) { QRect screen = QApplication::desktop()->screenGeometry(); pos.setX((screen.width() - size.width()) / 2); pos.setY((screen.height() - size.height()) / 2); } parent->resize(size); parent->move(pos); } void setClipboard(const QString &str) { QApplication::clipboard()->setText(str, QClipboard::Clipboard); QApplication::clipboard()->setText(str, QClipboard::Selection); } #if BOOST_FILESYSTEM_VERSION >= 3 boost::filesystem::path qstringToBoostPath(const QString &path) { return boost::filesystem::path(path.toStdString(), utf8); } QString boostPathToQString(const boost::filesystem::path &path) { return QString::fromStdString(path.string(utf8)); } #else #warning Conversion between boost path and QString can use invalid character encoding with boost_filesystem v2 and older boost::filesystem::path qstringToBoostPath(const QString &path) { return boost::filesystem::path(path.toStdString()); } QString boostPathToQString(const boost::filesystem::path &path) { return QString::fromStdString(path.string()); } #endif QString formatDurationStr(int secs) { QStringList strList; int days = secs / 86400; int hours = (secs % 86400) / 3600; int mins = (secs % 3600) / 60; int seconds = secs % 60; if (days) strList.append(QString(QObject::tr("%1 d")).arg(days)); if (hours) strList.append(QString(QObject::tr("%1 h")).arg(hours)); if (mins) strList.append(QString(QObject::tr("%1 m")).arg(mins)); if (seconds || (!days && !hours && !mins)) strList.append(QString(QObject::tr("%1 s")).arg(seconds)); return strList.join(" "); } QString formatServicesStr(quint64 mask) { QStringList strList; // Just scan the last 8 bits for now. for (int i = 0; i < 8; i++) { uint64_t check = 1 << i; if (mask & check) { switch (check) { case NODE_NETWORK: strList.append("NETWORK"); break; case NODE_GETUTXO: strList.append("GETUTXO"); break; case NODE_BLOOM: strList.append("BLOOM"); break; case NODE_XTHIN: strList.append("XTHIN"); break; case NODE_BITCOIN_CASH: strList.append("CASH"); break; default: strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check)); } } } if (strList.size()) return strList.join(" & "); else return QObject::tr("None"); } QString formatPingTime(double dPingTime) { return (dPingTime == std::numeric_limits::max() / 1e6 || dPingTime == 0) ? QObject::tr("N/A") : QString(QObject::tr("%1 ms")) .arg(QString::number((int)(dPingTime * 1000), 10)); } QString formatTimeOffset(int64_t nTimeOffset) { return QString(QObject::tr("%1 s")) .arg(QString::number((int)nTimeOffset, 10)); } QString formatNiceTimeOffset(qint64 secs) { // Represent time from last generated block in human readable text QString timeBehindText; const int HOUR_IN_SECONDS = 60 * 60; const int DAY_IN_SECONDS = 24 * 60 * 60; const int WEEK_IN_SECONDS = 7 * 24 * 60 * 60; // Average length of year in Gregorian calendar const int YEAR_IN_SECONDS = 31556952; if (secs < 60) { timeBehindText = QObject::tr("%n second(s)", "", secs); } else if (secs < 2 * HOUR_IN_SECONDS) { timeBehindText = QObject::tr("%n minute(s)", "", secs / 60); } else if (secs < 2 * DAY_IN_SECONDS) { timeBehindText = QObject::tr("%n hour(s)", "", secs / HOUR_IN_SECONDS); } else if (secs < 2 * WEEK_IN_SECONDS) { timeBehindText = QObject::tr("%n day(s)", "", secs / DAY_IN_SECONDS); } else if (secs < YEAR_IN_SECONDS) { timeBehindText = QObject::tr("%n week(s)", "", secs / WEEK_IN_SECONDS); } else { qint64 years = secs / YEAR_IN_SECONDS; qint64 remainder = secs % YEAR_IN_SECONDS; timeBehindText = QObject::tr("%1 and %2") .arg(QObject::tr("%n year(s)", "", years)) .arg(QObject::tr("%n week(s)", "", remainder / WEEK_IN_SECONDS)); } return timeBehindText; } void ClickableLabel::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); } void ClickableProgressBar::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); } } // namespace GUIUtil diff --git a/src/qt/test/bitcoinaddressvalidatortests.cpp b/src/qt/test/bitcoinaddressvalidatortests.cpp new file mode 100644 index 000000000..78a9d077a --- /dev/null +++ b/src/qt/test/bitcoinaddressvalidatortests.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2017 The Bitcoin Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "qt/test/bitcoinaddressvalidatortests.h" +#include "chainparams.h" +#include "qt/bitcoinaddressvalidator.h" +#include + +void BitcoinAddressValidatorTests::inputTests() { + const std::string prefix = Params(CBaseChainParams::MAIN).CashAddrPrefix(); + BitcoinAddressEntryValidator v(prefix, nullptr); + + int unused = 0; + QString in; + + // invalid base58 because of I, invalid cashaddr + in = "BIIC"; + QVERIFY(QValidator::Invalid == v.validate(in, unused)); + + // invalid base58, invalid cashaddr + in = "BITCOINCASHH"; + QVERIFY(QValidator::Invalid == v.validate(in, unused)); + + // invalid base58 because of I, but could be a cashaddr prefix + in = "BITC"; + QVERIFY(QValidator::Acceptable == v.validate(in, unused)); + + // invalid base58, valid cashaddr + in = "BITCOINCASH:QP"; + QVERIFY(QValidator::Acceptable == v.validate(in, unused)); + + // valid base58, invalid cash + in = "BBBBBBBBBBBBBB"; + QVERIFY(QValidator::Acceptable == v.validate(in, unused)); +} diff --git a/src/qt/test/bitcoinaddressvalidatortests.h b/src/qt/test/bitcoinaddressvalidatortests.h new file mode 100644 index 000000000..d0e76eadf --- /dev/null +++ b/src/qt/test/bitcoinaddressvalidatortests.h @@ -0,0 +1,18 @@ +// Copyright (c) 2017 The Bitcoin Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_TEST_BITCOINADDRESSVALIDATORTESTS_H +#define BITCOIN_QT_TEST_BITCOINADDRESSVALIDATORTESTS_H + +#include +#include + +class BitcoinAddressValidatorTests : public QObject { + Q_OBJECT + +private Q_SLOTS: + void inputTests(); +}; + +#endif diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index b76318776..cdbab07cb 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -1,69 +1,72 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif +#include "bitcoinaddressvalidatortests.h" #include "chainparams.h" #include "compattests.h" #include "guiutiltests.h" #include "key.h" #include "rpcnestedtests.h" #include "uritests.h" #include "util.h" #ifdef ENABLE_WALLET #include "paymentservertests.h" #endif #include #include #include #include #if defined(QT_STATICPLUGIN) && QT_VERSION < 0x050000 #include Q_IMPORT_PLUGIN(qcncodecs) Q_IMPORT_PLUGIN(qjpcodecs) Q_IMPORT_PLUGIN(qtwcodecs) Q_IMPORT_PLUGIN(qkrcodecs) #endif extern void noui_connect(); // This is all you need to run all the tests int main(int argc, char *argv[]) { ECC_Start(); SetupEnvironment(); SetupNetworking(); SelectParams(CBaseChainParams::MAIN); noui_connect(); bool fInvalid = false; // Don't remove this, it's needed to access // QCoreApplication:: in the tests QCoreApplication app(argc, argv); app.setApplicationName("BitcoinABC-Qt-test"); SSL_library_init(); URITests test1; if (QTest::qExec(&test1) != 0) fInvalid = true; #ifdef ENABLE_WALLET PaymentServerTests test2; if (QTest::qExec(&test2) != 0) fInvalid = true; #endif RPCNestedTests test3; if (QTest::qExec(&test3) != 0) fInvalid = true; CompatTests test4; if (QTest::qExec(&test4) != 0) fInvalid = true; GUIUtilTests test5; if (QTest::qExec(&test5) != 0) fInvalid = true; + BitcoinAddressValidatorTests test6; + if (QTest::qExec(&test6) != 0) fInvalid = true; ECC_Stop(); return fInvalid; }