diff options
60 files changed, 2304 insertions, 816 deletions
diff --git a/.gitignore b/.gitignore index 4169a2d96c..e21ea92552 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ src/bitcoin src/bitcoind src/bitcoin-cli +src/bitcoin-tx src/test/test_bitcoin src/qt/test/test_bitcoin-qt 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/README.md b/contrib/README.md index 63b1875d30..dae975e9ef 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -19,10 +19,6 @@ Contains the script `github-merge.sh` for merging github pull requests securely ### [Linearize](/contrib/linearize) ### Construct a linear, no-fork, best version of the blockchain. -### [PyMiner](/contrib/pyminer) ### - -This is a 'getwork' CPU mining client for Bitcoin. It is pure-python, and therefore very, very slow. The purpose is to provide a reference implementation of a miner in order to study and develop other mining programs. - ### [Qos](/contrib/qos) ### A Linux bash script that will set up traffic control (tc) to limit the outgoing bandwidth for connections to the Bitcoin network. This means one can have an always-on bitcoind instance running, and another local bitcoind/bitcoin-qt instance which connects to this node and receives blocks from it. 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/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index fdad28018f..eece81dd3d 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -17,7 +17,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # -import subprocess, sys, re, os, shutil, stat, os.path +import subprocess, sys, re, os, shutil, stat, os.path, time from string import Template from argparse import ArgumentParser @@ -804,6 +804,7 @@ if config.dmg is not None: if verbose >= 2: print "+ Finalizing .dmg disk image +" + time.sleep(5) try: runHDIUtil("convert", dmg_name + ".temp", format="UDBZ", o=dmg_name + ".dmg", ov=True) diff --git a/contrib/pyminer/README.md b/contrib/pyminer/README.md deleted file mode 100644 index 3b20f2fdea..0000000000 --- a/contrib/pyminer/README.md +++ /dev/null @@ -1,8 +0,0 @@ -### PyMiner ### - -This is a 'getwork' CPU mining client for Bitcoin. It is pure-python, and therefore very, very slow. The purpose is to provide a reference implementation of a miner, for study. - -### Other Resources ### - -- [BitcoinTalk Thread](https://bitcointalk.org/index.php?topic=3546.0) -- [Jgarzik Repo](https://github.com/jgarzik/pyminer)
\ No newline at end of file diff --git a/contrib/pyminer/example-config.cfg b/contrib/pyminer/example-config.cfg deleted file mode 100644 index 103e7c1372..0000000000 --- a/contrib/pyminer/example-config.cfg +++ /dev/null @@ -1,32 +0,0 @@ - -# -# RPC login details -# -host=127.0.0.1 -port=8332 - -rpcuser=myusername -rpcpass=mypass - - -# -# mining details -# - -threads=4 - -# periodic rate for requesting new work, if solution not found -scantime=60 - - -# -# misc. -# - -# not really used right now -logdir=/tmp/pyminer - -# set to 1, to enable hashmeter output -hashmeter=0 - - diff --git a/contrib/pyminer/pyminer.py b/contrib/pyminer/pyminer.py deleted file mode 100755 index 0a2932d66e..0000000000 --- a/contrib/pyminer/pyminer.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/python -# -# Copyright (c) 2011 The Bitcoin developers -# Distributed under the MIT/X11 software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# - -import time -import json -import pprint -import hashlib -import struct -import re -import base64 -import httplib -import sys -from multiprocessing import Process - -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) - -def uint32(x): - return x & 0xffffffffL - -def bytereverse(x): - 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) - -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) - -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 miner_thread(id): - 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']) - diff --git a/contrib/systemd/bitcoind.service b/contrib/systemd/bitcoind.service new file mode 100644 index 0000000000..edc81cc763 --- /dev/null +++ b/contrib/systemd/bitcoind.service @@ -0,0 +1,17 @@ +[Unit] +Description=Bitcoin's distributed currency daemon +After=network.target + +[Service] +User=bitcoind +Group=bitcoind + +Type=forking +PIDFile=/var/lib/bitcoind/bitcoind.pid +ExecStart=/usr/bin/bitcoind -daemon -pid=/var/lib/bitcoind/bitcoind.pid -conf=/etc/bitcoind.conf -datadir=/var/lib/bitcoind + +Restart=always +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/doc/README.md b/doc/README.md index f5aeb34a3c..f8bb8020d4 100644 --- a/doc/README.md +++ b/doc/README.md @@ -68,9 +68,10 @@ The Bitcoin repo's [root README](https://github.com/bitcoin/bitcoin/blob/master/ - [Assets Attribution](assets-attribution.md) - [Files](files.md) - [Tor Support](tor.md) +- [Systemd](systemd.md) License --------------------- Distributed under the [MIT/X11 software license](http://www.opensource.org/licenses/mit-license.php). -This product includes software developed by the OpenSSL Project for use in the [OpenSSL Toolkit](http://www.openssl.org/). This product includes +This product includes software developed by the OpenSSL Project for use in the [OpenSSL Toolkit](https://www.openssl.org/). This product includes cryptographic software written by Eric Young ([eay@cryptsoft.com](mailto:eay@cryptsoft.com)), and UPnP software written by Thomas Bernard. diff --git a/doc/README_windows.txt b/doc/README_windows.txt index 18fd4216f9..368f2b45e1 100644 --- a/doc/README_windows.txt +++ b/doc/README_windows.txt @@ -5,7 +5,7 @@ Copyright (c) 2009-2014 Bitcoin Core Developers Distributed under the MIT/X11 software license, see the accompanying
file COPYING or http://www.opensource.org/licenses/mit-license.php.
This product includes software developed by the OpenSSL Project for use in
-the OpenSSL Toolkit (http://www.openssl.org/). This product includes
+the OpenSSL Toolkit (https://www.openssl.org/). This product includes
cryptographic software written by Eric Young (eay@cryptsoft.com).
diff --git a/doc/build-msw.md b/doc/build-msw.md deleted file mode 100644 index 9e4eaee3f5..0000000000 --- a/doc/build-msw.md +++ /dev/null @@ -1,83 +0,0 @@ -WINDOWS BUILD NOTES -=================== - - -Compilers Supported -------------------- -TODO: What works? -Note: releases are cross-compiled using mingw running on Linux. - - -Dependencies ------------- -Libraries you need to download separately and build: - - name default path download - -------------------------------------------------------------------------------------------------------------------- - OpenSSL \openssl-1.0.1c-mgw http://www.openssl.org/source/ - Berkeley DB \db-4.8.30.NC-mgw http://www.oracle.com/technology/software/products/berkeley-db/index.html - Boost \boost-1.50.0-mgw http://www.boost.org/users/download/ - miniupnpc \miniupnpc-1.6-mgw http://miniupnp.tuxfamily.org/files/ - -Their licenses: - - OpenSSL Old BSD license with the problematic advertising requirement - Berkeley DB New BSD license with additional requirement that linked software must be free open source - Boost MIT-like license - miniupnpc New (3-clause) BSD license - -Versions used in this release: - - OpenSSL 1.0.1c - Berkeley DB 4.8.30.NC - Boost 1.50.0 - miniupnpc 1.6 - - -OpenSSL -------- -MSYS shell: - -un-tar sources with MSYS 'tar xfz' to avoid issue with symlinks (OpenSSL ticket 2377) -change 'MAKE' env. variable from 'C:\MinGW32\bin\mingw32-make.exe' to '/c/MinGW32/bin/mingw32-make.exe' - - cd /c/openssl-1.0.1c-mgw - ./config - make - -Berkeley DB ------------ -MSYS shell: - - cd /c/db-4.8.30.NC-mgw/build_unix - sh ../dist/configure --enable-mingw --enable-cxx - make - -Boost ------ -MSYS shell: - - downloaded boost jam 3.1.18 - cd \boost-1.50.0-mgw - bjam toolset=gcc --build-type=complete stage - -MiniUPnPc ---------- -UPnP support is optional, make with `USE_UPNP=` to disable it. - -MSYS shell: - - cd /c/miniupnpc-1.6-mgw - make -f Makefile.mingw - mkdir miniupnpc - cp *.h miniupnpc/ - -Bitcoin -------- -MSYS shell: - - cd \bitcoin - sh autogen.sh - sh configure - mingw32-make - strip bitcoind.exe diff --git a/doc/systemd.md b/doc/systemd.md new file mode 100644 index 0000000000..96202c1532 --- /dev/null +++ b/doc/systemd.md @@ -0,0 +1,47 @@ +SYSTEMD SUPPORT IN BITCOIN +========================== + +Packagers can find a .service file in this repo in order to integrate bitcoin's +daemon into systemd based distributions. + +bitcoind.service file is located in contrib/systemd/ folder. + +1. Users +--------------------------------- + +This .service file assumes bitcoind user and group exist in the system, so packager +should make sure they are created on installation. + +2. Files +--------------------------------- + +The .service file assumes several paths that might need to be adjusted according +to packager's needs. + +Daemon's config file is assumed to be located at /etc/bitcoind.conf (you can +use contrib/debian/examples/bitcoin.conf as an example). Once installed, users +must edit the file in order to update at least these two +values: rpcuser and rpcpassword . Failing to do so will make the daemon fail +to boot. However, the message written to /var/lib/bitcoind/debug.log file is +very helpful and no default values should be set: + + YYYY-MM-DD HH:MM:DD Error: To use the "-server" option, you must set a rpcpassword in the configuration file: + /etc/bitcoind.conf + It is recommended you use the following random password: + rpcuser=bitcoinrpc + rpcpassword=HdYZ5HGtAF7mx8aTw6uCATtD2maMAK4E12Ysp4YNZQcX + (you do not need to remember this password) + The username and password MUST NOT be the same. + If the file does not exist, create it with owner-readable-only file permissions. + It is also recommended to set alertnotify so you are notified of problems; + for example: alertnotify=echo %s | mail -s "Bitcoin Alert" admin@foo.com + +Daemon's data and pid files will be stored in /var/lib/bitcoind directory, so it +should be created on installation and make bitcoind user/group it's owner. + +3. Installing .service file +--------------------------------- + +Installing this .service file consists on just copying it to /usr/lib/systemd/system +directory, followed by the command "systemctl daemon-reload" in order to update +running systemd configuration. 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.am b/src/Makefile.am index ff23747592..2d84eeba1c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,6 +34,7 @@ LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CLI=libbitcoin_cli.a LIBBITCOIN_UTIL=libbitcoin_util.a LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a +LIBBITCOIN_UNIVALUE=univalue/libbitcoin_univalue.a LIBBITCOINQT=qt/libbitcoinqt.a noinst_LIBRARIES = \ @@ -41,6 +42,7 @@ noinst_LIBRARIES = \ libbitcoin_common.a \ libbitcoin_cli.a \ libbitcoin_util.a \ + univalue/libbitcoin_univalue.a \ crypto/libbitcoin_crypto.a if ENABLE_WALLET BITCOIN_INCLUDES += $(BDB_CPPFLAGS) @@ -58,6 +60,8 @@ if BUILD_BITCOIN_CLI bin_PROGRAMS += bitcoin-cli endif +bin_PROGRAMS += bitcoin-tx + .PHONY: FORCE # bitcoin core # BITCOIN_CORE_H = \ @@ -75,6 +79,7 @@ BITCOIN_CORE_H = \ coins.h \ compat.h \ core.h \ + core_io.h \ crypter.h \ db.h \ hash.h \ @@ -177,6 +182,13 @@ crypto_libbitcoin_crypto_a_SOURCES = \ crypto/sha1.h \ crypto/ripemd160.h +# univalue JSON library +univalue_libbitcoin_univalue_a_SOURCES = \ + univalue/univalue.cpp \ + univalue/univalue_read.cpp \ + univalue/univalue_write.cpp \ + univalue/univalue.h + # common: shared between bitcoind, and bitcoin-qt and non-server tools libbitcoin_common_a_CPPFLAGS = $(BITCOIN_INCLUDES) libbitcoin_common_a_SOURCES = \ @@ -185,6 +197,8 @@ libbitcoin_common_a_SOURCES = \ chainparams.cpp \ coins.cpp \ core.cpp \ + core_read.cpp \ + core_write.cpp \ hash.cpp \ key.cpp \ keystore.cpp \ @@ -226,6 +240,7 @@ nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h bitcoind_LDADD = \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_UNIVALUE) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CRYPTO) \ $(LIBLEVELDB) \ @@ -264,6 +279,17 @@ endif bitcoin_cli_CPPFLAGS = $(BITCOIN_INCLUDES) # +# bitcoin-tx binary # +bitcoin_tx_LDADD = \ + $(LIBBITCOIN_UNIVALUE) \ + $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_UTIL) \ + $(LIBBITCOIN_CRYPTO) \ + $(BOOST_LIBS) +bitcoin_tx_SOURCES = bitcoin-tx.cpp +bitcoin_tx_CPPFLAGS = $(BITCOIN_INCLUDES) +# + if TARGET_WINDOWS bitcoin_cli_SOURCES += bitcoin-cli-res.rc endif diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 75b7b683dd..2052264dc9 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 @@ -358,7 +359,7 @@ qt_bitcoin_qt_LDADD = qt/libbitcoinqt.a $(LIBBITCOIN_SERVER) if ENABLE_WALLET qt_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif -qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBLEVELDB) $(LIBMEMENV) \ +qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) if USE_LIBSECP256K1 qt_bitcoin_qt_LDADD += secp256k1/libsecp256k1.la diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 51ce006fc1..d49b2240ea 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -30,7 +30,7 @@ qt_test_test_bitcoin_qt_LDADD = $(LIBBITCOINQT) $(LIBBITCOIN_SERVER) if ENABLE_WALLET qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif -qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBLEVELDB) \ +qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) \ $(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) if USE_LIBSECP256K1 diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 12b90adca3..72451fba9a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -64,7 +64,7 @@ endif test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) -test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBLEVELDB) $(LIBMEMENV) \ +test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) 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/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 016b2f50f5..3b991f9276 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -50,8 +50,7 @@ static bool AppInitRPC(int argc, char* argv[]) // Parameters // ParseParameters(argc, argv); - if (!boost::filesystem::is_directory(GetDataDir(false))) - { + if (!boost::filesystem::is_directory(GetDataDir(false))) { fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str()); return false; } @@ -66,11 +65,9 @@ static bool AppInitRPC(int argc, char* argv[]) fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); return false; } - if (argc<2 || mapArgs.count("-?") || mapArgs.count("-help") || mapArgs.count("-version")) - { + if (argc<2 || mapArgs.count("-?") || mapArgs.count("-help") || mapArgs.count("-version")) { std::string strUsage = _("Bitcoin Core RPC client version") + " " + FormatFullVersion() + "\n"; - if (!mapArgs.count("-version")) - { + if (!mapArgs.count("-version")) { strUsage += "\n" + _("Usage:") + "\n" + " bitcoin-cli [options] <command> [params] " + _("Send command to Bitcoin Core") + "\n" + " bitcoin-cli [options] help " + _("List commands") + "\n" + @@ -153,11 +150,9 @@ int CommandLineRPC(int argc, char *argv[]) { string strPrint; int nRet = 0; - try - { + try { // Skip switches - while (argc > 1 && IsSwitchChar(argv[1][0])) - { + while (argc > 1 && IsSwitchChar(argv[1][0])) { argc--; argv++; } @@ -178,15 +173,12 @@ int CommandLineRPC(int argc, char *argv[]) const Value& result = find_value(reply, "result"); const Value& error = find_value(reply, "error"); - if (error.type() != null_type) - { + if (error.type() != null_type) { // Error strPrint = "error: " + write_string(error, false); int code = find_value(error.get_obj(), "code").get_int(); nRet = abs(code); - } - else - { + } else { // Result if (result.type() == null_type) strPrint = ""; @@ -208,8 +200,7 @@ int CommandLineRPC(int argc, char *argv[]) throw; } - if (strPrint != "") - { + if (strPrint != "") { fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); } return nRet; @@ -219,8 +210,7 @@ int main(int argc, char* argv[]) { SetupEnvironment(); - try - { + try { if(!AppInitRPC(argc, argv)) return EXIT_FAILURE; } @@ -233,8 +223,7 @@ int main(int argc, char* argv[]) } int ret = EXIT_FAILURE; - try - { + try { ret = CommandLineRPC(argc, argv); } catch (std::exception& e) { diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp new file mode 100644 index 0000000000..299315424b --- /dev/null +++ b/src/bitcoin-tx.cpp @@ -0,0 +1,597 @@ +// Copyright (c) 2009-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "base58.h" +#include "util.h" +#include "core.h" +#include "main.h" // for MAX_BLOCK_SIZE +#include "keystore.h" +#include "ui_interface.h" // for _(...) +#include "univalue/univalue.h" +#include "core_io.h" + +#include <stdio.h> +#include <boost/assign/list_of.hpp> + +using namespace std; +using namespace boost::assign; + +static bool fCreateBlank; +static map<string,UniValue> registers; +CClientUIInterface uiInterface; + +static bool AppInitRawTx(int argc, char* argv[]) +{ + // + // Parameters + // + ParseParameters(argc, argv); + + // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) + if (!SelectParamsFromCommandLine()) { + fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); + return false; + } + + fCreateBlank = GetBoolArg("-create", false); + + if (argc<2 || mapArgs.count("-?") || mapArgs.count("-help")) + { + // First part of help message is specific to this utility + std::string strUsage = _("Bitcoin Core bitcoin-tx utility version") + " " + FormatFullVersion() + "\n\n" + + _("Usage:") + "\n" + + " bitcoin-tx [options] <hex-tx> [commands] " + _("Update hex-encoded bitcoin transaction") + "\n" + + " bitcoin-tx [options] -create [commands] " + _("Create hex-encoded bitcoin transaction") + "\n" + + "\n"; + + fprintf(stdout, "%s", strUsage.c_str()); + + strUsage = _("Options:") + "\n"; + strUsage += " -? " + _("This help message") + "\n"; + strUsage += " -create " + _("Create new, empty TX.") + "\n"; + strUsage += " -json " + _("Select JSON output") + "\n"; + strUsage += " -regtest " + _("Enter regression test mode, which uses a special chain in which blocks can be solved instantly.") + "\n"; + strUsage += " -testnet " + _("Use the test network") + "\n"; + strUsage += "\n"; + + fprintf(stdout, "%s", strUsage.c_str()); + + + strUsage = _("Commands:") + "\n"; + strUsage += " delin=N " + _("Delete input N from TX") + "\n"; + strUsage += " delout=N " + _("Delete output N from TX") + "\n"; + strUsage += " in=TXID:VOUT " + _("Add input to TX") + "\n"; + strUsage += " locktime=N " + _("Set TX lock time to N") + "\n"; + strUsage += " nversion=N " + _("Set TX version to N") + "\n"; + strUsage += " outaddr=VALUE:ADDRESS " + _("Add address-based output to TX") + "\n"; + strUsage += " outscript=VALUE:SCRIPT " + _("Add raw script output to TX") + "\n"; + strUsage += " sign=SIGHASH-FLAGS " + _("Add zero or more signatures to transaction") + "\n"; + strUsage += " This command requires JSON registers:\n"; + strUsage += " prevtxs=JSON object\n"; + strUsage += " privatekeys=JSON object\n"; + strUsage += " See signrawtransaction docs for format of sighash flags, JSON objects.\n"; + strUsage += "\n"; + fprintf(stdout, "%s", strUsage.c_str()); + + strUsage = _("Register Commands:") + "\n"; + strUsage += " load=NAME:FILENAME " + _("Load JSON file FILENAME into register NAME") + "\n"; + strUsage += " set=NAME:JSON-STRING " + _("Set register NAME to given JSON-STRING") + "\n"; + strUsage += "\n"; + fprintf(stdout, "%s", strUsage.c_str()); + + return false; + } + return true; +} + +static void RegisterSetJson(const string& key, const string& rawJson) +{ + UniValue val; + if (!val.read(rawJson)) { + string strErr = "Cannot parse JSON for key " + key; + throw runtime_error(strErr); + } + + registers[key] = val; +} + +static void RegisterSet(const string& strInput) +{ + // separate NAME:VALUE in string + size_t pos = strInput.find(':'); + if ((pos == string::npos) || + (pos == 0) || + (pos == (strInput.size() - 1))) + throw runtime_error("Register input requires NAME:VALUE"); + + string key = strInput.substr(0, pos); + string valStr = strInput.substr(pos + 1, string::npos); + + RegisterSetJson(key, valStr); +} + +static void RegisterLoad(const string& strInput) +{ + // separate NAME:FILENAME in string + size_t pos = strInput.find(':'); + if ((pos == string::npos) || + (pos == 0) || + (pos == (strInput.size() - 1))) + throw runtime_error("Register load requires NAME:FILENAME"); + + string key = strInput.substr(0, pos); + string filename = strInput.substr(pos + 1, string::npos); + + FILE *f = fopen(filename.c_str(), "r"); + if (!f) { + string strErr = "Cannot open file " + filename; + throw runtime_error(strErr); + } + + // load file chunks into one big buffer + string valStr; + while ((!feof(f)) && (!ferror(f))) { + char buf[4096]; + int bread = fread(buf, 1, sizeof(buf), f); + if (bread <= 0) + break; + + valStr.insert(valStr.size(), buf, bread); + } + + if (ferror(f)) { + string strErr = "Error reading file " + filename; + throw runtime_error(strErr); + } + + fclose(f); + + // evaluate as JSON buffer register + RegisterSetJson(key, valStr); +} + +static void MutateTxVersion(CMutableTransaction& tx, const string& cmdVal) +{ + int64_t newVersion = atoi64(cmdVal); + if (newVersion < 1 || newVersion > CTransaction::CURRENT_VERSION) + throw runtime_error("Invalid TX version requested"); + + tx.nVersion = (int) newVersion; +} + +static void MutateTxLocktime(CMutableTransaction& tx, const string& cmdVal) +{ + int64_t newLocktime = atoi64(cmdVal); + if (newLocktime < 0LL || newLocktime > 0xffffffffLL) + throw runtime_error("Invalid TX locktime requested"); + + tx.nLockTime = (unsigned int) newLocktime; +} + +static void MutateTxAddInput(CMutableTransaction& tx, const string& strInput) +{ + // separate TXID:VOUT in string + size_t pos = strInput.find(':'); + if ((pos == string::npos) || + (pos == 0) || + (pos == (strInput.size() - 1))) + throw runtime_error("TX input missing separator"); + + // extract and validate TXID + string strTxid = strInput.substr(0, pos); + if ((strTxid.size() != 64) || !IsHex(strTxid)) + throw runtime_error("invalid TX input txid"); + uint256 txid(strTxid); + + static const unsigned int minTxOutSz = 9; + static const unsigned int maxVout = MAX_BLOCK_SIZE / minTxOutSz; + + // extract and validate vout + string strVout = strInput.substr(pos + 1, string::npos); + int vout = atoi(strVout); + if ((vout < 0) || (vout > (int)maxVout)) + throw runtime_error("invalid TX input vout"); + + // append to transaction input list + CTxIn txin(txid, vout); + tx.vin.push_back(txin); +} + +static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput) +{ + // separate VALUE:ADDRESS in string + size_t pos = strInput.find(':'); + if ((pos == string::npos) || + (pos == 0) || + (pos == (strInput.size() - 1))) + throw runtime_error("TX output missing separator"); + + // extract and validate VALUE + string strValue = strInput.substr(0, pos); + int64_t value; + if (!ParseMoney(strValue, value)) + throw runtime_error("invalid TX output value"); + + // extract and validate ADDRESS + string strAddr = strInput.substr(pos + 1, string::npos); + CBitcoinAddress addr(strAddr); + if (!addr.IsValid()) + throw runtime_error("invalid TX output address"); + + // build standard output script via SetDestination() + CScript scriptPubKey; + scriptPubKey.SetDestination(addr.Get()); + + // construct TxOut, append to transaction output list + CTxOut txout(value, scriptPubKey); + tx.vout.push_back(txout); +} + +static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput) +{ + // separate VALUE:SCRIPT in string + size_t pos = strInput.find(':'); + if ((pos == string::npos) || + (pos == 0) || + (pos == (strInput.size() - 1))) + throw runtime_error("TX output missing separator"); + + // extract and validate VALUE + string strValue = strInput.substr(0, pos); + int64_t value; + if (!ParseMoney(strValue, value)) + throw runtime_error("invalid TX output value"); + + // extract and validate script + string strScript = strInput.substr(pos + 1, string::npos); + CScript scriptPubKey = ParseScript(strScript); // throws on err + + // construct TxOut, append to transaction output list + CTxOut txout(value, scriptPubKey); + tx.vout.push_back(txout); +} + +static void MutateTxDelInput(CMutableTransaction& tx, const string& strInIdx) +{ + // parse requested deletion index + int inIdx = atoi(strInIdx); + if (inIdx < 0 || inIdx >= (int)tx.vin.size()) { + string strErr = "Invalid TX input index '" + strInIdx + "'"; + throw runtime_error(strErr.c_str()); + } + + // delete input from transaction + tx.vin.erase(tx.vin.begin() + inIdx); +} + +static void MutateTxDelOutput(CMutableTransaction& tx, const string& strOutIdx) +{ + // parse requested deletion index + int outIdx = atoi(strOutIdx); + if (outIdx < 0 || outIdx >= (int)tx.vout.size()) { + string strErr = "Invalid TX output index '" + strOutIdx + "'"; + throw runtime_error(strErr.c_str()); + } + + // delete output from transaction + tx.vout.erase(tx.vout.begin() + outIdx); +} + +static const unsigned int N_SIGHASH_OPTS = 6; +static const struct { + const char *flagStr; + int flags; +} sighashOptions[N_SIGHASH_OPTS] = { + "ALL", SIGHASH_ALL, + "NONE", SIGHASH_NONE, + "SINGLE", SIGHASH_SINGLE, + "ALL|ANYONECANPAY", SIGHASH_ALL|SIGHASH_ANYONECANPAY, + "NONE|ANYONECANPAY", SIGHASH_NONE|SIGHASH_ANYONECANPAY, + "SINGLE|ANYONECANPAY", SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, +}; + +static bool findSighashFlags(int& flags, const string& flagStr) +{ + flags = 0; + + for (unsigned int i = 0; i < N_SIGHASH_OPTS; i++) { + if (flagStr == sighashOptions[i].flagStr) { + flags = sighashOptions[i].flags; + return true; + } + } + + return false; +} + +uint256 ParseHashUO(map<string,UniValue>& o, string strKey) +{ + if (!o.count(strKey)) + return 0; + return ParseHashUV(o[strKey], strKey); +} + +vector<unsigned char> ParseHexUO(map<string,UniValue>& o, string strKey) +{ + if (!o.count(strKey)) { + vector<unsigned char> emptyVec; + return emptyVec; + } + return ParseHexUV(o[strKey], strKey); +} + +static void MutateTxSign(CMutableTransaction& tx, const string& flagStr) +{ + int nHashType = SIGHASH_ALL; + + if (flagStr.size() > 0) + if (!findSighashFlags(nHashType, flagStr)) + throw runtime_error("unknown sighash flag/sign option"); + + vector<CTransaction> txVariants; + txVariants.push_back(tx); + + // mergedTx will end up with all the signatures; it + // starts as a clone of the raw tx: + CMutableTransaction mergedTx(txVariants[0]); + bool fComplete = true; + CCoinsView viewDummy; + CCoinsViewCache view(viewDummy); + + if (!registers.count("privatekeys")) + throw runtime_error("privatekeys register variable must be set."); + bool fGivenKeys = false; + CBasicKeyStore tempKeystore; + UniValue keysObj = registers["privatekeys"]; + fGivenKeys = true; + + for (unsigned int kidx = 0; kidx < keysObj.count(); kidx++) { + if (!keysObj[kidx].isStr()) + throw runtime_error("privatekey not a string"); + CBitcoinSecret vchSecret; + bool fGood = vchSecret.SetString(keysObj[kidx].getValStr()); + if (!fGood) + throw runtime_error("privatekey not valid"); + + CKey key = vchSecret.GetKey(); + tempKeystore.AddKey(key); + } + + // Add previous txouts given in the RPC call: + if (!registers.count("prevtxs")) + throw runtime_error("prevtxs register variable must be set."); + UniValue prevtxsObj = registers["privatekeys"]; + { + for (unsigned int previdx = 0; previdx < prevtxsObj.count(); previdx++) { + UniValue prevOut = prevtxsObj[previdx]; + if (!prevOut.isObject()) + throw runtime_error("expected prevtxs internal object"); + + map<string,UniValue::VType> types = map_list_of("txid", UniValue::VSTR)("vout",UniValue::VNUM)("scriptPubKey",UniValue::VSTR); + if (!prevOut.checkObject(types)) + throw runtime_error("prevtxs internal object typecheck fail"); + + uint256 txid = ParseHashUV(prevOut, "txid"); + + int nOut = atoi(prevOut["vout"].getValStr()); + if (nOut < 0) + throw runtime_error("vout must be positive"); + + vector<unsigned char> pkData(ParseHexUV(prevOut, "scriptPubKey")); + CScript scriptPubKey(pkData.begin(), pkData.end()); + + CCoins coins; + if (view.GetCoins(txid, coins)) { + if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) { + string err("Previous output scriptPubKey mismatch:\n"); + err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+ + scriptPubKey.ToString(); + throw runtime_error(err); + } + // what todo if txid is known, but the actual output isn't? + } + if ((unsigned int)nOut >= coins.vout.size()) + coins.vout.resize(nOut+1); + coins.vout[nOut].scriptPubKey = scriptPubKey; + coins.vout[nOut].nValue = 0; // we don't know the actual output value + view.SetCoins(txid, coins); + + // if redeemScript given and private keys given, + // add redeemScript to the tempKeystore so it can be signed: + if (fGivenKeys && scriptPubKey.IsPayToScriptHash() && + prevOut.exists("redeemScript")) { + UniValue v = prevOut["redeemScript"]; + vector<unsigned char> rsData(ParseHexUV(v, "redeemScript")); + CScript redeemScript(rsData.begin(), rsData.end()); + tempKeystore.AddCScript(redeemScript); + } + } + } + + const CKeyStore& keystore = tempKeystore; + + bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); + + // Sign what we can: + for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { + CTxIn& txin = mergedTx.vin[i]; + CCoins coins; + if (!view.GetCoins(txin.prevout.hash, coins) || !coins.IsAvailable(txin.prevout.n)) { + fComplete = false; + continue; + } + const CScript& prevPubKey = coins.vout[txin.prevout.n].scriptPubKey; + + txin.scriptSig.clear(); + // Only sign SIGHASH_SINGLE if there's a corresponding output: + if (!fHashSingle || (i < mergedTx.vout.size())) + SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); + + // ... and merge in other signatures: + BOOST_FOREACH(const CTransaction& txv, txVariants) { + txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); + } + if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, STANDARD_SCRIPT_VERIFY_FLAGS, 0)) + fComplete = false; + } + + if (fComplete) { + // do nothing... for now + // perhaps store this for later optional JSON output + } + + tx = mergedTx; +} + +static void MutateTx(CMutableTransaction& tx, const string& command, + const string& commandVal) +{ + if (command == "nversion") + MutateTxVersion(tx, commandVal); + else if (command == "locktime") + MutateTxLocktime(tx, commandVal); + + else if (command == "delin") + MutateTxDelInput(tx, commandVal); + else if (command == "in") + MutateTxAddInput(tx, commandVal); + + else if (command == "delout") + MutateTxDelOutput(tx, commandVal); + else if (command == "outaddr") + MutateTxAddOutAddr(tx, commandVal); + else if (command == "outscript") + MutateTxAddOutScript(tx, commandVal); + + else if (command == "sign") + MutateTxSign(tx, commandVal); + + else if (command == "load") + RegisterLoad(commandVal); + + else if (command == "set") + RegisterSet(commandVal); + + else + throw runtime_error("unknown command"); +} + +static void OutputTxJSON(const CTransaction& tx) +{ + UniValue entry(UniValue::VOBJ); + TxToUniv(tx, 0, entry); + + string jsonOutput = entry.write(4); + fprintf(stdout, "%s\n", jsonOutput.c_str()); +} + +static void OutputTxHex(const CTransaction& tx) +{ + string strHex = EncodeHexTx(tx); + + fprintf(stdout, "%s\n", strHex.c_str()); +} + +static void OutputTx(const CTransaction& tx) +{ + if (GetBoolArg("-json", false)) + OutputTxJSON(tx); + else + OutputTxHex(tx); +} + +static int CommandLineRawTx(int argc, char* argv[]) +{ + string strPrint; + int nRet = 0; + try { + // Skip switches + while (argc > 1 && IsSwitchChar(argv[1][0])) { + argc--; + argv++; + } + + CTransaction txDecodeTmp; + int startArg; + + if (!fCreateBlank) { + // require at least one param + if (argc < 2) + throw runtime_error("too few parameters"); + + // param: hex-encoded bitcoin transaction + string strHexTx(argv[1]); + + if (!DecodeHexTx(txDecodeTmp, strHexTx)) + throw runtime_error("invalid transaction encoding"); + + startArg = 2; + } else + startArg = 1; + + CMutableTransaction tx(txDecodeTmp); + + for (int i = startArg; i < argc; i++) { + string arg = argv[i]; + string key, value; + size_t eqpos = arg.find('='); + if (eqpos == string::npos) + key = arg; + else { + key = arg.substr(0, eqpos); + value = arg.substr(eqpos + 1); + } + + MutateTx(tx, key, value); + } + + OutputTx(tx); + } + + catch (boost::thread_interrupted) { + throw; + } + catch (std::exception& e) { + strPrint = string("error: ") + e.what(); + nRet = EXIT_FAILURE; + } + catch (...) { + PrintExceptionContinue(NULL, "CommandLineRawTx()"); + throw; + } + + if (strPrint != "") { + fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); + } + return nRet; +} + +int main(int argc, char* argv[]) +{ + SetupEnvironment(); + + try { + if(!AppInitRawTx(argc, argv)) + return EXIT_FAILURE; + } + catch (std::exception& e) { + PrintExceptionContinue(&e, "AppInitRawTx()"); + return EXIT_FAILURE; + } catch (...) { + PrintExceptionContinue(NULL, "AppInitRawTx()"); + return EXIT_FAILURE; + } + + int ret = EXIT_FAILURE; + try { + ret = CommandLineRawTx(argc, argv); + } + catch (std::exception& e) { + PrintExceptionContinue(&e, "CommandLineRawTx()"); + } catch (...) { + PrintExceptionContinue(NULL, "CommandLineRawTx()"); + } + return ret; +} + diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index 4cab11db3d..717f0b90fe 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -51,11 +51,12 @@ namespace Checkpoints { (225430, uint256("0x00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932")) (250000, uint256("0x000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214")) (279000, uint256("0x0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40")) + (295000, uint256("0x00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983")) ; static const CCheckpointData data = { &mapCheckpoints, - 1389047471, // * UNIX timestamp of last checkpoint block - 30549816, // * total number of transactions between genesis and last checkpoint + 1397080064, // * UNIX timestamp of last checkpoint block + 36544669, // * total number of transactions between genesis and last checkpoint // (the tx=... number in the SetBestChain debug.log lines) 60000.0 // * estimated number of transactions per day after checkpoint }; diff --git a/src/core_io.h b/src/core_io.h new file mode 100644 index 0000000000..8a7d580570 --- /dev/null +++ b/src/core_io.h @@ -0,0 +1,23 @@ +#ifndef __BITCOIN_CORE_IO_H__ +#define __BITCOIN_CORE_IO_H__ + +#include <string> + +class uint256; +class CScript; +class CTransaction; +class UniValue; + +// core_read.cpp +extern CScript ParseScript(std::string s); +extern bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx); +extern uint256 ParseHashUV(const UniValue& v, const std::string& strName); +extern std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName); + +// core_write.cpp +extern std::string EncodeHexTx(const CTransaction& tx); +extern void ScriptPubKeyToUniv(const CScript& scriptPubKey, + UniValue& out, bool fIncludeHex); +extern void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry); + +#endif // __BITCOIN_CORE_IO_H__ diff --git a/src/core_read.cpp b/src/core_read.cpp new file mode 100644 index 0000000000..0f06bb6952 --- /dev/null +++ b/src/core_read.cpp @@ -0,0 +1,127 @@ + +#include <vector> +#include "core_io.h" +#include "core.h" +#include "serialize.h" +#include "script.h" +#include "util.h" + +#include <boost/assign/list_of.hpp> +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/algorithm/string/replace.hpp> +#include "univalue/univalue.h" + +using namespace std; +using namespace boost; +using namespace boost::algorithm; + +CScript ParseScript(std::string s) +{ + CScript result; + + static map<string, opcodetype> mapOpNames; + + if (mapOpNames.empty()) + { + for (int op = 0; op <= OP_NOP10; op++) + { + // Allow OP_RESERVED to get into mapOpNames + if (op < OP_NOP && op != OP_RESERVED) + continue; + + const char* name = GetOpName((opcodetype)op); + if (strcmp(name, "OP_UNKNOWN") == 0) + continue; + string strName(name); + mapOpNames[strName] = (opcodetype)op; + // Convenience: OP_ADD and just ADD are both recognized: + replace_first(strName, "OP_", ""); + mapOpNames[strName] = (opcodetype)op; + } + } + + vector<string> words; + split(words, s, is_any_of(" \t\n"), token_compress_on); + + for (std::vector<std::string>::const_iterator w = words.begin(); w != words.end(); ++w) + { + if (w->empty()) + { + // Empty string, ignore. (boost::split given '' will return one word) + } + else if (all(*w, is_digit()) || + (starts_with(*w, "-") && all(string(w->begin()+1, w->end()), is_digit()))) + { + // Number + int64_t n = atoi64(*w); + result << n; + } + else if (starts_with(*w, "0x") && (w->begin()+2 != w->end()) && IsHex(string(w->begin()+2, w->end()))) + { + // Raw hex data, inserted NOT pushed onto stack: + std::vector<unsigned char> raw = ParseHex(string(w->begin()+2, w->end())); + result.insert(result.end(), raw.begin(), raw.end()); + } + else if (w->size() >= 2 && starts_with(*w, "'") && ends_with(*w, "'")) + { + // Single-quoted string, pushed as data. NOTE: this is poor-man's + // parsing, spaces/tabs/newlines in single-quoted strings won't work. + std::vector<unsigned char> value(w->begin()+1, w->end()-1); + result << value; + } + else if (mapOpNames.count(*w)) + { + // opcode, e.g. OP_ADD or ADD: + result << mapOpNames[*w]; + } + else + { + throw runtime_error("script parse error"); + } + } + + return result; +} + +bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx) +{ + if (!IsHex(strHexTx)) + return false; + + vector<unsigned char> txData(ParseHex(strHexTx)); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + try { + ssData >> tx; + } + catch (std::exception &e) { + return false; + } + + return true; +} + +uint256 ParseHashUV(const UniValue& v, const string& strName) +{ + string strHex; + if (v.isStr()) + strHex = v.getValStr(); + if (!IsHex(strHex)) // Note: IsHex("") is false + throw runtime_error(strName+" must be hexadecimal string (not '"+strHex+"')"); + + uint256 result; + result.SetHex(strHex); + return result; +} + +vector<unsigned char> ParseHexUV(const UniValue& v, const string& strName) +{ + string strHex; + if (v.isStr()) + strHex = v.getValStr(); + if (!IsHex(strHex)) + throw runtime_error(strName+" must be hexadecimal string (not '"+strHex+"')"); + return ParseHex(strHex); +} + diff --git a/src/core_write.cpp b/src/core_write.cpp new file mode 100644 index 0000000000..2eb220779f --- /dev/null +++ b/src/core_write.cpp @@ -0,0 +1,89 @@ + +#include <vector> +#include "core_io.h" +#include "univalue/univalue.h" +#include "script.h" +#include "core.h" +#include "serialize.h" +#include "util.h" +#include "base58.h" + +using namespace std; + +string EncodeHexTx(const CTransaction& tx) +{ + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << tx; + return HexStr(ssTx.begin(), ssTx.end()); +} + +void ScriptPubKeyToUniv(const CScript& scriptPubKey, + UniValue& out, bool fIncludeHex) +{ + txnouttype type; + vector<CTxDestination> addresses; + int nRequired; + + out.pushKV("asm", scriptPubKey.ToString()); + if (fIncludeHex) + out.pushKV("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end())); + + if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { + out.pushKV("type", GetTxnOutputType(type)); + return; + } + + out.pushKV("reqSigs", nRequired); + out.pushKV("type", GetTxnOutputType(type)); + + UniValue a(UniValue::VARR); + BOOST_FOREACH(const CTxDestination& addr, addresses) + a.push_back(CBitcoinAddress(addr).ToString()); + out.pushKV("addresses", a); +} + +void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry) +{ + entry.pushKV("txid", tx.GetHash().GetHex()); + entry.pushKV("version", tx.nVersion); + entry.pushKV("locktime", (int64_t)tx.nLockTime); + + UniValue vin(UniValue::VARR); + BOOST_FOREACH(const CTxIn& txin, tx.vin) { + UniValue in(UniValue::VOBJ); + if (tx.IsCoinBase()) + in.pushKV("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); + else { + in.pushKV("txid", txin.prevout.hash.GetHex()); + in.pushKV("vout", (int64_t)txin.prevout.n); + UniValue o(UniValue::VOBJ); + o.pushKV("asm", txin.scriptSig.ToString()); + o.pushKV("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); + in.pushKV("scriptSig", o); + } + in.pushKV("sequence", (int64_t)txin.nSequence); + vin.push_back(in); + } + entry.pushKV("vin", vin); + + UniValue vout(UniValue::VARR); + for (unsigned int i = 0; i < tx.vout.size(); i++) { + const CTxOut& txout = tx.vout[i]; + + UniValue out(UniValue::VOBJ); + + UniValue outValue(UniValue::VNUM, FormatMoney(txout.nValue)); + out.pushKV("value", outValue); + out.pushKV("n", (int64_t)i); + + UniValue o(UniValue::VOBJ); + ScriptPubKeyToUniv(txout.scriptPubKey, o, true); + out.pushKV("scriptPubKey", o); + vout.push_back(out); + } + entry.pushKV("vout", vout); + + if (hashBlock != 0) + entry.pushKV("blockhash", hashBlock.GetHex()); +} + diff --git a/src/init.cpp b/src/init.cpp index 3488a8bedf..b84c233b99 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -240,7 +240,8 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += " -connect=<ip> " + _("Connect only to the specified node(s)") + "\n"; strUsage += " -discover " + _("Discover own IP address (default: 1 when listening and no -externalip)") + "\n"; strUsage += " -dns " + _("Allow DNS lookups for -addnode, -seednode and -connect") + " " + _("(default: 1)") + "\n"; - strUsage += " -dnsseed " + _("Find peers using DNS lookup (default: 1 unless -connect)") + "\n"; + strUsage += " -dnsseed " + _("Query for peer addresses via DNS lookup, if low on addresses (default: 1 unless -connect)") + "\n"; + strUsage += " -forcednsseed " + _("Always query for peer addresses via DNS lookup (default: 0)") + "\n"; strUsage += " -externalip=<ip> " + _("Specify your own public address") + "\n"; strUsage += " -listen " + _("Accept connections from outside (default: 1 if no -proxy or -connect)") + "\n"; strUsage += " -maxconnections=<n> " + _("Maintain at most <n> connections to peers (default: 125)") + "\n"; @@ -262,6 +263,7 @@ std::string HelpMessage(HelpMessageMode mode) #endif strUsage += " -whitebind=<addr> " + _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6") + "\n"; strUsage += " -whitelist=<netmask> " + _("Whitelist peers connecting from the given netmask or ip. Can be specified multiple times.") + "\n"; + strUsage += " " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway") + "\n"; #ifdef ENABLE_WALLET strUsage += "\n" + _("Wallet options:") + "\n"; @@ -285,7 +287,6 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += "\n" + _("Debugging/Testing options:") + "\n"; if (GetBoolArg("-help-debug", false)) { - strUsage += " -benchmark " + _("Show benchmark information (default: 0)") + "\n"; strUsage += " -checkpoints " + _("Only accept block chain matching built-in checkpoints (default: 1)") + "\n"; strUsage += " -dblogsize=<n> " + _("Flush database activity from memory pool to disk log every <n> megabytes (default: 100)") + "\n"; strUsage += " -disablesafemode " + _("Disable safemode, override a real safe mode event (default: 0)") + "\n"; @@ -298,7 +299,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += " -debug=<category> " + _("Output debugging information (default: 0, supplying <category> is optional)") + "\n"; strUsage += " " + _("If <category> is not supplied, output all debugging information.") + "\n"; strUsage += " " + _("<category> can be:"); - strUsage += " addrman, alert, coindb, db, lock, rand, rpc, selectcoins, mempool, net"; // Don't translate these and qt below + strUsage += " addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, net"; // Don't translate these and qt below if (mode == HMM_BITCOIN_QT) strUsage += ", qt"; strUsage += ".\n"; @@ -599,7 +600,9 @@ bool AppInit2(boost::thread_group& threadGroup) if (GetBoolArg("-tor", false)) return InitError(_("Error: Unsupported argument -tor found, use -onion.")); - fBenchmark = GetBoolArg("-benchmark", false); + if (GetBoolArg("-benchmark", false)) + InitWarning(_("Warning: Unsupported argument -benchmark ignored, use -debug=bench.")); + // Checkmempool defaults to true in regtest mode mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultCheckMemPool())); Checkpoints::fEnabled = GetBoolArg("-checkpoints", true); diff --git a/src/main.cpp b/src/main.cpp index 84178b16e2..353cde0bd7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,7 +45,6 @@ CConditionVariable cvBlockChange; int nScriptCheckThreads = 0; bool fImporting = false; bool fReindex = false; -bool fBenchmark = false; bool fTxIndex = false; bool fIsBareMultisigStd = true; unsigned int nCoinCacheSize = 5000; @@ -96,7 +95,9 @@ namespace { }; CBlockIndex *pindexBestInvalid; - // may contain all CBlockIndex*'s that have validness >=BLOCK_VALID_TRANSACTIONS, and must contain those who aren't failed + + // The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS or better that are at least + // as good as our current tip. Entries may be failed, though. set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexValid; CCriticalSection cs_LastBlockFile; @@ -1680,6 +1681,12 @@ void ThreadScriptCheck() { scriptcheckqueue.Thread(); } +static int64_t nTimeVerify = 0; +static int64_t nTimeConnect = 0; +static int64_t nTimeIndex = 0; +static int64_t nTimeCallbacks = 0; +static int64_t nTimeTotal = 0; + bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool fJustCheck) { AssertLockHeld(cs_main); @@ -1735,7 +1742,7 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL); - int64_t nStart = GetTimeMicros(); + int64_t nTimeStart = GetTimeMicros(); int64_t nFees = 0; int nInputs = 0; unsigned int nSigOps = 0; @@ -1785,9 +1792,8 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C vPos.push_back(std::make_pair(tx.GetHash(), pos)); pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); } - int64_t nTime = GetTimeMicros() - nStart; - if (fBenchmark) - LogPrintf("- Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin)\n", (unsigned)block.vtx.size(), 0.001 * nTime, 0.001 * nTime / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * nTime / (nInputs-1)); + int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart; + LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001); if (block.vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees)) return state.DoS(100, @@ -1797,9 +1803,8 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C if (!control.Wait()) return state.DoS(100, false); - int64_t nTime2 = GetTimeMicros() - nStart; - if (fBenchmark) - LogPrintf("- Verify %u txins: %.2fms (%.3fms/txin)\n", nInputs - 1, 0.001 * nTime2, nInputs <= 1 ? 0 : 0.001 * nTime2 / (nInputs-1)); + int64_t nTime2 = GetTimeMicros(); nTimeVerify += nTime2 - nTimeStart; + LogPrint("bench", " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs]\n", nInputs - 1, 0.001 * (nTime2 - nTimeStart), nInputs <= 1 ? 0 : 0.001 * (nTime2 - nTimeStart) / (nInputs-1), nTimeVerify * 0.000001); if (fJustCheck) return true; @@ -1840,6 +1845,9 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C ret = view.SetBestBlock(pindex->GetBlockHash()); assert(ret); + int64_t nTime3 = GetTimeMicros(); nTimeIndex += nTime3 - nTime2; + LogPrint("bench", " - Index writing: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeIndex * 0.000001); + // Watch for transactions paying to me BOOST_FOREACH(const CTransaction& tx, block.vtx) g_signals.SyncTransaction(tx, &block); @@ -1849,6 +1857,9 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C g_signals.UpdatedTransaction(hashPrevBestCoinBase); hashPrevBestCoinBase = block.vtx[0].GetHash(); + int64_t nTime4 = GetTimeMicros(); nTimeCallbacks += nTime4 - nTime3; + LogPrint("bench", " - Callbacks: %.2fms [%.2fs]\n", 0.001 * (nTime4 - nTime3), nTimeCallbacks * 0.000001); + return true; } @@ -1928,8 +1939,7 @@ bool static DisconnectTip(CValidationState &state) { return error("DisconnectTip() : DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString()); assert(view.Flush()); } - if (fBenchmark) - LogPrintf("- Disconnect: %.2fms\n", (GetTimeMicros() - nStart) * 0.001); + LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001); // Write the chain state to disk, if necessary. if (!WriteChainState(state)) return false; @@ -1953,16 +1963,25 @@ bool static DisconnectTip(CValidationState &state) { return true; } +static int64_t nTimeReadFromDisk = 0; +static int64_t nTimeConnectTotal = 0; +static int64_t nTimeFlush = 0; +static int64_t nTimeChainState = 0; +static int64_t nTimePostConnect = 0; + // Connect a new block to chainActive. bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew) { assert(pindexNew->pprev == chainActive.Tip()); mempool.check(pcoinsTip); // Read block from disk. + int64_t nTime1 = GetTimeMicros(); CBlock block; if (!ReadBlockFromDisk(block, pindexNew)) return state.Abort(_("Failed to read block")); // Apply the block atomically to the chain state. - int64_t nStart = GetTimeMicros(); + int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1; + int64_t nTime3; + LogPrint("bench", " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * 0.001, nTimeReadFromDisk * 0.000001); { CCoinsViewCache view(*pcoinsTip, true); CInv inv(MSG_BLOCK, pindexNew->GetBlockHash()); @@ -1972,13 +1991,17 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew) { return error("ConnectTip() : ConnectBlock %s failed", pindexNew->GetBlockHash().ToString()); } mapBlockSource.erase(inv.hash); + nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2; + LogPrint("bench", " - Connect total: %.2fms [%.2fs]\n", (nTime3 - nTime2) * 0.001, nTimeConnectTotal * 0.000001); assert(view.Flush()); } - if (fBenchmark) - LogPrintf("- Connect: %.2fms\n", (GetTimeMicros() - nStart) * 0.001); + int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3; + LogPrint("bench", " - Flush: %.2fms [%.2fs]\n", (nTime4 - nTime3) * 0.001, nTimeFlush * 0.000001); // Write the chain state to disk, if necessary. if (!WriteChainState(state)) return false; + int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4; + LogPrint("bench", " - Writing chainstate: %.2fms [%.2fs]\n", (nTime5 - nTime4) * 0.001, nTimeChainState * 0.000001); // Remove conflicting transactions from the mempool. list<CTransaction> txConflicted; mempool.removeForBlock(block.vtx, pindexNew->nHeight, txConflicted); @@ -1994,6 +2017,9 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew) { BOOST_FOREACH(const CTransaction &tx, block.vtx) { SyncWithWallets(tx, &block); } + int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; + LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001); + LogPrint("bench", "- Connect block: %.2fms [%.2fs]\n", (nTime6 - nTime1) * 0.001, nTimeTotal * 0.000001); return true; } @@ -2016,7 +2042,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 +2052,7 @@ static CBlockIndex* FindMostWorkChain() { setBlockIndexValid.erase(pindexFailed); pindexFailed = pindexFailed->pprev; } + setBlockIndexValid.erase(pindexTest); fInvalidAncestor = true; break; } @@ -2072,6 +2099,15 @@ static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMo return false; } } else { + // Delete all entries in setBlockIndexValid that are worse than our new current block. + // Note that we can't delete the current block itself, as we may need to return to it later in case a + // reorganization to a better block fails. + std::set<CBlockIndex*, CBlockIndexWorkComparator>::iterator it = setBlockIndexValid.begin(); + while (setBlockIndexValid.value_comp()(*it, chainActive.Tip())) { + setBlockIndexValid.erase(it++); + } + // Either the current tip or a successor of it we're working towards is left in setBlockIndexValid. + assert(!setBlockIndexValid.empty()); if (!pindexOldTip || chainActive.Tip()->nChainWork > pindexOldTip->nChainWork) { // We're in a better position than we were. Return temporarily to release the lock. break; diff --git a/src/main.h b/src/main.h index 5f231fa45b..48ec86f6a8 100644 --- a/src/main.h +++ b/src/main.h @@ -91,7 +91,6 @@ extern CWaitableCriticalSection csBestBlock; extern CConditionVariable cvBlockChange; extern bool fImporting; extern bool fReindex; -extern bool fBenchmark; extern int nScriptCheckThreads; extern bool fTxIndex; extern bool fIsBareMultisigStd; diff --git a/src/net.cpp b/src/net.cpp index b55cd72e86..62124514c8 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -456,7 +456,7 @@ CNode* FindNode(const CNetAddr& ip) return NULL; } -CNode* FindNode(std::string addrName) +CNode* FindNode(const std::string& addrName) { LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) @@ -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); @@ -1227,6 +1221,18 @@ void MapPort(bool) void ThreadDNSAddressSeed() { + // goal: only query DNS seeds if address need is acute + if ((addrman.size() > 0) && + (!GetBoolArg("-forcednsseed", false))) { + MilliSleep(11 * 1000); + + LOCK(cs_vNodes); + if (vNodes.size() >= 2) { + LogPrintf("P2P peers available. Skipped DNS seeding.\n"); + return; + } + } + const vector<CDNSSeedData> &vSeeds = Params().DNSSeeds(); int found = 0; @@ -1478,7 +1484,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; @@ -1642,14 +1648,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; } @@ -59,6 +59,7 @@ bool RecvLine(SOCKET hSocket, std::string& strLine); bool GetMyExternalIP(CNetAddr& ipRet); void AddressCurrentlyConnected(const CService& addr); CNode* FindNode(const CNetAddr& ip); +CNode* FindNode(const std::string& addrName); CNode* FindNode(const CService& ip); CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL); bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false); diff --git a/src/netbase.cpp b/src/netbase.cpp index e9f3515456..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> @@ -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) { @@ -404,20 +397,10 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe } } - // 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; @@ -1271,3 +1254,32 @@ bool CloseSocket(SOCKET& hSocket) 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 05221a5fde..7d83e35344 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -180,5 +180,7 @@ bool ConnectSocketByName(CService &addr, SOCKET& hSocketRet, const char *pszDest 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/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/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/optionsmodel.h b/src/qt/optionsmodel.h index 89c2ec7453..9699f6eea6 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -32,7 +32,6 @@ public: ProxyUse, // bool ProxyIP, // QString ProxyPort, // int - ProxySocksVersion, // int Fee, // qint64 DisplayUnit, // BitcoinUnits::Unit DisplayAddresses, // bool 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 ac1614efd0..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> diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 4825713b69..7acb0e8871 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -577,7 +577,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const case ConfirmedRole: return rec->status.countsForBalance; case FormattedAmountRole: - // Used for copy/export, so don't include separators + // 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 ad88d14a90..2124d3dd1c 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -5,11 +5,11 @@ #ifndef TRANSACTIONTABLEMODEL_H #define TRANSACTIONTABLEMODEL_H +#include "bitcoinunits.h" + #include <QAbstractTableModel> #include <QStringList> -#include "bitcoinunits.h" - class TransactionRecord; class TransactionTablePriv; class WalletModel; diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 5edeecf933..a0921453cc 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -85,6 +85,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getrawmempool", 0 }, { "estimatefee", 0 }, { "estimatepriority", 0 }, + { "prioritisetransaction", 1 }, + { "prioritisetransaction", 2 }, }; class CRPCConvertTable 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/rpcmining.cpp b/src/rpcmining.cpp index 6f72ea7404..edab427cbf 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -10,6 +10,7 @@ #include "main.h" #include "miner.h" #include "pow.h" +#include "core_io.h" #ifdef ENABLE_WALLET #include "db.h" #include "wallet.h" @@ -252,11 +253,30 @@ Value prioritisetransaction(const Array& params, bool fHelp) if (fHelp || params.size() != 3) throw runtime_error( "prioritisetransaction <txid> <priority delta> <fee delta>\n" - "Accepts the transaction into mined blocks at a higher (or lower) priority"); + "Accepts the transaction into mined blocks at a higher (or lower) priority\n" + "\nArguments:\n" + "1. \"txid\" (string, required) The transaction id.\n" + "2. priority delta (numeric, required) The priority to add or subtract.\n" + " The transaction selection algorithm considers the tx as it would have a higher priority.\n" + " (priority of a transaction is calculated: coinage * value_in_satoshis / txsize) \n" + "3. fee delta (numeric, required) The absolute fee value to add or subtract in bitcoin.\n" + " The fee is not actually paid, only the algorithm for selecting transactions into a block\n" + " considers the transaction as it would have paid a higher (or lower) fee.\n" + "\nResult\n" + "true (boolean) Returns true\n" + "\nExamples:\n" + + HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 0.00010000") + + HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 0.00010000") + ); uint256 hash; hash.SetHex(params[0].get_str()); - mempool.PrioritiseTransaction(hash, params[0].get_str(), params[1].get_real(), params[2].get_int64()); + + int64_t nAmount = 0; + if (params[2].get_real() != 0.0) + nAmount = AmountFromValue(params[2]); + + mempool.PrioritiseTransaction(hash, params[0].get_str(), params[1].get_real(), nAmount); return true; } @@ -453,9 +473,7 @@ Value getblocktemplate(const Array& params, bool fHelp) Object entry; - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << tx; - entry.push_back(Pair("data", HexStr(ssTx.begin(), ssTx.end()))); + entry.push_back(Pair("data", EncodeHexTx(tx))); entry.push_back(Pair("hash", txHash.GetHex())); diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index cff795bdf4..bd992397b8 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -27,6 +27,19 @@ using namespace boost::assign; using namespace json_spirit; using namespace std; +/** + * @note Do not add or change anything in the information returned by this + * method. `getinfo` exists for backwards-compatibilty only. It combines + * information from wildly different sources in the program, which is a mess, + * and is thus planned to be deprecated eventually. + * + * Based on the source of the information, new information should be added to: + * - `getblockchaininfo`, + * - `getnetworkinfo` or + * - `getwalletinfo` + * + * Or alternatively, create a specific query method for the information. + **/ Value getinfo(const Array& params, bool fHelp) { if (fHelp || params.size() != 0) diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 1efe38e830..763615120a 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -5,6 +5,7 @@ #include "base58.h" #include "core.h" +#include "core_io.h" #include "init.h" #include "keystore.h" #include "main.h" @@ -36,8 +37,7 @@ void ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out, bool fIncludeH if (fIncludeHex) out.push_back(Pair("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); - if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) - { + if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { out.push_back(Pair("type", GetTxnOutputType(type))); return; } @@ -57,13 +57,11 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) entry.push_back(Pair("version", tx.nVersion)); entry.push_back(Pair("locktime", (int64_t)tx.nLockTime)); Array vin; - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { + BOOST_FOREACH(const CTxIn& txin, tx.vin) { Object in; if (tx.IsCoinBase()) in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); - else - { + else { in.push_back(Pair("txid", txin.prevout.hash.GetHex())); in.push_back(Pair("vout", (int64_t)txin.prevout.n)); Object o; @@ -76,8 +74,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) } entry.push_back(Pair("vin", vin)); Array vout; - for (unsigned int i = 0; i < tx.vout.size(); i++) - { + for (unsigned int i = 0; i < tx.vout.size(); i++) { const CTxOut& txout = tx.vout[i]; Object out; out.push_back(Pair("value", ValueFromAmount(txout.nValue))); @@ -89,15 +86,12 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) } entry.push_back(Pair("vout", vout)); - if (hashBlock != 0) - { + if (hashBlock != 0) { entry.push_back(Pair("blockhash", hashBlock.GetHex())); map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock); - if (mi != mapBlockIndex.end() && (*mi).second) - { + if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; - if (chainActive.Contains(pindex)) - { + if (chainActive.Contains(pindex)) { entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime())); @@ -182,9 +176,7 @@ Value getrawtransaction(const Array& params, bool fHelp) if (!GetTransaction(hash, tx, hashBlock, true)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << tx; - string strHex = HexStr(ssTx.begin(), ssTx.end()); + string strHex = EncodeHexTx(tx); if (!fVerbose) return strHex; @@ -245,11 +237,9 @@ Value listunspent(const Array& params, bool fHelp) nMaxDepth = params[1].get_int(); set<CBitcoinAddress> setAddress; - if (params.size() > 2) - { + if (params.size() > 2) { Array inputs = params[2].get_array(); - BOOST_FOREACH(Value& input, inputs) - { + BOOST_FOREACH(Value& input, inputs) { CBitcoinAddress address(input.get_str()); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+input.get_str()); @@ -263,13 +253,11 @@ Value listunspent(const Array& params, bool fHelp) vector<COutput> vecOutputs; assert(pwalletMain != NULL); pwalletMain->AvailableCoins(vecOutputs, false); - BOOST_FOREACH(const COutput& out, vecOutputs) - { + BOOST_FOREACH(const COutput& out, vecOutputs) { if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) continue; - if (setAddress.size()) - { + if (setAddress.size()) { CTxDestination address; if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) continue; @@ -284,18 +272,15 @@ Value listunspent(const Array& params, bool fHelp) entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); entry.push_back(Pair("vout", out.i)); CTxDestination address; - if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) - { + if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { entry.push_back(Pair("address", CBitcoinAddress(address).ToString())); if (pwalletMain->mapAddressBook.count(address)) entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name)); } entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end()))); - if (pk.IsPayToScriptHash()) - { + if (pk.IsPayToScriptHash()) { CTxDestination address; - if (ExtractDestination(pk, address)) - { + if (ExtractDestination(pk, address)) { const CScriptID& hash = boost::get<const CScriptID&>(address); CScript redeemScript; if (pwalletMain->GetCScript(hash, redeemScript)) @@ -352,8 +337,7 @@ Value createrawtransaction(const Array& params, bool fHelp) CMutableTransaction rawTx; - BOOST_FOREACH(const Value& input, inputs) - { + BOOST_FOREACH(const Value& input, inputs) { const Object& o = input.get_obj(); uint256 txid = ParseHashO(o, "txid"); @@ -370,8 +354,7 @@ Value createrawtransaction(const Array& params, bool fHelp) } set<CBitcoinAddress> setAddress; - BOOST_FOREACH(const Pair& s, sendTo) - { + BOOST_FOREACH(const Pair& s, sendTo) { CBitcoinAddress address(s.name_); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+s.name_); @@ -388,9 +371,7 @@ Value createrawtransaction(const Array& params, bool fHelp) rawTx.vout.push_back(out); } - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << rawTx; - return HexStr(ss.begin(), ss.end()); + return EncodeHexTx(rawTx); } Value decoderawtransaction(const Array& params, bool fHelp) @@ -444,15 +425,12 @@ Value decoderawtransaction(const Array& params, bool fHelp) + HelpExampleRpc("decoderawtransaction", "\"hexstring\"") ); - vector<unsigned char> txData(ParseHexV(params[0], "argument")); - CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + RPCTypeCheck(params, list_of(str_type)); + CTransaction tx; - try { - ssData >> tx; - } - catch (std::exception &e) { + + if (!DecodeHexTx(tx, params[0].get_str())) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } Object result; TxToJSON(tx, 0, result); @@ -556,8 +534,7 @@ Value signrawtransaction(const Array& params, bool fHelp) vector<unsigned char> txData(ParseHexV(params[0], "argument 1")); CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); vector<CMutableTransaction> txVariants; - while (!ssData.empty()) - { + while (!ssData.empty()) { try { CMutableTransaction tx; ssData >> tx; @@ -596,12 +573,10 @@ Value signrawtransaction(const Array& params, bool fHelp) bool fGivenKeys = false; CBasicKeyStore tempKeystore; - if (params.size() > 2 && params[2].type() != null_type) - { + if (params.size() > 2 && params[2].type() != null_type) { fGivenKeys = true; Array keys = params[2].get_array(); - BOOST_FOREACH(Value k, keys) - { + BOOST_FOREACH(Value k, keys) { CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(k.get_str()); if (!fGood) @@ -616,11 +591,9 @@ Value signrawtransaction(const Array& params, bool fHelp) #endif // Add previous txouts given in the RPC call: - if (params.size() > 1 && params[1].type() != null_type) - { + if (params.size() > 1 && params[1].type() != null_type) { Array prevTxs = params[1].get_array(); - BOOST_FOREACH(Value& p, prevTxs) - { + BOOST_FOREACH(Value& p, prevTxs) { if (p.type() != obj_type) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}"); @@ -655,12 +628,10 @@ Value signrawtransaction(const Array& params, bool fHelp) // if redeemScript given and not using the local wallet (private keys // given), add redeemScript to the tempKeystore so it can be signed: - if (fGivenKeys && scriptPubKey.IsPayToScriptHash()) - { + if (fGivenKeys && scriptPubKey.IsPayToScriptHash()) { RPCTypeCheck(prevOut, map_list_of("txid", str_type)("vout", int_type)("scriptPubKey", str_type)("redeemScript",str_type)); Value v = find_value(prevOut, "redeemScript"); - if (!(v == Value::null)) - { + if (!(v == Value::null)) { vector<unsigned char> rsData(ParseHexV(v, "redeemScript")); CScript redeemScript(rsData.begin(), rsData.end()); tempKeystore.AddCScript(redeemScript); @@ -676,8 +647,7 @@ Value signrawtransaction(const Array& params, bool fHelp) #endif int nHashType = SIGHASH_ALL; - if (params.size() > 3 && params[3].type() != null_type) - { + if (params.size() > 3 && params[3].type() != null_type) { static map<string, int> mapSigHashValues = boost::assign::map_list_of (string("ALL"), int(SIGHASH_ALL)) @@ -697,12 +667,10 @@ Value signrawtransaction(const Array& params, bool fHelp) bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); // Sign what we can: - for (unsigned int i = 0; i < mergedTx.vin.size(); i++) - { + for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { CTxIn& txin = mergedTx.vin[i]; CCoins coins; - if (!view.GetCoins(txin.prevout.hash, coins) || !coins.IsAvailable(txin.prevout.n)) - { + if (!view.GetCoins(txin.prevout.hash, coins) || !coins.IsAvailable(txin.prevout.n)) { fComplete = false; continue; } @@ -714,8 +682,7 @@ Value signrawtransaction(const Array& params, bool fHelp) SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); // ... and merge in other signatures: - BOOST_FOREACH(const CMutableTransaction& txv, txVariants) - { + BOOST_FOREACH(const CMutableTransaction& txv, txVariants) { txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); } if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, STANDARD_SCRIPT_VERIFY_FLAGS, 0)) @@ -723,9 +690,7 @@ Value signrawtransaction(const Array& params, bool fHelp) } Object result; - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << mergedTx; - result.push_back(Pair("hex", HexStr(ssTx.begin(), ssTx.end()))); + result.push_back(Pair("hex", EncodeHexTx(mergedTx))); result.push_back(Pair("complete", fComplete)); return result; @@ -754,25 +719,18 @@ Value sendrawtransaction(const Array& params, bool fHelp) + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") ); + RPCTypeCheck(params, list_of(str_type)(bool_type)); // parse hex string from parameter - vector<unsigned char> txData(ParseHexV(params[0], "parameter")); - CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); CTransaction tx; + if (!DecodeHexTx(tx, params[0].get_str())) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + uint256 hashTx = tx.GetHash(); bool fOverrideFees = false; if (params.size() > 1) fOverrideFees = params[1].get_bool(); - // deserialize binary data stream - try { - ssData >> tx; - } - catch (std::exception &e) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } - const uint256 &hashTx = tx.GetHash(); - CCoinsViewCache &view = *pcoinsTip; CCoins existingCoins; bool fHaveMempool = mempool.exists(hashTx); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 5b83fe900e..667ca33ce1 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" +#include "core_io.h" #include "rpcserver.h" #include "init.h" #include "net.h" @@ -1550,9 +1551,7 @@ Value gettransaction(const Array& params, bool fHelp) ListTransactions(wtx, "*", 0, false, details, filter); entry.push_back(Pair("details", details)); - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << static_cast<CTransaction>(wtx); - string strHex = HexStr(ssTx.begin(), ssTx.end()); + string strHex = EncodeHexTx(static_cast<CTransaction>(wtx)); entry.push_back(Pair("hex", strHex)); return entry; diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index cba582e941..5e35875a8e 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -10,6 +10,7 @@ #include "key.h" #include "keystore.h" #include "main.h" +#include "core_io.h" #include <fstream> #include <stdint.h> @@ -36,76 +37,6 @@ extern uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo static const unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC; -CScript -ParseScript(string s) -{ - CScript result; - - static map<string, opcodetype> mapOpNames; - - if (mapOpNames.size() == 0) - { - for (int op = 0; op <= OP_NOP10; op++) - { - // Allow OP_RESERVED to get into mapOpNames - if (op < OP_NOP && op != OP_RESERVED) - continue; - - const char* name = GetOpName((opcodetype)op); - if (strcmp(name, "OP_UNKNOWN") == 0) - continue; - string strName(name); - mapOpNames[strName] = (opcodetype)op; - // Convenience: OP_ADD and just ADD are both recognized: - replace_first(strName, "OP_", ""); - mapOpNames[strName] = (opcodetype)op; - } - } - - vector<string> words; - split(words, s, is_any_of(" \t\n"), token_compress_on); - - BOOST_FOREACH(string w, words) - { - if (w.size() == 0) - { - // Empty string, ignore. (boost::split given '' will return one word) - } - else if (all(w, is_digit()) || - (starts_with(w, "-") && all(string(w.begin()+1, w.end()), is_digit()))) - { - // Number - int64_t n = atoi64(w); - result << n; - } - else if (starts_with(w, "0x") && IsHex(string(w.begin()+2, w.end()))) - { - // Raw hex data, inserted NOT pushed onto stack: - std::vector<unsigned char> raw = ParseHex(string(w.begin()+2, w.end())); - result.insert(result.end(), raw.begin(), raw.end()); - } - else if (w.size() >= 2 && starts_with(w, "'") && ends_with(w, "'")) - { - // Single-quoted string, pushed as data. NOTE: this is poor-man's - // parsing, spaces/tabs/newlines in single-quoted strings won't work. - std::vector<unsigned char> value(w.begin()+1, w.end()-1); - result << value; - } - else if (mapOpNames.count(w)) - { - // opcode, e.g. OP_ADD or ADD: - result << mapOpNames[w]; - } - else - { - BOOST_ERROR("Parse error: " << s); - return CScript(); - } - } - - return result; -} - Array read_json(const std::string& jsondata) { diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 238033f407..03919e7c7d 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -9,6 +9,7 @@ #include "keystore.h" #include "main.h" #include "script.h" +#include "core_io.h" #include <map> #include <string> @@ -24,7 +25,6 @@ using namespace boost::algorithm; // In script_tests.cpp extern Array read_json(const std::string& jsondata); -extern CScript ParseScript(string s); unsigned int ParseFlags(string strFlags){ unsigned int flags = 0; 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/univalue/univalue.cpp b/src/univalue/univalue.cpp new file mode 100644 index 0000000000..afc208bffb --- /dev/null +++ b/src/univalue/univalue.cpp @@ -0,0 +1,211 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <stdint.h> +#include <ctype.h> +#include <sstream> +#include "univalue.h" + +using namespace std; + +static const UniValue nullValue; + +void UniValue::clear() +{ + typ = VNULL; + val.clear(); + keys.clear(); + values.clear(); +} + +bool UniValue::setNull() +{ + clear(); + return true; +} + +bool UniValue::setBool(bool val_) +{ + clear(); + typ = VBOOL; + if (val_) + val = "1"; + return true; +} + +static bool validNumStr(const string& s) +{ + string tokenVal; + unsigned int consumed; + enum jtokentype tt = getJsonToken(tokenVal, consumed, s.c_str()); + return (tt == JTOK_NUMBER); +} + +bool UniValue::setNumStr(const string& val_) +{ + if (!validNumStr(val)) + return false; + + clear(); + typ = VNUM; + val = val_; + return true; +} + +bool UniValue::setInt(uint64_t val) +{ + string s; + ostringstream oss; + + oss << val; + + return setNumStr(oss.str()); +} + +bool UniValue::setInt(int64_t val) +{ + string s; + ostringstream oss; + + oss << val; + + return setNumStr(oss.str()); +} + +bool UniValue::setFloat(double val) +{ + string s; + ostringstream oss; + + oss << val; + + return setNumStr(oss.str()); +} + +bool UniValue::setStr(const string& val_) +{ + clear(); + typ = VSTR; + val = val_; + return true; +} + +bool UniValue::setArray() +{ + clear(); + typ = VARR; + return true; +} + +bool UniValue::setObject() +{ + clear(); + typ = VOBJ; + return true; +} + +bool UniValue::push_back(const UniValue& val) +{ + if (typ != VARR) + return false; + + values.push_back(val); + return true; +} + +bool UniValue::push_backV(const std::vector<UniValue>& vec) +{ + if (typ != VARR) + return false; + + values.insert(values.end(), vec.begin(), vec.end()); + + return true; +} + +bool UniValue::pushKV(const std::string& key, const UniValue& val) +{ + if (typ != VOBJ) + return false; + + keys.push_back(key); + values.push_back(val); + return true; +} + +bool UniValue::pushKVs(const UniValue& obj) +{ + if (typ != VOBJ || obj.typ != VOBJ) + return false; + + for (unsigned int i = 0; i < obj.keys.size(); i++) { + keys.push_back(obj.keys[i]); + values.push_back(obj.values[i]); + } + + return true; +} + +int UniValue::findKey(const std::string& key) const +{ + for (unsigned int i = 0; i < keys.size(); i++) { + if (keys[i] == key) + return (int) i; + } + + return -1; +} + +bool UniValue::checkObject(const std::map<std::string,UniValue::VType>& t) +{ + for (std::map<std::string,UniValue::VType>::const_iterator it = t.begin(); + it != t.end(); it++) { + int idx = findKey(it->first); + if (idx < 0) + return false; + + if (values[idx].getType() != it->second) + return false; + } + + return true; +} + +const UniValue& UniValue::operator[](const std::string& key) const +{ + if (typ != VOBJ) + return nullValue; + + int index = findKey(key); + if (index < 0) + return nullValue; + + return values[index]; +} + +const UniValue& UniValue::operator[](unsigned int index) const +{ + if (typ != VOBJ && typ != VARR) + return nullValue; + if (index >= values.size()) + return nullValue; + + return values[index]; +} + +const char *uvTypeName(UniValue::VType t) +{ + switch (t) { + case UniValue::VNULL: return "null"; + case UniValue::VBOOL: return "bool"; + case UniValue::VOBJ: return "object"; + case UniValue::VARR: return "array"; + case UniValue::VSTR: return "string"; + case UniValue::VNUM: return "number"; + } + + // not reached + return NULL; +} + diff --git a/src/univalue/univalue.h b/src/univalue/univalue.h new file mode 100644 index 0000000000..0a7bf3cceb --- /dev/null +++ b/src/univalue/univalue.h @@ -0,0 +1,155 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef __UNIVALUE_H__ +#define __UNIVALUE_H__ + +#include <stdint.h> +#include <string> +#include <vector> +#include <map> +#include <cassert> + +class UniValue { +public: + enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, }; + + UniValue() { typ = VNULL; } + UniValue(UniValue::VType initialType, const std::string& initialStr = "") { + typ = initialType; + val = initialStr; + } + UniValue(uint64_t val_) { + setInt(val_); + } + UniValue(int64_t val_) { + setInt(val_); + } + UniValue(int val_) { + setInt(val_); + } + UniValue(double val_) { + setFloat(val_); + } + UniValue(const std::string& val_) { + setStr(val_); + } + UniValue(const char *val_) { + std::string s(val_); + setStr(s); + } + ~UniValue() {} + + void clear(); + + bool setNull(); + bool setBool(bool val); + bool setNumStr(const std::string& val); + bool setInt(uint64_t val); + bool setInt(int64_t val); + bool setInt(int val) { return setInt((int64_t)val); } + bool setFloat(double val); + bool setStr(const std::string& val); + bool setArray(); + bool setObject(); + + enum VType getType() const { return typ; } + std::string getValStr() const { return val; } + bool empty() const { return (values.size() == 0); } + + size_t count() const { return values.size(); } + + bool getBool() const { return isTrue(); } + bool checkObject(const std::map<std::string,UniValue::VType>& memberTypes); + const UniValue& operator[](const std::string& key) const; + const UniValue& operator[](unsigned int index) const; + bool exists(const std::string& key) const { return (findKey(key) >= 0); } + + bool isNull() const { return (typ == VNULL); } + bool isTrue() const { return (typ == VBOOL) && (val == "1"); } + bool isFalse() const { return (!isTrue()); } + bool isBool() const { return (typ == VBOOL); } + bool isStr() const { return (typ == VSTR); } + bool isNum() const { return (typ == VNUM); } + bool isArray() const { return (typ == VARR); } + bool isObject() const { return (typ == VOBJ); } + + bool push_back(const UniValue& val); + bool push_back(const std::string& val_) { + UniValue tmpVal(VSTR, val_); + return push_back(tmpVal); + } + bool push_back(const char *val_) { + std::string s(val_); + return push_back(s); + } + bool push_backV(const std::vector<UniValue>& vec); + + bool pushKV(const std::string& key, const UniValue& val); + bool pushKV(const std::string& key, const std::string& val) { + UniValue tmpVal(VSTR, val); + return pushKV(key, tmpVal); + } + bool pushKV(const std::string& key, const char *val_) { + std::string val(val_); + return pushKV(key, val); + } + bool pushKV(const std::string& key, int64_t val) { + UniValue tmpVal(val); + return pushKV(key, tmpVal); + } + bool pushKV(const std::string& key, uint64_t val) { + UniValue tmpVal(val); + return pushKV(key, tmpVal); + } + bool pushKV(const std::string& key, int val) { + UniValue tmpVal((int64_t)val); + return pushKV(key, tmpVal); + } + bool pushKV(const std::string& key, double val) { + UniValue tmpVal(val); + return pushKV(key, tmpVal); + } + bool pushKVs(const UniValue& obj); + + std::string write(unsigned int prettyIndent = 0, + unsigned int indentLevel = 0) const; + + bool read(const char *raw); + bool read(const std::string& rawStr) { + return read(rawStr.c_str()); + } + +private: + UniValue::VType typ; + std::string val; // numbers are stored as C++ strings + std::vector<std::string> keys; + std::vector<UniValue> values; + + int findKey(const std::string& key) const; + void writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; + void writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; +}; + +enum jtokentype { + JTOK_ERR = -1, + JTOK_NONE = 0, // eof + JTOK_OBJ_OPEN, + JTOK_OBJ_CLOSE, + JTOK_ARR_OPEN, + JTOK_ARR_CLOSE, + JTOK_COLON, + JTOK_COMMA, + JTOK_KW_NULL, + JTOK_KW_TRUE, + JTOK_KW_FALSE, + JTOK_NUMBER, + JTOK_STRING, +}; + +extern enum jtokentype getJsonToken(std::string& tokenVal, + unsigned int& consumed, const char *raw); +extern const char *uvTypeName(UniValue::VType t); + +#endif // __UNIVALUE_H__ diff --git a/src/univalue/univalue_read.cpp b/src/univalue/univalue_read.cpp new file mode 100644 index 0000000000..405be3e81f --- /dev/null +++ b/src/univalue/univalue_read.cpp @@ -0,0 +1,390 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <string.h> +#include <vector> +#include <stdio.h> +#include "univalue.h" + +using namespace std; + +// convert hexadecimal string to unsigned integer +static const char *hatoui(const char *first, const char *last, + unsigned int& out) +{ + unsigned int result = 0; + for (; first != last; ++first) + { + int digit; + if (isdigit(*first)) + digit = *first - '0'; + + else if (*first >= 'a' && *first <= 'f') + digit = *first - 'a' + 10; + + else if (*first >= 'A' && *first <= 'F') + digit = *first - 'A' + 10; + + else + break; + + result = 16 * result + digit; + } + out = result; + + return first; +} + +enum jtokentype getJsonToken(string& tokenVal, unsigned int& consumed, + const char *raw) +{ + tokenVal.clear(); + consumed = 0; + + const char *rawStart = raw; + + while ((*raw) && (isspace(*raw))) // skip whitespace + raw++; + + switch (*raw) { + + case 0: + return JTOK_NONE; + + case '{': + raw++; + consumed = (raw - rawStart); + return JTOK_OBJ_OPEN; + case '}': + raw++; + consumed = (raw - rawStart); + return JTOK_OBJ_CLOSE; + case '[': + raw++; + consumed = (raw - rawStart); + return JTOK_ARR_OPEN; + case ']': + raw++; + consumed = (raw - rawStart); + return JTOK_ARR_CLOSE; + + case ':': + raw++; + consumed = (raw - rawStart); + return JTOK_COLON; + case ',': + raw++; + consumed = (raw - rawStart); + return JTOK_COMMA; + + case 'n': + case 't': + case 'f': + if (!strncmp(raw, "null", 4)) { + raw += 4; + consumed = (raw - rawStart); + return JTOK_KW_NULL; + } else if (!strncmp(raw, "true", 4)) { + raw += 4; + consumed = (raw - rawStart); + return JTOK_KW_TRUE; + } else if (!strncmp(raw, "false", 5)) { + raw += 5; + consumed = (raw - rawStart); + return JTOK_KW_FALSE; + } else + return JTOK_ERR; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + // part 1: int + string numStr; + + const char *first = raw; + + const char *firstDigit = first; + if (!isdigit(*firstDigit)) + firstDigit++; + if ((*firstDigit == '0') && isdigit(firstDigit[1])) + return JTOK_ERR; + + numStr += *raw; // copy first char + raw++; + + if ((*first == '-') && (!isdigit(*raw))) + return JTOK_ERR; + + while ((*raw) && isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + + // part 2: frac + if (*raw == '.') { + numStr += *raw; // copy . + raw++; + + if (!isdigit(*raw)) + return JTOK_ERR; + while ((*raw) && isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + } + + // part 3: exp + if (*raw == 'e' || *raw == 'E') { + numStr += *raw; // copy E + raw++; + + if (*raw == '-' || *raw == '+') { // copy +/- + numStr += *raw; + raw++; + } + + if (!isdigit(*raw)) + return JTOK_ERR; + while ((*raw) && isdigit(*raw)) { // copy digits + numStr += *raw; + raw++; + } + } + + tokenVal = numStr; + consumed = (raw - rawStart); + return JTOK_NUMBER; + } + + case '"': { + raw++; // skip " + + string valStr; + + while (*raw) { + if (*raw < 0x20) + return JTOK_ERR; + + else if (*raw == '\\') { + raw++; // skip backslash + + switch (*raw) { + case '"': valStr += "\""; break; + case '\\': valStr += "\\"; break; + case '/': valStr += "/"; break; + case 'b': valStr += "\b"; break; + case 'f': valStr += "\f"; break; + case 'n': valStr += "\n"; break; + case 'r': valStr += "\r"; break; + case 't': valStr += "\t"; break; + + case 'u': { + char buf[4] = {0,0,0,0}; + char *last = &buf[0]; + unsigned int codepoint; + if (hatoui(raw + 1, raw + 1 + 4, codepoint) != + raw + 1 + 4) + return JTOK_ERR; + + if (codepoint <= 0x7f) + *last = (char)codepoint; + else if (codepoint <= 0x7FF) { + *last++ = (char)(0xC0 | (codepoint >> 6)); + *last = (char)(0x80 | (codepoint & 0x3F)); + } else if (codepoint <= 0xFFFF) { + *last++ = (char)(0xE0 | (codepoint >> 12)); + *last++ = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + *last = (char)(0x80 | (codepoint & 0x3F)); + } + + valStr += buf; + raw += 4; + break; + } + default: + return JTOK_ERR; + + } + + raw++; // skip esc'd char + } + + else if (*raw == '"') { + raw++; // skip " + break; // stop scanning + } + + else { + valStr += *raw; + raw++; + } + } + + tokenVal = valStr; + consumed = (raw - rawStart); + return JTOK_STRING; + } + + default: + return JTOK_ERR; + } +} + +bool UniValue::read(const char *raw) +{ + clear(); + + bool expectName = false; + bool expectColon = false; + vector<UniValue*> stack; + + enum jtokentype tok = JTOK_NONE; + enum jtokentype last_tok = JTOK_NONE; + while (1) { + last_tok = tok; + + string tokenVal; + unsigned int consumed; + tok = getJsonToken(tokenVal, consumed, raw); + if (tok == JTOK_NONE || tok == JTOK_ERR) + break; + raw += consumed; + + switch (tok) { + + case JTOK_OBJ_OPEN: + case JTOK_ARR_OPEN: { + VType utyp = (tok == JTOK_OBJ_OPEN ? VOBJ : VARR); + if (!stack.size()) { + if (utyp == VOBJ) + setObject(); + else + setArray(); + stack.push_back(this); + } else { + UniValue tmpVal(utyp); + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + + UniValue *newTop = &(top->values.back()); + stack.push_back(newTop); + } + + if (utyp == VOBJ) + expectName = true; + break; + } + + case JTOK_OBJ_CLOSE: + case JTOK_ARR_CLOSE: { + if (!stack.size() || expectColon || (last_tok == JTOK_COMMA)) + return false; + + VType utyp = (tok == JTOK_OBJ_CLOSE ? VOBJ : VARR); + UniValue *top = stack.back(); + if (utyp != top->getType()) + return false; + + stack.pop_back(); + expectName = false; + break; + } + + case JTOK_COLON: { + if (!stack.size() || expectName || !expectColon) + return false; + + UniValue *top = stack.back(); + if (top->getType() != VOBJ) + return false; + + expectColon = false; + break; + } + + case JTOK_COMMA: { + if (!stack.size() || expectName || expectColon || + (last_tok == JTOK_COMMA) || (last_tok == JTOK_ARR_OPEN)) + return false; + + UniValue *top = stack.back(); + if (top->getType() == VOBJ) + expectName = true; + break; + } + + case JTOK_KW_NULL: + case JTOK_KW_TRUE: + case JTOK_KW_FALSE: { + if (!stack.size() || expectName || expectColon) + return false; + + UniValue tmpVal; + switch (tok) { + case JTOK_KW_NULL: + // do nothing more + break; + case JTOK_KW_TRUE: + tmpVal.setBool(true); + break; + case JTOK_KW_FALSE: + tmpVal.setBool(false); + break; + default: /* impossible */ break; + } + + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + + break; + } + + case JTOK_NUMBER: { + if (!stack.size() || expectName || expectColon) + return false; + + UniValue tmpVal(VNUM, tokenVal); + UniValue *top = stack.back(); + top->values.push_back(tmpVal); + + break; + } + + case JTOK_STRING: { + if (!stack.size()) + return false; + + UniValue *top = stack.back(); + + if (expectName) { + top->keys.push_back(tokenVal); + expectName = false; + expectColon = true; + } else { + UniValue tmpVal(VSTR, tokenVal); + top->values.push_back(tmpVal); + } + + break; + } + + default: + return false; + } + } + + if (stack.size() != 0) + return false; + + return true; +} + diff --git a/src/univalue/univalue_write.cpp b/src/univalue/univalue_write.cpp new file mode 100644 index 0000000000..1818f5c6f9 --- /dev/null +++ b/src/univalue/univalue_write.cpp @@ -0,0 +1,145 @@ +// Copyright 2014 BitPay Inc. +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <ctype.h> +#include <stdio.h> +#include "univalue.h" + +// TODO: Using UTF8 + +using namespace std; + +static bool initEscapes; +static const char *escapes[256]; + +static void initJsonEscape() +{ + escapes['"'] = "\\\""; + escapes['\\'] = "\\\\"; + escapes['/'] = "\\/"; + escapes['\b'] = "\\b"; + escapes['\f'] = "\\f"; + escapes['\n'] = "\\n"; + escapes['\r'] = "\\r"; + escapes['\t'] = "\\t"; + + initEscapes = true; +} + +static string json_escape(const string& inS) +{ + if (!initEscapes) + initJsonEscape(); + + string outS; + outS.reserve(inS.size() * 2); + + for (unsigned int i = 0; i < inS.size(); i++) { + unsigned char ch = inS[i]; + const char *escStr = escapes[ch]; + + if (escStr) + outS += escStr; + + else if (isprint(ch)) + outS += ch; + + else { + char tmpesc[16]; + sprintf(tmpesc, "\\u%04x", ch); + outS += tmpesc; + } + } + + return outS; +} + +string UniValue::write(unsigned int prettyIndent, + unsigned int indentLevel) const +{ + string s; + s.reserve(1024); + + unsigned int modIndent = indentLevel; + if (modIndent == 0) + modIndent = 1; + + switch (typ) { + case VNULL: + s += "null"; + break; + case VOBJ: + writeObject(prettyIndent, modIndent, s); + break; + case VARR: + writeArray(prettyIndent, modIndent, s); + break; + case VSTR: + s += "\"" + json_escape(val) + "\""; + break; + case VNUM: + s += val; + break; + case VBOOL: + s += (val == "1" ? "true" : "false"); + break; + } + + return s; +} + +static string spaceStr; + +static string indentStr(unsigned int prettyIndent, unsigned int indentLevel) +{ + unsigned int spaces = prettyIndent * indentLevel; + while (spaceStr.size() < spaces) + spaceStr += " "; + + return spaceStr.substr(0, spaces); +} + +void UniValue::writeArray(unsigned int prettyIndent, unsigned int indentLevel, string& s) const +{ + s += "["; + if (prettyIndent) + s += "\n"; + + for (unsigned int i = 0; i < values.size(); i++) { + if (prettyIndent) + s += indentStr(prettyIndent, indentLevel); + s += values[i].write(prettyIndent, indentLevel + 1); + if (i != (values.size() - 1)) + s += ", "; + if (prettyIndent) + s += "\n"; + } + + if (prettyIndent) + s += indentStr(prettyIndent, indentLevel - 1); + s += "]"; +} + +void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel, string& s) const +{ + s += "{"; + if (prettyIndent) + s += "\n"; + + for (unsigned int i = 0; i < keys.size(); i++) { + if (prettyIndent) + s += indentStr(prettyIndent, indentLevel); + s += "\"" + json_escape(keys[i]) + "\": "; + s += values[i].write(prettyIndent, indentLevel + 1); + if (i != (values.size() - 1)) + s += ","; + if (prettyIndent) + s += "\n"; + } + + if (prettyIndent) + s += indentStr(prettyIndent, indentLevel - 1); + s += "}"; +} + 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 |