diff options
-rw-r--r-- | configure.ac | 9 | ||||
-rw-r--r-- | contrib/debian/examples/bitcoin.conf | 2 | ||||
-rw-r--r-- | contrib/debian/manpages/bitcoin-qt.1 | 5 | ||||
-rw-r--r-- | contrib/debian/manpages/bitcoind.1 | 2 | ||||
-rw-r--r-- | contrib/gitian-descriptors/gitian-linux.yml | 2 | ||||
-rwxr-xr-x | contrib/pyminer/pyminer.py | 457 | ||||
-rw-r--r-- | doc/tor.md | 5 | ||||
-rw-r--r-- | src/Makefile.qt.include | 1 | ||||
-rw-r--r-- | src/allocators.h | 2 | ||||
-rw-r--r-- | src/main.cpp | 3 | ||||
-rw-r--r-- | src/qt/bitcoinamountfield.cpp | 272 | ||||
-rw-r--r-- | src/qt/bitcoinamountfield.h | 14 | ||||
-rw-r--r-- | src/qt/bitcoinunits.cpp | 29 | ||||
-rw-r--r-- | src/qt/bitcoinunits.h | 7 | ||||
-rw-r--r-- | src/qt/sendcoinsentry.cpp | 18 | ||||
-rw-r--r-- | src/rpcdump.cpp | 26 | ||||
-rw-r--r-- | src/timedata.cpp | 18 | ||||
-rw-r--r-- | src/txmempool.cpp | 7 | ||||
-rw-r--r-- | src/version.h | 4 |
19 files changed, 487 insertions, 396 deletions
diff --git a/configure.ac b/configure.ac index cedc34e520..719b06da67 100644 --- a/configure.ac +++ b/configure.ac @@ -392,6 +392,8 @@ AC_TRY_COMPILE([#include <sys/socket.h>], [ AC_MSG_RESULT(no)] ) +AC_SEARCH_LIBS([clock_gettime],[rt]) + LEVELDB_CPPFLAGS= LIBLEVELDB= LIBMEMENV= @@ -460,11 +462,8 @@ dnl after 1.56. dnl If neither is available, abort. dnl If sleep_for is used, boost_chrono becomes a requirement. if test x$ax_cv_boost_chrono = xyes; then -dnl Allow passing extra needed dependency libraries for boost-chrono from static gitian build -BOOST_CHRONO_LIB="$BOOST_CHRONO_LIB $BOOST_CHRONO_EXTRALIBS" - TEMP_LIBS="$LIBS" -LIBS="$LIBS $BOOST_LIBS $BOOST_CHRONO_LIB" +LIBS="$BOOST_LIBS $BOOST_CHRONO_LIB $LIBS" TEMP_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" AC_TRY_LINK([ @@ -486,7 +485,7 @@ fi if test x$boost_sleep != xyes; then TEMP_LIBS="$LIBS" -LIBS="$LIBS $BOOST_LIBS" +LIBS="$BOOST_LIBS $LIBS" TEMP_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" AC_TRY_LINK([ diff --git a/contrib/debian/examples/bitcoin.conf b/contrib/debian/examples/bitcoin.conf index 0aa8674af9..31cca981e0 100644 --- a/contrib/debian/examples/bitcoin.conf +++ b/contrib/debian/examples/bitcoin.conf @@ -10,7 +10,7 @@ # Run a regression test network #regtest=0 -# Connect via a socks4 proxy +# Connect via a SOCKS5 proxy #proxy=127.0.0.1:9050 ############################################################## diff --git a/contrib/debian/manpages/bitcoin-qt.1 b/contrib/debian/manpages/bitcoin-qt.1 index cd478b1875..a023582bc0 100644 --- a/contrib/debian/manpages/bitcoin-qt.1 +++ b/contrib/debian/manpages/bitcoin-qt.1 @@ -32,10 +32,7 @@ Set database cache size in megabytes (default: 25) Specify connection timeout in milliseconds (default: 5000) .TP \fB\-proxy=\fR<ip:port> -Connect through socks proxy -.TP -\fB\-socks=\fR<n> -Select the version of socks proxy to use (4\-5, default: 5) +Connect through SOCKS5 proxy .TP \fB\-tor=\fR<ip:port> Use proxy to reach tor hidden services (default: same as \fB\-proxy\fR) diff --git a/contrib/debian/manpages/bitcoind.1 b/contrib/debian/manpages/bitcoind.1 index 0c191b6043..a1b17d6077 100644 --- a/contrib/debian/manpages/bitcoind.1 +++ b/contrib/debian/manpages/bitcoind.1 @@ -28,7 +28,7 @@ Start minimized Specify data directory .TP \fB\-proxy=\fR<ip:port> -Connect through socks4 proxy +Connect through SOCKS5 proxy .TP \fB\-addnode=\fR<ip> Add a node to connect to diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index 65a6c3c1e9..30b0227bdd 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -59,7 +59,7 @@ script: | local: *; };' > $LINKER_SCRIPT function do_configure { - ./configure "$@" --enable-upnp-default --prefix=$STAGING --with-protoc-bindir=$STAGING/host/bin --with-qt-bindir=$STAGING/bin --with-boost=$STAGING --disable-maintainer-mode --disable-dependency-tracking PKG_CONFIG_PATH="$STAGING/lib/pkgconfig" CPPFLAGS="-I$STAGING/include ${OPTFLAGS}" LDFLAGS="-L$STAGING/lib -Wl,--version-script=$LINKER_SCRIPT ${OPTFLAGS}" CXXFLAGS="-frandom-seed=bitcoin ${OPTFLAGS}" BOOST_CHRONO_EXTRALIBS="-lrt" --enable-glibc-back-compat + ./configure "$@" --enable-upnp-default --prefix=$STAGING --with-protoc-bindir=$STAGING/host/bin --with-qt-bindir=$STAGING/bin --with-boost=$STAGING --disable-maintainer-mode --disable-dependency-tracking PKG_CONFIG_PATH="$STAGING/lib/pkgconfig" CPPFLAGS="-I$STAGING/include ${OPTFLAGS}" LDFLAGS="-L$STAGING/lib -Wl,--version-script=$LINKER_SCRIPT ${OPTFLAGS}" CXXFLAGS="-frandom-seed=bitcoin ${OPTFLAGS}" --enable-glibc-back-compat } # cd bitcoin diff --git a/contrib/pyminer/pyminer.py b/contrib/pyminer/pyminer.py index 0a2932d66e..706a10b39d 100755 --- a/contrib/pyminer/pyminer.py +++ b/contrib/pyminer/pyminer.py @@ -5,248 +5,265 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. # +import sys +from multiprocessing import Process import time -import json -import pprint -import hashlib import struct -import re +import hashlib import base64 +import re import httplib -import sys -from multiprocessing import Process +import json ERR_SLEEP = 15 MAX_NONCE = 1000000L settings = {} -pp = pprint.PrettyPrinter(indent=4) + class BitcoinRPC: - OBJID = 1 - - def __init__(self, host, port, username, password): - authpair = "%s:%s" % (username, password) - self.authhdr = "Basic %s" % (base64.b64encode(authpair)) - self.conn = httplib.HTTPConnection(host, port, False, 30) - def rpc(self, method, params=None): - self.OBJID += 1 - obj = { 'version' : '1.1', - 'method' : method, - 'id' : self.OBJID } - if params is None: - obj['params'] = [] - else: - obj['params'] = params - self.conn.request('POST', '/', json.dumps(obj), - { 'Authorization' : self.authhdr, - 'Content-type' : 'application/json' }) - - resp = self.conn.getresponse() - if resp is None: - print "JSON-RPC: no response" - return None - - body = resp.read() - resp_obj = json.loads(body) - if resp_obj is None: - print "JSON-RPC: cannot JSON-decode body" - return None - if 'error' in resp_obj and resp_obj['error'] != None: - return resp_obj['error'] - if 'result' not in resp_obj: - print "JSON-RPC: no result in object" - return None - - return resp_obj['result'] - def getblockcount(self): - return self.rpc('getblockcount') - def getwork(self, data=None): - return self.rpc('getwork', data) + object_id = 1 + + def __init__(self, host, port, username, password): + authpair = "{0}:{1}".format(username, password) + self.authhdr = "Basic {0}".format(base64.b64encode(authpair)) + self.conn = httplib.HTTPConnection(host, port, strict=False, timeout=30) + + def rpc(self, method, params=None): + self.object_id += 1 + obj = {'version' : '1.1', + 'method' : method, + 'id' : self.object_id, + 'params' : params or []} + + self.conn.request('POST', '/', json.dumps(obj), + { 'Authorization' : self.authhdr, + 'Content-type' : 'application/json' }) + + resp = self.conn.getresponse() + + if resp is None: + print("JSON-RPC: no response") + return None + + body = resp.read() + resp_obj = json.loads(body) + + if resp_obj is None: + print("JSON-RPC: cannot JSON-decode body") + return None + + if 'error' in resp_obj and resp_obj['error'] != None: + return resp_obj['error'] + + if 'result' not in resp_obj: + print("JSON-RPC: no result in object") + return None + + return resp_obj['result'] + + def getblockcount(self): + return self.rpc('getblockcount') + + def getwork(self, data=None): + return self.rpc('getwork', data) def uint32(x): - return x & 0xffffffffL + return x & 0xffffffffL def bytereverse(x): - return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | - (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) + return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | + (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) def bufreverse(in_buf): - out_words = [] - for i in range(0, len(in_buf), 4): - word = struct.unpack('@I', in_buf[i:i+4])[0] - out_words.append(struct.pack('@I', bytereverse(word))) - return ''.join(out_words) + out_words = [] + + for i in range(0, len(in_buf), 4): + word = struct.unpack('@I', in_buf[i:i+4])[0] + out_words.append(struct.pack('@I', bytereverse(word))) + + return ''.join(out_words) def wordreverse(in_buf): - out_words = [] - for i in range(0, len(in_buf), 4): - out_words.append(in_buf[i:i+4]) - out_words.reverse() - return ''.join(out_words) + out_words = [] + + for i in range(0, len(in_buf), 4): + out_words.append(in_buf[i:i+4]) + + out_words.reverse() + + return ''.join(out_words) + class Miner: - def __init__(self, id): - self.id = id - self.max_nonce = MAX_NONCE - - def work(self, datastr, targetstr): - # decode work data hex string to binary - static_data = datastr.decode('hex') - static_data = bufreverse(static_data) - - # the first 76b of 80b do not change - blk_hdr = static_data[:76] - - # decode 256-bit target value - targetbin = targetstr.decode('hex') - targetbin = targetbin[::-1] # byte-swap and dword-swap - targetbin_str = targetbin.encode('hex') - target = long(targetbin_str, 16) - - # pre-hash first 76b of block header - static_hash = hashlib.sha256() - static_hash.update(blk_hdr) - - for nonce in xrange(self.max_nonce): - - # encode 32-bit nonce value - nonce_bin = struct.pack("<I", nonce) - - # hash final 4b, the nonce value - hash1_o = static_hash.copy() - hash1_o.update(nonce_bin) - hash1 = hash1_o.digest() - - # sha256 hash of sha256 hash - hash_o = hashlib.sha256() - hash_o.update(hash1) - hash = hash_o.digest() - - # quick test for winning solution: high 32 bits zero? - if hash[-4:] != '\0\0\0\0': - continue - - # convert binary hash to 256-bit Python long - hash = bufreverse(hash) - hash = wordreverse(hash) - - hash_str = hash.encode('hex') - l = long(hash_str, 16) - - # proof-of-work test: hash < target - if l < target: - print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,) - return (nonce + 1, nonce_bin) - else: - print time.asctime(), "PROOF-OF-WORK false positive %064x" % (l,) -# return (nonce + 1, nonce_bin) - - return (nonce + 1, None) - - def submit_work(self, rpc, original_data, nonce_bin): - nonce_bin = bufreverse(nonce_bin) - nonce = nonce_bin.encode('hex') - solution = original_data[:152] + nonce + original_data[160:256] - param_arr = [ solution ] - result = rpc.getwork(param_arr) - print time.asctime(), "--> Upstream RPC result:", result - - def iterate(self, rpc): - work = rpc.getwork() - if work is None: - time.sleep(ERR_SLEEP) - return - if 'data' not in work or 'target' not in work: - time.sleep(ERR_SLEEP) - return - - time_start = time.time() - - (hashes_done, nonce_bin) = self.work(work['data'], - work['target']) - - time_end = time.time() - time_diff = time_end - time_start - - self.max_nonce = long( - (hashes_done * settings['scantime']) / time_diff) - if self.max_nonce > 0xfffffffaL: - self.max_nonce = 0xfffffffaL - - if settings['hashmeter']: - print "HashMeter(%d): %d hashes, %.2f Khash/sec" % ( - self.id, hashes_done, - (hashes_done / 1000.0) / time_diff) - - if nonce_bin is not None: - self.submit_work(rpc, work['data'], nonce_bin) - - def loop(self): - rpc = BitcoinRPC(settings['host'], settings['port'], - settings['rpcuser'], settings['rpcpass']) - if rpc is None: - return - - while True: - self.iterate(rpc) + def __init__(self, id): + self.id = id + self.max_nonce = MAX_NONCE + + def work(self, datastr, targetstr): + # decode work data hex string to binary + static_data = datastr.decode('hex') + static_data = bufreverse(static_data) + + # the first 76b of 80b do not change + blk_hdr = static_data[:76] + + # decode 256-bit target value + targetbin = targetstr.decode('hex') + targetbin = targetbin[::-1] # byte-swap and dword-swap + targetbin_str = targetbin.encode('hex') + target = long(targetbin_str, 16) + + # pre-hash first 76b of block header + static_hash = hashlib.sha256() + static_hash.update(blk_hdr) + + for nonce in xrange(self.max_nonce): + + # encode 32-bit nonce value + nonce_bin = struct.pack("<I", nonce) + + # hash final 4b, the nonce value + hash1_o = static_hash.copy() + hash1_o.update(nonce_bin) + hash1 = hash1_o.digest() + + # sha256 hash of sha256 hash + hash_o = hashlib.sha256() + hash_o.update(hash1) + hash = hash_o.digest() + + # quick test for winning solution: high 32 bits zero? + if hash[-4:] != '\0\0\0\0': + continue + + # convert binary hash to 256-bit Python long + hash = bufreverse(hash) + hash = wordreverse(hash) + + hash_str = hash.encode('hex') + long_hash = long(hash_str, 16) + + # proof-of-work test: hash < target + if long_hash < target: + print(time.asctime(), "PROOF-OF-WORK found: " + "{0:064x}".format(long_hash)) + return (nonce + 1, nonce_bin) + else: + print(time.asctime(), "PROOF-OF-WORK false" + "positive {0:064x}".format(long_hash)) + + return (nonce + 1, None) + + def submit_work(self, rpc, original_data, nonce_bin): + nonce_bin = bufreverse(nonce_bin) + nonce = nonce_bin.encode('hex') + solution = original_data[:152] + nonce + original_data[160:256] + param_arr = [ solution ] + result = rpc.getwork(param_arr) + + print(time.asctime(), "--> Upstream RPC result:", result) + + def iterate(self, rpc): + work = rpc.getwork() + + if work is None: + time.sleep(ERR_SLEEP) + return + + if 'data' not in work or 'target' not in work: + time.sleep(ERR_SLEEP) + return + + time_start = time.time() + + (hashes_done, nonce_bin) = self.work(work['data'], + work['target']) + + time_end = time.time() + time_diff = time_end - time_start + + self.max_nonce = long( + (hashes_done * settings['scantime']) / time_diff) + + if self.max_nonce > 0xfffffffaL: + self.max_nonce = 0xfffffffaL + + if settings['hashmeter']: + print("HashMeter({:d}): {:d} hashes, {:.2f} Khash/sec".format( + self.id, hashes_done, (hashes_done / 1000.0) / time_diff)) + + if nonce_bin is not None: + self.submit_work(rpc, work['data'], nonce_bin) + + def loop(self): + rpc = BitcoinRPC(settings['host'], settings['port'], + settings['rpcuser'], settings['rpcpass']) + + if rpc is not None: + + while True: + self.iterate(rpc) + + self.conn.close() + def miner_thread(id): - miner = Miner(id) - miner.loop() + miner = Miner(id) + miner.loop() if __name__ == '__main__': - if len(sys.argv) != 2: - print "Usage: pyminer.py CONFIG-FILE" - sys.exit(1) - - f = open(sys.argv[1]) - for line in f: - # skip comment lines - m = re.search('^\s*#', line) - if m: - continue - - # parse key=value lines - m = re.search('^(\w+)\s*=\s*(\S.*)$', line) - if m is None: - continue - settings[m.group(1)] = m.group(2) - f.close() - - if 'host' not in settings: - settings['host'] = '127.0.0.1' - if 'port' not in settings: - settings['port'] = 8332 - if 'threads' not in settings: - settings['threads'] = 1 - if 'hashmeter' not in settings: - settings['hashmeter'] = 0 - if 'scantime' not in settings: - settings['scantime'] = 30L - if 'rpcuser' not in settings or 'rpcpass' not in settings: - print "Missing username and/or password in cfg file" - sys.exit(1) - - settings['port'] = int(settings['port']) - settings['threads'] = int(settings['threads']) - settings['hashmeter'] = int(settings['hashmeter']) - settings['scantime'] = long(settings['scantime']) - - thr_list = [] - for thr_id in range(settings['threads']): - p = Process(target=miner_thread, args=(thr_id,)) - p.start() - thr_list.append(p) - time.sleep(1) # stagger threads - - print settings['threads'], "mining threads started" - - print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port']) - try: - for thr_proc in thr_list: - thr_proc.join() - except KeyboardInterrupt: - pass - print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port']) - + if len(sys.argv) != 2: + print("Usage: pyminer.py CONFIG-FILE") + sys.exit(1) + + with open(sys.argv[1]) as f: + + for line in f: + # skip comment lines + m = re.search('^\s*#', line) + if m: + continue + + # parse key=value lines + m = re.search('^(\w+)\s*=\s*(\S.*)$', line) + if m is None: + continue + + settings[m.group(1)] = m.group(2) + + settings.setdefault('host', '127.0.0.1') + settings.setdefault('port', 8332) + settings.setdefault('threads', 1) + settings.setdefault('hashmeter', 0) + settings.setdefault('scantime', 30L) + + if 'rpcuser' not in settings or 'rpcpass' not in settings: + print("Missing username and/or password in cfg file") + sys.exit(1) + + settings['port'] = int(settings['port']) + settings['threads'] = int(settings['threads']) + settings['hashmeter'] = int(settings['hashmeter']) + settings['scantime'] = long(settings['scantime']) + + thread_list = [] + + for thread_id in range(settings['threads']): + p = Process(target=miner_thread, args=(thread_id,)) + p.start() + thread_list.append(p) + time.sleep(1) # stagger threads + + print(settings['threads'], "mining threads started") + + print(time.asctime(), "Miner Starts - {0}:{1}".format(settings['host'], + settings['port'])) + try: + for thread_process in thread_list: + thread_process.join() + except KeyboardInterrupt: + pass + + print(time.asctime(), "Miner Stops - {0}:{1}".format(settings['host'], + settings['port'])) diff --git a/doc/tor.md b/doc/tor.md index b5eb91e12e..560f71fa27 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -13,11 +13,6 @@ configure Tor. The first step is running Bitcoin behind a Tor proxy. This will already make all outgoing connections be anonymized, but more is possible. - -socks=5 SOCKS5 supports connecting-to-hostname, which can be used instead - of doing a (leaking) local DNS lookup. SOCKS5 is the default, - but SOCKS4 does not support this. (SOCKS4a does, but isn't - implemented). - -proxy=ip:port Set the proxy server. If SOCKS5 is selected (default), this proxy server will be used to try to reach .onion addresses as well. diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 75b7b683dd..2772bc753a 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -145,6 +145,7 @@ BITCOIN_MM = \ QT_MOC = \ qt/bitcoin.moc \ + qt/bitcoinamountfield.moc \ qt/intro.moc \ qt/overviewpage.moc \ qt/rpcconsole.moc diff --git a/src/allocators.h b/src/allocators.h index 7012ef7e2a..be0be7ab96 100644 --- a/src/allocators.h +++ b/src/allocators.h @@ -131,7 +131,7 @@ public: * Due to the unpredictable order of static initializers, we have to make sure the * LockedPageManager instance exists before any other STL-based objects that use * secure_allocator are created. So instead of having LockedPageManager also be - * static-intialized, it is created on demand. + * static-initialized, it is created on demand. */ class LockedPageManager: public LockedPageManagerBase<MemoryPageLocker> { diff --git a/src/main.cpp b/src/main.cpp index 84178b16e2..06ce15b5b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2016,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; @@ -2026,6 +2026,7 @@ static CBlockIndex* FindMostWorkChain() { setBlockIndexValid.erase(pindexFailed); pindexFailed = pindexFailed->pprev; } + setBlockIndexValid.erase(pindexTest); fInvalidAncestor = true; break; } diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index e047c278b7..6466039013 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -9,63 +9,185 @@ #include "qvaluecombobox.h" #include <QApplication> -#include <QDoubleSpinBox> +#include <QAbstractSpinBox> #include <QHBoxLayout> #include <QKeyEvent> -#include <qmath.h> // for qPow() +#include <QLineEdit> -// QDoubleSpinBox that shows SI-style thin space thousands separators -class AmountSpinBox: public QDoubleSpinBox +/** 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): - QDoubleSpinBox(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); + } } - QString textFromValue(double value) const + + qint64 value(bool *valid_out=0) const { - QStringList parts = QDoubleSpinBox::textFromValue(value).split("."); - QString quotient_str = parts[0]; - QString remainder_str; - if(parts.size() > 1) - remainder_str = parts[1]; - - // Code duplication between here and BitcoinUnits::format - // TODO: Figure out how to share this code - QChar thin_sp(THIN_SP_CP); - int q_size = quotient_str.size(); - if (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 (r_size > 4) - for (int i = 3, adj = 0; i < r_size; i += 3, adj++) - remainder_str.insert(i + adj, thin_sp); - - if(remainder_str.isEmpty()) - return quotient_str; + 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 - return quotient_str + QString(".") + remainder_str; + clear(); } - QValidator::State validate (QString &text, int &pos) const + + void setSingleStep(qint64 step) { - QString s(BitcoinUnits::removeSpaces(text)); - return QDoubleSpinBox::validate(s, pos); + 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; } - double valueFromText(const QString& text) const + +protected: + bool event(QEvent *event) { - return QDoubleSpinBox::valueFromText(BitcoinUnits::removeSpaces(text)); + 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 AmountSpinBox(this); amount->setLocale(QLocale::c()); amount->installEventFilter(this); @@ -85,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(BitcoinUnits::removeSpaces(text).toDouble()); -} - void BitcoinAmountField::clear() { amount->clear(); @@ -108,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; } @@ -129,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) @@ -144,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); } @@ -167,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) @@ -195,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) @@ -226,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 21aed235cf..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): @@ -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) @@ -250,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 7fa24c8542..be9dca6012 100644 --- a/src/qt/bitcoinunits.h +++ b/src/qt/bitcoinunits.h @@ -82,10 +82,6 @@ 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 @@ -120,6 +116,9 @@ public: return text; } + //! Return maximum number of base units (Satoshis) + static qint64 maxMoney(); + private: QList<BitcoinUnits::Unit> unitlist; }; 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/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/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 ebb1369e31..164e2741a2 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -602,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/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 |