diff options
83 files changed, 926 insertions, 375 deletions
diff --git a/contrib/README.md b/contrib/README.md index dae975e9ef..7d4b91e887 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -16,6 +16,9 @@ Repository Tools Specific tools for developers working on this repository. Contains the script `github-merge.sh` for merging github pull requests securely and signing them using GPG. +### [Verify-Commits](/contrib/verify-commits) ### +Tool to verify that every merge commit was signed by a developer using the above `github-merge.sh` script. + ### [Linearize](/contrib/linearize) ### Construct a linear, no-fork, best version of the blockchain. diff --git a/contrib/bitrpc/bitrpc.py b/contrib/bitrpc/bitrpc.py index 02577b1b6a..c3ce9d7936 100644 --- a/contrib/bitrpc/bitrpc.py +++ b/contrib/bitrpc/bitrpc.py @@ -20,9 +20,9 @@ if cmd == "backupwallet": try: path = raw_input("Enter destination path/filename: ") print access.backupwallet(path) - except: - print "\n---An error occurred---\n" - + except Exception as inst: + print inst + elif cmd == "encryptwallet": try: pwd = getpass.getpass(prompt="Enter passphrase: ") @@ -32,29 +32,29 @@ elif cmd == "encryptwallet": print "\n---Wallet encrypted. Server stopping, restart to run with encrypted wallet---\n" else: print "\n---Passphrases do not match---\n" - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getaccount": try: addr = raw_input("Enter a Bitcoin address: ") print access.getaccount(addr) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getaccountaddress": try: acct = raw_input("Enter an account name: ") print access.getaccountaddress(acct) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getaddressesbyaccount": try: acct = raw_input("Enter an account name: ") print access.getaddressesbyaccount(acct) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getbalance": try: @@ -64,57 +64,57 @@ elif cmd == "getbalance": print access.getbalance(acct, mc) except: print access.getbalance() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getblockbycount": try: height = raw_input("Height: ") print access.getblockbycount(height) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getblockcount": try: print access.getblockcount() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getblocknumber": try: print access.getblocknumber() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getconnectioncount": try: print access.getconnectioncount() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getdifficulty": try: print access.getdifficulty() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getgenerate": try: print access.getgenerate() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "gethashespersec": try: print access.gethashespersec() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getinfo": try: print access.getinfo() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getnewaddress": try: @@ -123,8 +123,8 @@ elif cmd == "getnewaddress": print access.getnewaddress(acct) except: print access.getnewaddress() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getreceivedbyaccount": try: @@ -134,8 +134,8 @@ elif cmd == "getreceivedbyaccount": print access.getreceivedbyaccount(acct, mc) except: print access.getreceivedbyaccount() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getreceivedbyaddress": try: @@ -145,15 +145,15 @@ elif cmd == "getreceivedbyaddress": print access.getreceivedbyaddress(addr, mc) except: print access.getreceivedbyaddress() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "gettransaction": try: txid = raw_input("Enter a transaction ID: ") print access.gettransaction(txid) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "getwork": try: @@ -162,8 +162,8 @@ elif cmd == "getwork": print access.gettransaction(data) except: print access.gettransaction() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "help": try: @@ -172,8 +172,8 @@ elif cmd == "help": print access.help(cmd) except: print access.help() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "listaccounts": try: @@ -182,8 +182,8 @@ elif cmd == "listaccounts": print access.listaccounts(mc) except: print access.listaccounts() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "listreceivedbyaccount": try: @@ -193,8 +193,8 @@ elif cmd == "listreceivedbyaccount": print access.listreceivedbyaccount(mc, incemp) except: print access.listreceivedbyaccount() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "listreceivedbyaddress": try: @@ -204,8 +204,8 @@ elif cmd == "listreceivedbyaddress": print access.listreceivedbyaddress(mc, incemp) except: print access.listreceivedbyaddress() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "listtransactions": try: @@ -216,8 +216,8 @@ elif cmd == "listtransactions": print access.listtransactions(acct, count, frm) except: print access.listtransactions() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "move": try: @@ -230,8 +230,8 @@ elif cmd == "move": print access.move(frm, to, amt, mc, comment) except: print access.move(frm, to, amt) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "sendfrom": try: @@ -245,8 +245,8 @@ elif cmd == "sendfrom": print access.sendfrom(frm, to, amt, mc, comment, commentto) except: print access.sendfrom(frm, to, amt) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "sendmany": try: @@ -258,8 +258,8 @@ elif cmd == "sendmany": print access.sendmany(frm,to,mc,comment) except: print access.sendmany(frm,to) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "sendtoaddress": try: @@ -271,16 +271,16 @@ elif cmd == "sendtoaddress": print access.sendtoaddress(to,amt,comment,commentto) except: print access.sendtoaddress(to,amt) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "setaccount": try: addr = raw_input("Address: ") acct = raw_input("Account:") print access.setaccount(addr,acct) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "setgenerate": try: @@ -290,36 +290,36 @@ elif cmd == "setgenerate": print access.setgenerate(gen, cpus) except: print access.setgenerate(gen) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "settxfee": try: amt = raw_input("Amount:") print access.settxfee(amt) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "stop": try: print access.stop() - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "validateaddress": try: addr = raw_input("Address: ") print access.validateaddress(addr) - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "walletpassphrase": try: pwd = getpass.getpass(prompt="Enter wallet passphrase: ") access.walletpassphrase(pwd, 60) print "\n---Wallet unlocked---\n" - except: - print "\n---An error occurred---\n" + except Exception as inst: + print inst elif cmd == "walletpassphrasechange": try: @@ -328,10 +328,8 @@ elif cmd == "walletpassphrasechange": access.walletpassphrasechange(pwd, pwd2) print print "\n---Passphrase changed---\n" - except: - print - print "\n---An error occurred---\n" - print + except Exception as inst: + print inst else: print "Command not found or not supported" diff --git a/contrib/init/README.md b/contrib/init/README.md index d3fa966583..0d19da3039 100644 --- a/contrib/init/README.md +++ b/contrib/init/README.md @@ -4,6 +4,7 @@ SystemD: bitcoind.service Upstart: bitcoind.conf OpenRC: bitcoind.openrc bitcoind.openrcconf +CentOS: bitcoind.init have been made available to assist packagers in creating node packages here. diff --git a/contrib/init/bitcoind.init b/contrib/init/bitcoind.init new file mode 100644 index 0000000000..db5061874b --- /dev/null +++ b/contrib/init/bitcoind.init @@ -0,0 +1,67 @@ +#!/bin/bash +# +# bitcoind The bitcoin core server. +# +# +# chkconfig: 345 80 20 +# description: bitcoind +# processname: bitcoind +# + +# Source function library. +. /etc/init.d/functions + +# you can override defaults in /etc/sysconfig/bitcoind, see below +if [ -f /etc/sysconfig/bitcoind ]; then + . /etc/sysconfig/bitcoind +fi + +RETVAL=0 + +prog=bitcoind +# you can override the lockfile via BITCOIND_LOCKFILE in /etc/sysconfig/bitcoind +lockfile=${BITCOIND_LOCKFILE-/var/lock/subsys/bitcoind} + +# bitcoind defaults to /usr/bin/bitcoind, override with BITCOIND_BIN +bitcoind=${BITCOIND_BIN-/usr/bin/bitcoind} + +# bitcoind opts default to -disablewallet, override with BITCOIND_OPTS +bitcoind_opts=${BITCOIND_OPTS--disablewallet} + +start() { + echo -n $"Starting $prog: " + daemon $DAEMONOPTS $bitcoind $bitcoind_opts + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch $lockfile + return $RETVAL +} + +stop() { + echo -n $"Stopping $prog: " + killproc $prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f $lockfile + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status $prog + ;; + restart) + stop + start + ;; + *) + echo "Usage: service $prog {start|stop|status|restart}" + exit 1 + ;; +esac diff --git a/contrib/init/bitcoind.openrc b/contrib/init/bitcoind.openrc index 1f7758c920..b0ac5e31e1 100644 --- a/contrib/init/bitcoind.openrc +++ b/contrib/init/bitcoind.openrc @@ -12,9 +12,11 @@ BITCOIND_CONFIGFILE=${BITCOIND_CONFIGFILE:-/etc/bitcoin/bitcoin.conf} BITCOIND_PIDDIR=${BITCOIND_PIDDIR:-/var/run/bitcoind} BITCOIND_PIDFILE=${BITCOIND_PIDFILE:-${BITCOIND_PIDDIR}/bitcoind.pid} BITCOIND_DATADIR=${BITCOIND_DATADIR:-${BITCOIND_DEFAULT_DATADIR}} -BITCOIND_USER=${BITCOIND_USER:-bitcoin} +BITCOIND_USER=${BITCOIND_USER:-${BITCOIN_USER:-bitcoin}} BITCOIND_GROUP=${BITCOIND_GROUP:-bitcoin} BITCOIND_BIN=${BITCOIND_BIN:-/usr/bin/bitcoind} +BITCOIND_NICE=${BITCOIND_NICE:-${NICELEVEL:-0}} +BITCOIND_OPTS="${BITCOIND_OPTS:-${BITCOIN_OPTS}}" name="Bitcoin Core Daemon" description="Bitcoin crypto-currency p2p network daemon" @@ -28,7 +30,7 @@ command_args="-pid=\"${BITCOIND_PIDFILE}\" \ required_files="${BITCOIND_CONFIGFILE}" start_stop_daemon_args="-u ${BITCOIND_USER} \ - -N ${BITCOIND_NICE:-0} -w 2000" + -N ${BITCOIND_NICE} -w 2000" pidfile="${BITCOIND_PIDFILE}" retry=60 diff --git a/contrib/verify-commits/gpg.sh b/contrib/verify-commits/gpg.sh new file mode 100755 index 0000000000..6b5137e7b5 --- /dev/null +++ b/contrib/verify-commits/gpg.sh @@ -0,0 +1,15 @@ +#!/bin/sh +INPUT=$(</dev/stdin) +VALID=false +IFS=$'\n' +for LINE in $(echo "$INPUT" | gpg --trust-model always "$@" 2>/dev/null); do + case "$LINE" in "[GNUPG:] VALIDSIG"*) + while read KEY; do + case "$LINE" in "[GNUPG:] VALIDSIG $KEY "*) VALID=true;; esac + done < ./contrib/verify-commits/trusted-keys + esac +done +if ! $VALID; then + exit 1 +fi +echo "$INPUT" | gpg --trust-model always "$@" 2>/dev/null diff --git a/contrib/verify-commits/pre-push-hook.sh b/contrib/verify-commits/pre-push-hook.sh new file mode 100755 index 0000000000..607c0cac45 --- /dev/null +++ b/contrib/verify-commits/pre-push-hook.sh @@ -0,0 +1,16 @@ +#!/bin/bash +if ! [[ "$2" =~ [git@]?[www.]?github.com[:|/]bitcoin/bitcoin[.git]? ]]; then + exit 0 +fi + +while read LINE; do + set -- A $LINE + if [ "$4" != "refs/heads/master" ]; then + continue + fi + if ! ./contrib/verify-commits/verify-commits.sh $3 > /dev/null 2>&1; then + echo "ERROR: A commit is not signed, can't push" + ./contrib/verify-commits/verify-commits.sh + exit 1 + fi +done < /dev/stdin diff --git a/contrib/verify-commits/trusted-git-root b/contrib/verify-commits/trusted-git-root new file mode 100644 index 0000000000..eb13f8762e --- /dev/null +++ b/contrib/verify-commits/trusted-git-root @@ -0,0 +1 @@ +053038e5ba116cb319fb85f3cb3e062cf1b3df15 diff --git a/contrib/verify-commits/trusted-keys b/contrib/verify-commits/trusted-keys new file mode 100644 index 0000000000..658ad0375b --- /dev/null +++ b/contrib/verify-commits/trusted-keys @@ -0,0 +1,5 @@ +71A3B16735405025D447E8F274810B012346C9A6 +1F4410F6A89268CE3197A84C57896D2FF8F0B657 +01CDF4627A3B88AAE4A571C87588242FBE38D3A8 +AF8BE07C7049F3A26B239D5325B3083201782B2F +81291FA67D2C379A006A053FEAB5AF94D9E9ABE7 diff --git a/contrib/verify-commits/verify-commits.sh b/contrib/verify-commits/verify-commits.sh new file mode 100755 index 0000000000..5841fa2077 --- /dev/null +++ b/contrib/verify-commits/verify-commits.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +DIR=$(dirname "$0") + +echo "Please verify all commits in the following list are not evil:" +git log "$DIR" + +VERIFIED_ROOT=$(cat "${DIR}/trusted-git-root") + +HAVE_FAILED=false +IS_SIGNED () { + if [ $1 = $VERIFIED_ROOT ]; then + return 0; + fi + if ! git -c "gpg.program=${DIR}/gpg.sh" verify-commit $1 > /dev/null 2>&1; then + return 1; + fi + local PARENTS=$(git show -s --format=format:%P $1) + for PARENT in $PARENTS; do + if IS_SIGNED $PARENT > /dev/null; then + return 0; + fi + done + if ! "$HAVE_FAILED"; then + echo "No parent of $1 was signed with a trusted key!" > /dev/stderr + echo "Parents are:" > /dev/stderr + for PARENT in $PARENTS; do + git show -s $PARENT > /dev/stderr + done + HAVE_FAILED=true + fi + return 1; +} + +if [ x"$1" = "x" ]; then + TEST_COMMIT="HEAD" +else + TEST_COMMIT="$1" +fi + +IS_SIGNED "$TEST_COMMIT" +RES=$? +if [ "$RES" = 1 ]; then + if ! "$HAVE_FAILED"; then + echo "$TEST_COMMIT was not signed with a trusted key!" + fi +else + echo "There is a valid path from $TEST_COMMIT to $VERIFIED_ROOT where all commits are signed!" +fi + +exit $RES diff --git a/depends/packages/libxcb.mk b/depends/packages/libxcb.mk index f29b577f8a..28f2bd6f25 100644 --- a/depends/packages/libxcb.mk +++ b/depends/packages/libxcb.mk @@ -13,8 +13,13 @@ define $(package)_preprocess_cmds sed "s/pthread-stubs//" -i configure endef +# Don't install xcb headers to the default path in order to work around a qt +# build issue: https://bugreports.qt.io/browse/QTBUG-34748 +# When using qt's internal libxcb, it may end up finding the real headers in +# depends staging. Use a non-default path to avoid that. + define $(package)_config_cmds - $($(package)_autoconf) + $($(package)_autoconf) --includedir=$(host_prefix)/include/xcb-shared endef define $(package)_build_cmds diff --git a/depends/patches/qt/fix-xcb-include-order.patch b/depends/patches/qt/fix-xcb-include-order.patch index bf6c6dca36..3bdbba32a4 100644 --- a/depends/patches/qt/fix-xcb-include-order.patch +++ b/depends/patches/qt/fix-xcb-include-order.patch @@ -1,21 +1,31 @@ ---- old/qtbase/src/plugins/platforms/xcb/xcb-plugin.pro 2014-07-30 18:17:27.384458441 -0400 -+++ new/qtbase/src/plugins/platforms/xcb/xcb-plugin.pro 2014-07-30 18:18:28.620459303 -0400 -@@ -101,10 +101,6 @@ - } - } - --DEFINES += $$QMAKE_DEFINES_XCB --LIBS += $$QMAKE_LIBS_XCB +--- old/qtbase/src/plugins/platforms/xcb/xcb-plugin.pro 2015-03-17 02:06:42.705930685 +0000 ++++ new/qtbase/src/plugins/platforms/xcb/xcb-plugin.pro 2015-03-17 02:08:41.281926351 +0000 +@@ -103,7 +103,6 @@ + + DEFINES += $$QMAKE_DEFINES_XCB + LIBS += $$QMAKE_LIBS_XCB -QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_XCB -- + CONFIG += qpa/genericunixfontdatabase - - contains(QT_CONFIG, dbus) { -@@ -141,3 +137,7 @@ - INCLUDEPATH += ../../../3rdparty/xkbcommon/xkbcommon/ - } - } -+ -+DEFINES += $$QMAKE_DEFINES_XCB -+LIBS += $$QMAKE_LIBS_XCB -+INCLUDEPATH += $$QMAKE_CFLAGS_XCB + +@@ -118,7 +117,8 @@ + contains(QT_CONFIG, xcb-qt) { + DEFINES += XCB_USE_RENDER + XCB_DIR = ../../../3rdparty/xcb +- INCLUDEPATH += $$XCB_DIR/include $$XCB_DIR/sysinclude ++ QMAKE_CFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/sysinclude $$QMAKE_CFLAGS_XCB ++ QMAKE_CXXFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/sysinclude $$QMAKE_CFLAGS_XCB + LIBS += -lxcb -L$$OUT_PWD/xcb-static -lxcb-static + } else { + LIBS += -lxcb -lxcb-image -lxcb-icccm -lxcb-sync -lxcb-xfixes -lxcb-shm -lxcb-randr +--- old/qtbase/src/plugins/platforms/xcb/xcb-static/xcb-static.pro 2015-03-17 02:07:04.641929383 +0000 ++++ new/qtbase/src/plugins/platforms/xcb/xcb-static/xcb-static.pro 2015-03-17 02:10:15.485922059 +0000 +@@ -8,7 +8,7 @@ + + XCB_DIR = ../../../../3rdparty/xcb + +-INCLUDEPATH += $$XCB_DIR/include $$XCB_DIR/include/xcb $$XCB_DIR/sysinclude ++QMAKE_CFLAGS += -I$$XCB_DIR/include -I$$XCB_DIR/include/xcb -I$$XCB_DIR/sysinclude $$QMAKE_CFLAGS_XCB + + # ignore compiler warnings in 3rdparty code + QMAKE_CFLAGS_STATIC_LIB+=-w diff --git a/doc/init.md b/doc/init.md index 1f0559d806..871bdc8123 100644 --- a/doc/init.md +++ b/doc/init.md @@ -8,6 +8,7 @@ can be found in the contrib/init folder. contrib/init/bitcoind.openrc: OpenRC compatible SysV style init script contrib/init/bitcoind.openrcconf: OpenRC conf.d file contrib/init/bitcoind.conf: Upstart service configuration file + contrib/init/bitcoind.init: CentOS compatible SysV style init script 1. Service User --------------------------------- @@ -49,6 +50,7 @@ Configuration file: /etc/bitcoin/bitcoin.conf Data directory: /var/lib/bitcoind PID file: /var/run/bitcoind/bitcoind.pid (OpenRC and Upstart) /var/lib/bitcoind/bitcoind.pid (systemd) +Lock file: /var/lock/subsys/bitcoind (CentOS) The configuration file, PID directory (if applicable) and data directory should all be owned by the bitcoin user and group. It is advised for security @@ -81,7 +83,15 @@ Drop bitcoind.conf in /etc/init. Test by running "service bitcoind start" it will automatically start on reboot. NOTE: This script is incompatible with CentOS 5 and Amazon Linux 2014 as they -use old versions of Upstart and do not supply the start-stop-daemon uitility. +use old versions of Upstart and do not supply the start-stop-daemon utility. + +4d) CentOS + +Copy bitcoind.init to /etc/init.d/bitcoind. Test by running "service bitcoind start". + +Using this script, you can adjust the path and flags to the bitcoind program by +setting the BITCOIND and FLAGS environment variables in the file +/etc/sysconfig/bitcoind. You can also use the DAEMONOPTS environment variable here. 5. Auto-respawn ----------------------------------- diff --git a/qa/rpc-tests/invalidateblock.py b/qa/rpc-tests/invalidateblock.py new file mode 100755 index 0000000000..a8bfbe6e6c --- /dev/null +++ b/qa/rpc-tests/invalidateblock.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test InvalidateBlock code +# + +from test_framework import BitcoinTestFramework +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException +from util import * + +class InvalidateTest(BitcoinTestFramework): + + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 2) + + def setup_network(self): + self.nodes = [] + self.is_network_split = False + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug"])) + + def run_test(self): + print "Mine 4 blocks on Node 0" + self.nodes[0].setgenerate(True, 4) + assert(self.nodes[0].getblockcount() == 4) + besthash = self.nodes[0].getbestblockhash() + + print "Mine competing 6 blocks on Node 1" + self.nodes[1].setgenerate(True, 6) + assert(self.nodes[1].getblockcount() == 6) + + print "Connect nodes to force a reorg" + connect_nodes_bi(self.nodes,0,1) + sync_blocks(self.nodes) + assert(self.nodes[0].getblockcount() == 6) + badhash = self.nodes[1].getblockhash(2) + + print "Invalidate block 2 on node 0 and verify we reorg to node 0's original chain" + self.nodes[0].invalidateblock(badhash) + newheight = self.nodes[0].getblockcount() + newhash = self.nodes[0].getbestblockhash() + if (newheight != 4 or newhash != besthash): + raise AssertionError("Wrong tip for node0, hash %s, height %d"%(newhash,newheight)) + +if __name__ == '__main__': + InvalidateTest().main() diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py index 3e63493dc6..dc4e0f77bd 100755 --- a/qa/rpc-tests/wallet.py +++ b/qa/rpc-tests/wallet.py @@ -102,6 +102,35 @@ class WalletTest (BitcoinTestFramework): assert_equal(self.nodes[2].getbalance(), 100) assert_equal(self.nodes[2].getbalance("from1"), 100-21) + # Send 10 BTC normal + address = self.nodes[0].getnewaddress("test") + self.nodes[2].settxfee(Decimal('0.001')) + txid = self.nodes[2].sendtoaddress(address, 10, "", "", False) + self.nodes[2].setgenerate(True, 1) + self.sync_all() + assert_equal(self.nodes[2].getbalance(), Decimal('89.99900000')) + assert_equal(self.nodes[0].getbalance(), Decimal('10.00000000')) + + # Send 10 BTC with subtract fee from amount + txid = self.nodes[2].sendtoaddress(address, 10, "", "", True) + self.nodes[2].setgenerate(True, 1) + self.sync_all() + assert_equal(self.nodes[2].getbalance(), Decimal('79.99900000')) + assert_equal(self.nodes[0].getbalance(), Decimal('19.99900000')) + + # Sendmany 10 BTC + txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", []) + self.nodes[2].setgenerate(True, 1) + self.sync_all() + assert_equal(self.nodes[2].getbalance(), Decimal('69.99800000')) + assert_equal(self.nodes[0].getbalance(), Decimal('29.99900000')) + + # Sendmany 10 BTC with subtract fee from amount + txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [address]) + self.nodes[2].setgenerate(True, 1) + self.sync_all() + assert_equal(self.nodes[2].getbalance(), Decimal('59.99800000')) + assert_equal(self.nodes[0].getbalance(), Decimal('39.99800000')) if __name__ == '__main__': WalletTest ().main () diff --git a/src/Makefile.am b/src/Makefile.am index c30368c5bf..d1a8efaa6a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -72,7 +72,6 @@ endif BITCOIN_CORE_H = \ addrman.h \ alert.h \ - allocators.h \ amount.h \ arith_uint256.h \ base58.h \ @@ -123,7 +122,10 @@ BITCOIN_CORE_H = \ script/standard.h \ serialize.h \ streams.h \ + support/allocators/secure.h \ + support/allocators/zeroafterfree.h \ support/cleanse.h \ + support/pagelocker.h \ sync.h \ threadsafety.h \ timedata.h \ @@ -233,7 +235,6 @@ univalue_libbitcoin_univalue_a_SOURCES = \ # common: shared between bitcoind, and bitcoin-qt and non-server tools libbitcoin_common_a_CPPFLAGS = $(BITCOIN_INCLUDES) libbitcoin_common_a_SOURCES = \ - allocators.cpp \ arith_uint256.cpp \ amount.cpp \ base58.cpp \ @@ -264,6 +265,7 @@ libbitcoin_common_a_SOURCES = \ # backward-compatibility objects and their sanity checks are linked. libbitcoin_util_a_CPPFLAGS = $(BITCOIN_INCLUDES) libbitcoin_util_a_SOURCES = \ + support/pagelocker.cpp \ chainparamsbase.cpp \ clientversion.cpp \ compat/glibc_sanity.cpp \ diff --git a/src/addrman.cpp b/src/addrman.cpp index 1e08ae772e..4b7e4d51b9 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -272,8 +272,9 @@ void CAddrMan::Good_(const CService& addr, int64_t nTime) // update info info.nLastSuccess = nTime; info.nLastTry = nTime; - info.nTime = nTime; info.nAttempts = 0; + // nTime is not updated here, to avoid leaking information about + // currently-connected peers. // if it is already in the tried set, don't do anything else if (info.fInTried) diff --git a/src/base58.h b/src/base58.h index ed134e6e77..8de90046a9 100644 --- a/src/base58.h +++ b/src/base58.h @@ -19,6 +19,7 @@ #include "pubkey.h" #include "script/script.h" #include "script/standard.h" +#include "support/allocators/zeroafterfree.h" #include <string> #include <vector> diff --git a/src/crypter.h b/src/crypter.h index 8a91498e2e..32746b00df 100644 --- a/src/crypter.h +++ b/src/crypter.h @@ -5,9 +5,9 @@ #ifndef BITCOIN_CRYPTER_H #define BITCOIN_CRYPTER_H -#include "allocators.h" #include "keystore.h" #include "serialize.h" +#include "support/allocators/secure.h" class uint256; @@ -6,8 +6,8 @@ #ifndef BITCOIN_KEY_H #define BITCOIN_KEY_H -#include "allocators.h" #include "serialize.h" +#include "support/allocators/secure.h" #include "uint256.h" #include <stdexcept> diff --git a/src/main.cpp b/src/main.cpp index 6bbb3bc135..94cf213490 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -238,6 +238,10 @@ struct CBlockReject { * and we're no longer holding the node's locks. */ struct CNodeState { + //! The peer's address + CService address; + //! Whether we have a fully established connection. + bool fCurrentlyConnected; //! Accumulated misbehaviour score for this peer. int nMisbehavior; //! Whether this peer should be disconnected and banned (unless whitelisted). @@ -262,6 +266,7 @@ struct CNodeState { bool fPreferredDownload; CNodeState() { + fCurrentlyConnected = false; nMisbehavior = 0; fShouldBan = false; pindexBestKnownBlock = NULL; @@ -305,6 +310,7 @@ void InitializeNode(NodeId nodeid, const CNode *pnode) { LOCK(cs_main); CNodeState &state = mapNodeState.insert(std::make_pair(nodeid, CNodeState())).first->second; state.name = pnode->addrName; + state.address = pnode->addr; } void FinalizeNode(NodeId nodeid) { @@ -314,6 +320,10 @@ void FinalizeNode(NodeId nodeid) { if (state->fSyncStarted) nSyncStarted--; + if (state->nMisbehavior == 0 && state->fCurrentlyConnected) { + AddressCurrentlyConnected(state->address); + } + BOOST_FOREACH(const QueuedBlock& entry, state->vBlocksInFlight) mapBlocksInFlight.erase(entry.hash); EraseOrphansFor(nodeid); @@ -2311,7 +2321,7 @@ bool InvalidateBlock(CValidationState& state, CBlockIndex *pindex) { BlockMap::iterator it = mapBlockIndex.begin(); while (it != mapBlockIndex.end()) { if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->nChainTx && setBlockIndexCandidates.value_comp()(chainActive.Tip(), it->second)) { - setBlockIndexCandidates.insert(pindex); + setBlockIndexCandidates.insert(it->second); } it++; } @@ -3627,6 +3637,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, else if (strCommand == "verack") { pfrom->SetRecvVersion(min(pfrom->nVersion, PROTOCOL_VERSION)); + + // Mark this node as currently connected, so we update its timestamp later. + if (pfrom->fNetworkNode) { + LOCK(cs_main); + State(pfrom->GetId())->fCurrentlyConnected = true; + } } @@ -4271,11 +4287,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } - // Update the last seen time for this node's address - if (pfrom->fNetworkNode) - if (strCommand == "version" || strCommand == "addr" || strCommand == "inv" || strCommand == "getdata" || strCommand == "ping") - AddressCurrentlyConnected(pfrom->addr); - return true; } @@ -78,12 +78,27 @@ void SocketSendData(CNode *pnode); typedef int NodeId; +struct CombinerAll +{ + typedef bool result_type; + + template<typename I> + bool operator()(I first, I last) const + { + while (first != last) { + if (!(*first)) return false; + ++first; + } + return true; + } +}; + // Signals for message handling struct CNodeSignals { boost::signals2::signal<int ()> GetHeight; - boost::signals2::signal<bool (CNode*)> ProcessMessages; - boost::signals2::signal<bool (CNode*, bool)> SendMessages; + boost::signals2::signal<bool (CNode*), CombinerAll> ProcessMessages; + boost::signals2::signal<bool (CNode*, bool), CombinerAll> SendMessages; boost::signals2::signal<void (NodeId, const CNode*)> InitializeNode; boost::signals2::signal<void (NodeId)> FinalizeNode; }; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 0ba9affeda..6cfd93a9a1 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -135,7 +135,7 @@ public: uint256 GetHash() const; - bool IsDust(CFeeRate minRelayTxFee) const + CAmount GetDustThreshold(const CFeeRate &minRelayTxFee) const { // "Dust" is defined in terms of CTransaction::minRelayTxFee, // which has units satoshis-per-kilobyte. @@ -146,7 +146,12 @@ public: // so dust is a txout less than 546 satoshis // with default minRelayTxFee. size_t nSize = GetSerializeSize(SER_DISK,0)+148u; - return (nValue < 3*minRelayTxFee.GetFee(nSize)); + return 3*minRelayTxFee.GetFee(nSize); + } + + bool IsDust(const CFeeRate &minRelayTxFee) const + { + return (nValue < GetDustThreshold(minRelayTxFee)); } friend bool operator==(const CTxOut& a, const CTxOut& b) diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 9b7b59c0db..229139e65c 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -8,7 +8,7 @@ #include "guiconstants.h" #include "walletmodel.h" -#include "allocators.h" +#include "support/allocators/secure.h" #include <QKeyEvent> #include <QMessageBox> diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 1ec968ff2b..58c005ae75 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -237,7 +237,7 @@ BitcoinGUI::~BitcoinGUI() trayIcon->hide(); #ifdef Q_OS_MAC delete appMenuBar; - MacDockIconHandler::instance()->setMainWindow(NULL); + MacDockIconHandler::cleanup(); #endif delete rpcConsole; diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 6d7ef70a70..e4e9015c85 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -33,6 +33,7 @@ using namespace std; QList<CAmount> CoinControlDialog::payAmounts; CCoinControl* CoinControlDialog::coinControl = new CCoinControl(); +bool CoinControlDialog::fSubtractFeeFromAmount = false; CoinControlDialog::CoinControlDialog(QWidget *parent) : QDialog(parent), @@ -541,6 +542,11 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * 29)); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 bytes of the input are ignored for priority) sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority, mempoolEstimatePriority); + // in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate + if (CoinControlDialog::fSubtractFeeFromAmount) + if (nAmount - nPayAmount == 0) + nBytes -= 34; + // Fee nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool); @@ -556,7 +562,9 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) if (nPayAmount > 0) { - nChange = nAmount - nPayFee - nPayAmount; + nChange = nAmount - nPayAmount; + if (!CoinControlDialog::fSubtractFeeFromAmount) + nChange -= nPayFee; // Never create dust outputs; if we would, just add the dust to the fee. if (nChange > 0 && nChange < CENT) @@ -564,12 +572,17 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0)); if (txout.IsDust(::minRelayTxFee)) { - nPayFee += nChange; - nChange = 0; + if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust + nChange = txout.GetDustThreshold(::minRelayTxFee); + else + { + nPayFee += nChange; + nChange = 0; + } } } - if (nChange == 0) + if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount) nBytes -= 34; } @@ -612,7 +625,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) { l3->setText(ASYMP_UTF8 + l3->text()); l4->setText(ASYMP_UTF8 + l4->text()); - if (nChange > 0) + if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount) l8->setText(ASYMP_UTF8 + l8->text()); } diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index 5a91876f1f..5ec382838f 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -43,6 +43,7 @@ public: static QList<CAmount> payAmounts; static CCoinControl *coinControl; + static bool fSubtractFeeFromAmount; private: Ui::CoinControlDialog *ui; diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 9f8c0a4844..b362928438 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -157,7 +157,21 @@ </widget> </item> <item row="2" column="1"> - <widget class="BitcoinAmountField" name="payAmount"/> + <layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1"> + <item> + <widget class="BitcoinAmountField" name="payAmount"/> + </item> + <item> + <widget class="QCheckBox" name="checkboxSubtractFeeFromAmount"> + <property name="toolTip"> + <string>The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</string> + </property> + <property name="text"> + <string>S&ubtract fee from amount</string> + </property> + </widget> + </item> + </layout> </item> <item row="3" column="0"> <widget class="QLabel" name="messageLabel"> diff --git a/src/qt/macdockiconhandler.h b/src/qt/macdockiconhandler.h index 1217bd8e88..15a6583ca4 100644 --- a/src/qt/macdockiconhandler.h +++ b/src/qt/macdockiconhandler.h @@ -14,12 +14,6 @@ class QMenu; class QWidget; QT_END_NAMESPACE -#ifdef __OBJC__ -@class DockIconClickEventHandler; -#else -class DockIconClickEventHandler; -#endif - /** Macintosh-specific dock icon handler. */ class MacDockIconHandler : public QObject @@ -33,7 +27,7 @@ public: void setIcon(const QIcon &icon); void setMainWindow(QMainWindow *window); static MacDockIconHandler *instance(); - + static void cleanup(); void handleDockIconClickEvent(); signals: @@ -42,7 +36,6 @@ signals: private: MacDockIconHandler(); - DockIconClickEventHandler *m_dockIconClickEventHandler; QWidget *m_dummyWidget; QMenu *m_dockMenu; QMainWindow *mainWindow; diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index e7b58b9cc0..58a0365d3d 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -11,52 +11,46 @@ #undef slots #include <Cocoa/Cocoa.h> +#include <objc/objc.h> +#include <objc/message.h> #if QT_VERSION < 0x050000 extern void qt_mac_set_dock_menu(QMenu *); #endif -@interface DockIconClickEventHandler : NSObject -{ - MacDockIconHandler* dockIconHandler; -} - -@end +static MacDockIconHandler *s_instance = NULL; -@implementation DockIconClickEventHandler - -- (id)initWithDockIconHandler:(MacDockIconHandler *)aDockIconHandler -{ - self = [super init]; - if (self) { - dockIconHandler = aDockIconHandler; - - [[NSAppleEventManager sharedAppleEventManager] - setEventHandler:self - andSelector:@selector(handleDockClickEvent:withReplyEvent:) - forEventClass:kCoreEventClass - andEventID:kAEReopenApplication]; - } - return self; +bool dockClickHandler(id self,SEL _cmd,...) { + Q_UNUSED(self) + Q_UNUSED(_cmd) + + s_instance->handleDockIconClickEvent(); + + // Return NO (false) to suppress the default OS X actions + return false; } -- (void)handleDockClickEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent -{ - Q_UNUSED(event) - Q_UNUSED(replyEvent) - - if (dockIconHandler) { - dockIconHandler->handleDockIconClickEvent(); +void setupDockClickHandler() { + Class cls = objc_getClass("NSApplication"); + id appInst = objc_msgSend((id)cls, sel_registerName("sharedApplication")); + + if (appInst != NULL) { + id delegate = objc_msgSend(appInst, sel_registerName("delegate")); + Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); + SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); + if (class_getInstanceMethod(delClass, shouldHandle)) + class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); + else + class_addMethod(delClass, shouldHandle, (IMP)dockClickHandler,"B@:"); } } -@end MacDockIconHandler::MacDockIconHandler() : QObject() { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - this->m_dockIconClickEventHandler = [[DockIconClickEventHandler alloc] initWithDockIconHandler:this]; + setupDockClickHandler(); this->m_dummyWidget = new QWidget(); this->m_dockMenu = new QMenu(this->m_dummyWidget); this->setMainWindow(NULL); @@ -74,7 +68,6 @@ void MacDockIconHandler::setMainWindow(QMainWindow *window) { MacDockIconHandler::~MacDockIconHandler() { - [this->m_dockIconClickEventHandler release]; delete this->m_dummyWidget; this->setMainWindow(NULL); } @@ -119,12 +112,16 @@ void MacDockIconHandler::setIcon(const QIcon &icon) MacDockIconHandler *MacDockIconHandler::instance() { - static MacDockIconHandler *s_instance = NULL; if (!s_instance) s_instance = new MacDockIconHandler(); return s_instance; } +void MacDockIconHandler::cleanup() +{ + delete s_instance; +} + void MacDockIconHandler::handleDockIconClickEvent() { if (this->mainWindow) diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 2ec180c3ac..28c8fb95f6 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -220,9 +220,37 @@ void SendCoinsDialog::on_sendButton_clicked() return; } + fNewRecipientAllowed = false; + WalletModel::UnlockContext ctx(model->requestUnlock()); + if(!ctx.isValid()) + { + // Unlock wallet was cancelled + fNewRecipientAllowed = true; + return; + } + + // prepare transaction for getting txFee earlier + WalletModelTransaction currentTransaction(recipients); + WalletModel::SendCoinsReturn prepareStatus; + if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled + prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl); + else + prepareStatus = model->prepareTransaction(currentTransaction); + + // process prepareStatus and on error generate message shown to user + processSendCoinsReturn(prepareStatus, + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); + + if(prepareStatus.status != WalletModel::OK) { + fNewRecipientAllowed = true; + return; + } + + CAmount txFee = currentTransaction.getTransactionFee(); + // Format confirmation message QStringList formatted; - foreach(const SendCoinsRecipient &rcp, recipients) + foreach(const SendCoinsRecipient &rcp, currentTransaction.getRecipients()) { // generate bold amount string QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); @@ -257,35 +285,6 @@ void SendCoinsDialog::on_sendButton_clicked() formatted.append(recipientElement); } - fNewRecipientAllowed = false; - - - WalletModel::UnlockContext ctx(model->requestUnlock()); - if(!ctx.isValid()) - { - // Unlock wallet was cancelled - fNewRecipientAllowed = true; - return; - } - - // prepare transaction for getting txFee earlier - WalletModelTransaction currentTransaction(recipients); - WalletModel::SendCoinsReturn prepareStatus; - if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled - prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl); - else - prepareStatus = model->prepareTransaction(currentTransaction); - - // process prepareStatus and on error generate message shown to user - processSendCoinsReturn(prepareStatus, - BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); - - if(prepareStatus.status != WalletModel::OK) { - fNewRecipientAllowed = true; - return; - } - - CAmount txFee = currentTransaction.getTransactionFee(); QString questionString = tr("Are you sure you want to send?"); questionString.append("<br /><br />%1"); @@ -368,6 +367,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry() ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*))); connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); + connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels())); updateTabsAndLabels(); @@ -783,11 +783,17 @@ void SendCoinsDialog::coinControlUpdateLabels() // set pay amounts CoinControlDialog::payAmounts.clear(); + CoinControlDialog::fSubtractFeeFromAmount = false; for(int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); if(entry) - CoinControlDialog::payAmounts.append(entry->getValue().amount); + { + SendCoinsRecipient rcp = entry->getValue(); + CoinControlDialog::payAmounts.append(rcp.amount); + if (rcp.fSubtractFeeFromAmount) + CoinControlDialog::fSubtractFeeFromAmount = true; + } } if (CoinControlDialog::coinControl->HasSelected()) diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 6db6eee75b..6ac650e74f 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -44,6 +44,7 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) : // Connect signals connect(ui->payAmount, SIGNAL(valueChanged()), this, SIGNAL(payAmountChanged())); + connect(ui->checkboxSubtractFeeFromAmount, SIGNAL(toggled(bool)), this, SIGNAL(subtractFeeFromAmountChanged())); 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())); @@ -94,6 +95,7 @@ void SendCoinsEntry::clear() ui->payTo->clear(); ui->addAsLabel->clear(); ui->payAmount->clear(); + ui->checkboxSubtractFeeFromAmount->setCheckState(Qt::Unchecked); ui->messageTextLabel->clear(); ui->messageTextLabel->hide(); ui->messageLabel->hide(); @@ -165,6 +167,7 @@ SendCoinsRecipient SendCoinsEntry::getValue() recipient.label = ui->addAsLabel->text(); recipient.amount = ui->payAmount->value(); recipient.message = ui->messageTextLabel->text(); + recipient.fSubtractFeeFromAmount = (ui->checkboxSubtractFeeFromAmount->checkState() == Qt::Checked); return recipient; } @@ -174,7 +177,8 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev) QWidget::setTabOrder(prev, ui->payTo); QWidget::setTabOrder(ui->payTo, ui->addAsLabel); QWidget *w = ui->payAmount->setupTabChain(ui->addAsLabel); - QWidget::setTabOrder(w, ui->addressBookButton); + QWidget::setTabOrder(w, ui->checkboxSubtractFeeFromAmount); + QWidget::setTabOrder(ui->checkboxSubtractFeeFromAmount, ui->addressBookButton); QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton); QWidget::setTabOrder(ui->pasteButton, ui->deleteButton); return ui->deleteButton; diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 4cb00cd36a..c2d1185bdd 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -51,6 +51,7 @@ public slots: signals: void removeEntry(SendCoinsEntry *entry); void payAmountChanged(); + void subtractFeeFromAmountChanged(); private slots: void deleteClicked(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d4e88c1a51..09ed8ce9fd 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -6,6 +6,7 @@ #include "addresstablemodel.h" #include "guiconstants.h" +#include "guiutil.h" #include "paymentserver.h" #include "recentrequeststablemodel.h" #include "transactiontablemodel.h" @@ -192,8 +193,9 @@ bool WalletModel::validateAddress(const QString &address) WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl) { CAmount total = 0; + bool fSubtractFeeFromAmount = false; QList<SendCoinsRecipient> recipients = transaction.getRecipients(); - std::vector<std::pair<CScript, CAmount> > vecSend; + std::vector<CRecipient> vecSend; if(recipients.empty()) { @@ -206,6 +208,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact // Pre-check input data for validity foreach(const SendCoinsRecipient &rcp, recipients) { + if (rcp.fSubtractFeeFromAmount) + fSubtractFeeFromAmount = true; + if (rcp.paymentRequest.IsInitialized()) { // PaymentRequest... CAmount subtotal = 0; @@ -217,7 +222,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact subtotal += out.amount(); const unsigned char* scriptStr = (const unsigned char*)out.script().data(); CScript scriptPubKey(scriptStr, scriptStr+out.script().size()); - vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, out.amount())); + CAmount nAmount = out.amount(); + CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount}; + vecSend.push_back(recipient); } if (subtotal <= 0) { @@ -239,7 +246,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact ++nAddresses; CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); - vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, rcp.amount)); + CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount}; + vecSend.push_back(recipient); total += rcp.amount; } @@ -260,17 +268,21 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact LOCK2(cs_main, wallet->cs_wallet); transaction.newPossibleKeyChange(wallet); + CAmount nFeeRequired = 0; + int nChangePosRet = -1; std::string strFailReason; CWalletTx *newTx = transaction.getTransaction(); CReserveKey *keyChange = transaction.getPossibleKeyChange(); - bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason, coinControl); + bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); transaction.setTransactionFee(nFeeRequired); + if (fSubtractFeeFromAmount && fCreated) + transaction.reassignAmounts(nChangePosRet); if(!fCreated) { - if((total + nFeeRequired) > nBalance) + if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance) { return SendCoinsReturn(AmountWithFeeExceedsBalance); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 4a9a12beaa..e263438880 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -8,7 +8,7 @@ #include "paymentrequestplus.h" #include "walletmodeltransaction.h" -#include "allocators.h" /* for SecureString */ +#include "support/allocators/secure.h" #include <map> #include <vector> @@ -36,9 +36,9 @@ QT_END_NAMESPACE class SendCoinsRecipient { public: - explicit SendCoinsRecipient() : amount(0), nVersion(SendCoinsRecipient::CURRENT_VERSION) { } + explicit SendCoinsRecipient() : amount(0), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) { } explicit SendCoinsRecipient(const QString &addr, const QString &label, const CAmount& amount, const QString &message): - address(addr), label(label), amount(amount), message(message), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} + address(addr), label(label), amount(amount), message(message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} // If from an unauthenticated payment request, this is used for storing // the addresses, e.g. address-A<br />address-B<br />address-C. @@ -56,6 +56,8 @@ public: // Empty if no authentication or invalid signature/cert/etc. QString authenticatedMerchant; + bool fSubtractFeeFromAmount; // memory only + static const int CURRENT_VERSION = 1; int nVersion; diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index d9129077f5..206bb7c774 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -46,6 +46,38 @@ void WalletModelTransaction::setTransactionFee(const CAmount& newFee) fee = newFee; } +void WalletModelTransaction::reassignAmounts(int nChangePosRet) +{ + int i = 0; + for (QList<SendCoinsRecipient>::iterator it = recipients.begin(); it != recipients.end(); ++it) + { + SendCoinsRecipient& rcp = (*it); + + if (rcp.paymentRequest.IsInitialized()) + { + CAmount subtotal = 0; + const payments::PaymentDetails& details = rcp.paymentRequest.getDetails(); + for (int j = 0; j < details.outputs_size(); j++) + { + const payments::Output& out = details.outputs(j); + if (out.amount() <= 0) continue; + if (i == nChangePosRet) + i++; + subtotal += walletTransaction->vout[i].nValue; + i++; + } + rcp.amount = subtotal; + } + else // normal recipient (no payment request) + { + if (i == nChangePosRet) + i++; + rcp.amount = walletTransaction->vout[i].nValue; + i++; + } + } +} + CAmount WalletModelTransaction::getTotalTransactionAmount() { CAmount totalTransactionAmount = 0; diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index b6bb6d67f6..7765fea4af 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -35,8 +35,10 @@ public: void newPossibleKeyChange(CWallet *wallet); CReserveKey *getPossibleKeyChange(); + void reassignAmounts(int nChangePosRet); // needed for the subtract-fee-from-amount feature + private: - const QList<SendCoinsRecipient> recipients; + QList<SendCoinsRecipient> recipients; CWalletTx *walletTransaction; CReserveKey *keyChange; CAmount fee; diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 4e45bc32ab..a45ea9839b 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -32,6 +32,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getnetworkhashps", 0 }, { "getnetworkhashps", 1 }, { "sendtoaddress", 1 }, + { "sendtoaddress", 4 }, { "settxfee", 0 }, { "getreceivedbyaddress", 1 }, { "getreceivedbyaccount", 1 }, @@ -59,6 +60,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listsinceblock", 2 }, { "sendmany", 1 }, { "sendmany", 2 }, + { "sendmany", 4 }, { "addmultisigaddress", 0 }, { "addmultisigaddress", 1 }, { "createmultisig", 0 }, diff --git a/src/streams.h b/src/streams.h index 9999c2341f..fa1e18defe 100644 --- a/src/streams.h +++ b/src/streams.h @@ -6,7 +6,7 @@ #ifndef BITCOIN_STREAMS_H #define BITCOIN_STREAMS_H -#include "allocators.h" +#include "support/allocators/zeroafterfree.h" #include "serialize.h" #include <algorithm> diff --git a/src/support/allocators/secure.h b/src/support/allocators/secure.h new file mode 100644 index 0000000000..7a74d87bb4 --- /dev/null +++ b/src/support/allocators/secure.h @@ -0,0 +1,62 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_ALLOCATORS_SECURE_H +#define BITCOIN_ALLOCATORS_SECURE_H + +#include "support/pagelocker.h" + +#include <string> + +// +// Allocator that locks its contents from being paged +// out of memory and clears its contents before deletion. +// +template <typename T> +struct secure_allocator : public std::allocator<T> { + // MSVC8 default copy constructor is broken + typedef std::allocator<T> base; + typedef typename base::size_type size_type; + typedef typename base::difference_type difference_type; + typedef typename base::pointer pointer; + typedef typename base::const_pointer const_pointer; + typedef typename base::reference reference; + typedef typename base::const_reference const_reference; + typedef typename base::value_type value_type; + secure_allocator() throw() {} + secure_allocator(const secure_allocator& a) throw() : base(a) {} + template <typename U> + secure_allocator(const secure_allocator<U>& a) throw() : base(a) + { + } + ~secure_allocator() throw() {} + template <typename _Other> + struct rebind { + typedef secure_allocator<_Other> other; + }; + + T* allocate(std::size_t n, const void* hint = 0) + { + T* p; + p = std::allocator<T>::allocate(n, hint); + if (p != NULL) + LockedPageManager::Instance().LockRange(p, sizeof(T) * n); + return p; + } + + void deallocate(T* p, std::size_t n) + { + if (p != NULL) { + memory_cleanse(p, sizeof(T) * n); + LockedPageManager::Instance().UnlockRange(p, sizeof(T) * n); + } + std::allocator<T>::deallocate(p, n); + } +}; + +// This is exactly like std::string, but with a custom allocator. +typedef std::basic_string<char, std::char_traits<char>, secure_allocator<char> > SecureString; + +#endif // BITCOIN_ALLOCATORS_SECURE_H diff --git a/src/support/allocators/zeroafterfree.h b/src/support/allocators/zeroafterfree.h new file mode 100644 index 0000000000..b01fcd088b --- /dev/null +++ b/src/support/allocators/zeroafterfree.h @@ -0,0 +1,48 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_ALLOCATORS_ZEROAFTERFREE_H +#define BITCOIN_ALLOCATORS_ZEROAFTERFREE_H + +#include "support/cleanse.h" + +#include <memory> +#include <vector> + +template <typename T> +struct zero_after_free_allocator : public std::allocator<T> { + // MSVC8 default copy constructor is broken + typedef std::allocator<T> base; + typedef typename base::size_type size_type; + typedef typename base::difference_type difference_type; + typedef typename base::pointer pointer; + typedef typename base::const_pointer const_pointer; + typedef typename base::reference reference; + typedef typename base::const_reference const_reference; + typedef typename base::value_type value_type; + zero_after_free_allocator() throw() {} + zero_after_free_allocator(const zero_after_free_allocator& a) throw() : base(a) {} + template <typename U> + zero_after_free_allocator(const zero_after_free_allocator<U>& a) throw() : base(a) + { + } + ~zero_after_free_allocator() throw() {} + template <typename _Other> + struct rebind { + typedef zero_after_free_allocator<_Other> other; + }; + + void deallocate(T* p, std::size_t n) + { + if (p != NULL) + memory_cleanse(p, sizeof(T) * n); + std::allocator<T>::deallocate(p, n); + } +}; + +// Byte-vector that clears its contents before deletion. +typedef std::vector<char, zero_after_free_allocator<char> > CSerializeData; + +#endif // BITCOIN_ALLOCATORS_ZEROAFTERFREE_H diff --git a/src/allocators.cpp b/src/support/pagelocker.cpp index d3958aa4d7..440e0a5193 100644 --- a/src/allocators.cpp +++ b/src/support/pagelocker.cpp @@ -2,7 +2,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "allocators.h" +#include "support/pagelocker.h" + +#if defined(HAVE_CONFIG_H) +#include "config/bitcoin-config.h" +#endif #ifdef WIN32 #ifdef _WIN32_WINNT diff --git a/src/allocators.h b/src/support/pagelocker.h index 8ffe015b9e..964be1aec4 100644 --- a/src/allocators.h +++ b/src/support/pagelocker.h @@ -3,15 +3,12 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_ALLOCATORS_H -#define BITCOIN_ALLOCATORS_H +#ifndef BITCOIN_ALLOCATORS_PAGELOCKER_H +#define BITCOIN_ALLOCATORS_PAGELOCKER_H #include "support/cleanse.h" #include <map> -#include <string> -#include <string.h> -#include <vector> #include <boost/thread/mutex.hpp> #include <boost/thread/once.hpp> @@ -178,91 +175,4 @@ void UnlockObject(const T& t) LockedPageManager::Instance().UnlockRange((void*)(&t), sizeof(T)); } -// -// Allocator that locks its contents from being paged -// out of memory and clears its contents before deletion. -// -template <typename T> -struct secure_allocator : public std::allocator<T> { - // MSVC8 default copy constructor is broken - typedef std::allocator<T> base; - typedef typename base::size_type size_type; - typedef typename base::difference_type difference_type; - typedef typename base::pointer pointer; - typedef typename base::const_pointer const_pointer; - typedef typename base::reference reference; - typedef typename base::const_reference const_reference; - typedef typename base::value_type value_type; - secure_allocator() throw() {} - secure_allocator(const secure_allocator& a) throw() : base(a) {} - template <typename U> - secure_allocator(const secure_allocator<U>& a) throw() : base(a) - { - } - ~secure_allocator() throw() {} - template <typename _Other> - struct rebind { - typedef secure_allocator<_Other> other; - }; - - T* allocate(std::size_t n, const void* hint = 0) - { - T* p; - p = std::allocator<T>::allocate(n, hint); - if (p != NULL) - LockedPageManager::Instance().LockRange(p, sizeof(T) * n); - return p; - } - - void deallocate(T* p, std::size_t n) - { - if (p != NULL) { - memory_cleanse(p, sizeof(T) * n); - LockedPageManager::Instance().UnlockRange(p, sizeof(T) * n); - } - std::allocator<T>::deallocate(p, n); - } -}; - - -// -// Allocator that clears its contents before deletion. -// -template <typename T> -struct zero_after_free_allocator : public std::allocator<T> { - // MSVC8 default copy constructor is broken - typedef std::allocator<T> base; - typedef typename base::size_type size_type; - typedef typename base::difference_type difference_type; - typedef typename base::pointer pointer; - typedef typename base::const_pointer const_pointer; - typedef typename base::reference reference; - typedef typename base::const_reference const_reference; - typedef typename base::value_type value_type; - zero_after_free_allocator() throw() {} - zero_after_free_allocator(const zero_after_free_allocator& a) throw() : base(a) {} - template <typename U> - zero_after_free_allocator(const zero_after_free_allocator<U>& a) throw() : base(a) - { - } - ~zero_after_free_allocator() throw() {} - template <typename _Other> - struct rebind { - typedef zero_after_free_allocator<_Other> other; - }; - - void deallocate(T* p, std::size_t n) - { - if (p != NULL) - memory_cleanse(p, sizeof(T) * n); - std::allocator<T>::deallocate(p, n); - } -}; - -// This is exactly like std::string, but with a custom allocator. -typedef std::basic_string<char, std::char_traits<char>, secure_allocator<char> > SecureString; - -// Byte-vector that clears its contents before deletion. -typedef std::vector<char, zero_after_free_allocator<char> > CSerializeData; - -#endif // BITCOIN_ALLOCATORS_H +#endif // BITCOIN_ALLOCATORS_PAGELOCKER_H diff --git a/src/test/Checkpoints_tests.cpp b/src/test/Checkpoints_tests.cpp index a9b6cd44a8..c3125d76dc 100644 --- a/src/test/Checkpoints_tests.cpp +++ b/src/test/Checkpoints_tests.cpp @@ -9,12 +9,13 @@ #include "checkpoints.h" #include "uint256.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> using namespace std; -BOOST_AUTO_TEST_SUITE(Checkpoints_tests) +BOOST_FIXTURE_TEST_SUITE(Checkpoints_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(sanity) { diff --git a/src/test/allocator_tests.cpp b/src/test/allocator_tests.cpp index 991b4ac099..2108efece5 100644 --- a/src/test/allocator_tests.cpp +++ b/src/test/allocator_tests.cpp @@ -4,11 +4,12 @@ #include "util.h" -#include "allocators.h" +#include "support/allocators/secure.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> -BOOST_AUTO_TEST_SUITE(allocator_tests) +BOOST_FIXTURE_TEST_SUITE(allocator_tests, BasicTestingSetup) // Dummy memory page locker for platform independent tests static const void *last_lock_addr, *last_unlock_addr; diff --git a/src/test/arith_uint256_tests.cpp b/src/test/arith_uint256_tests.cpp index 565b02ae64..17d6bed6d2 100644 --- a/src/test/arith_uint256_tests.cpp +++ b/src/test/arith_uint256_tests.cpp @@ -12,8 +12,9 @@ #include "arith_uint256.h" #include <string> #include "version.h" +#include "test/test_bitcoin.h" -BOOST_AUTO_TEST_SUITE(arith_uint256_tests) +BOOST_FIXTURE_TEST_SUITE(arith_uint256_tests, BasicTestingSetup) /// Convert vector to arith_uint256, via uint256 blob inline arith_uint256 arith_uint256V(const std::vector<unsigned char>& vch) diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp index 5d20a90ad0..8ec8861425 100644 --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -3,10 +3,11 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "utilstrencodings.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> -BOOST_AUTO_TEST_SUITE(base32_tests) +BOOST_FIXTURE_TEST_SUITE(base32_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(base32_testvectors) { diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index c242dd26e2..f07dd7a7db 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -13,6 +13,7 @@ #include "uint256.h" #include "util.h" #include "utilstrencodings.h" +#include "test/test_bitcoin.h" #include <boost/foreach.hpp> #include <boost/test/unit_test.hpp> @@ -23,7 +24,7 @@ using namespace json_spirit; extern Array read_json(const std::string& jsondata); -BOOST_AUTO_TEST_SUITE(base58_tests) +BOOST_FIXTURE_TEST_SUITE(base58_tests, BasicTestingSetup) // Goal: test low-level base58 encoding functionality BOOST_AUTO_TEST_CASE(base58_EncodeBase58) diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp index 9e6cb342cc..54c081b0ef 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -3,10 +3,11 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "utilstrencodings.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> -BOOST_AUTO_TEST_SUITE(base64_tests) +BOOST_FIXTURE_TEST_SUITE(base64_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(base64_testvectors) { diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 3d28e06ffc..d738851c1f 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -8,6 +8,7 @@ #include "key.h" #include "uint256.h" #include "util.h" +#include "test/test_bitcoin.h" #include <string> #include <vector> @@ -107,7 +108,7 @@ void RunTest(const TestVector &test) { } } -BOOST_AUTO_TEST_SUITE(bip32_tests) +BOOST_FIXTURE_TEST_SUITE(bip32_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(bip32_test1) { RunTest(test1); diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp index 64d9909b98..73a146f05c 100644 --- a/src/test/bloom_tests.cpp +++ b/src/test/bloom_tests.cpp @@ -13,6 +13,7 @@ #include "uint256.h" #include "util.h" #include "utilstrencodings.h" +#include "test/test_bitcoin.h" #include <vector> @@ -21,7 +22,7 @@ using namespace std; -BOOST_AUTO_TEST_SUITE(bloom_tests) +BOOST_FIXTURE_TEST_SUITE(bloom_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(bloom_create_insert_serialize) { diff --git a/src/test/checkblock_tests.cpp b/src/test/checkblock_tests.cpp index a4121caa8b..7abfad151e 100644 --- a/src/test/checkblock_tests.cpp +++ b/src/test/checkblock_tests.cpp @@ -11,6 +11,7 @@ #include "clientversion.h" #include "main.h" #include "utiltime.h" +#include "test/test_bitcoin.h" #include <cstdio> @@ -19,7 +20,7 @@ #include <boost/test/unit_test.hpp> -BOOST_AUTO_TEST_SUITE(CheckBlock_tests) +BOOST_FIXTURE_TEST_SUITE(CheckBlock_tests, BasicTestingSetup) bool read_block(const std::string& filename, CBlock& block) { diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 3ecd301bc7..2e2cc2214b 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -5,6 +5,7 @@ #include "coins.h" #include "random.h" #include "uint256.h" +#include "test/test_bitcoin.h" #include <vector> #include <map> @@ -60,7 +61,7 @@ public: }; } -BOOST_AUTO_TEST_SUITE(coins_tests) +BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup) static const unsigned int NUM_SIMULATION_ITERATIONS = 40000; diff --git a/src/test/compress_tests.cpp b/src/test/compress_tests.cpp index b4e4f2046f..376ae93681 100644 --- a/src/test/compress_tests.cpp +++ b/src/test/compress_tests.cpp @@ -4,6 +4,7 @@ #include "compressor.h" #include "util.h" +#include "test/test_bitcoin.h" #include <stdint.h> @@ -21,7 +22,7 @@ // amounts 50 .. 21000000 #define NUM_MULTIPLES_50BTC 420000 -BOOST_AUTO_TEST_SUITE(compress_tests) +BOOST_FIXTURE_TEST_SUITE(compress_tests, BasicTestingSetup) bool static TestEncode(uint64_t in) { return in == CTxOutCompressor::DecompressAmount(CTxOutCompressor::CompressAmount(in)); diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index d5e595cd8a..aeb2a5caa3 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -10,13 +10,14 @@ #include "crypto/hmac_sha512.h" #include "random.h" #include "utilstrencodings.h" +#include "test/test_bitcoin.h" #include <vector> #include <boost/assign/list_of.hpp> #include <boost/test/unit_test.hpp> -BOOST_AUTO_TEST_SUITE(crypto_tests) +BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup) template<typename Hasher, typename In, typename Out> void TestVector(const Hasher &h, const In &in, const Out &out) { diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index 5fb0f4ccdd..a0c5592a95 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "util.h" +#include "test/test_bitcoin.h" #include <string> #include <vector> @@ -11,7 +12,7 @@ #include <boost/foreach.hpp> #include <boost/test/unit_test.hpp> -BOOST_AUTO_TEST_SUITE(getarg_tests) +BOOST_FIXTURE_TEST_SUITE(getarg_tests, BasicTestingSetup) static void ResetArgs(const std::string& strArg) { diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp index f1ad25e6ee..e5d2e5a439 100644 --- a/src/test/hash_tests.cpp +++ b/src/test/hash_tests.cpp @@ -4,6 +4,7 @@ #include "hash.h" #include "utilstrencodings.h" +#include "test/test_bitcoin.h" #include <vector> @@ -11,7 +12,7 @@ using namespace std; -BOOST_AUTO_TEST_SUITE(hash_tests) +BOOST_FIXTURE_TEST_SUITE(hash_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(murmurhash3) { diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 1333aba471..13ca949469 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -9,6 +9,7 @@ #include "uint256.h" #include "util.h" #include "utilstrencodings.h" +#include "test/test_bitcoin.h" #include <string> #include <vector> @@ -58,7 +59,7 @@ void dumpKeyInfo(uint256 privkey) #endif -BOOST_AUTO_TEST_SUITE(key_tests) +BOOST_FIXTURE_TEST_SUITE(key_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(key_test1) { diff --git a/src/test/main_tests.cpp b/src/test/main_tests.cpp index 1927f3deab..9ec533bcca 100644 --- a/src/test/main_tests.cpp +++ b/src/test/main_tests.cpp @@ -23,4 +23,21 @@ BOOST_AUTO_TEST_CASE(subsidy_limit_test) BOOST_CHECK(nSum == 2099999997690000ULL); } +bool ReturnFalse() { return false; } +bool ReturnTrue() { return true; } + +BOOST_AUTO_TEST_CASE(test_combiner_all) +{ + boost::signals2::signal<bool (), CombinerAll> Test; + BOOST_CHECK(Test()); + Test.connect(&ReturnFalse); + BOOST_CHECK(!Test()); + Test.connect(&ReturnTrue); + BOOST_CHECK(!Test()); + Test.disconnect(&ReturnFalse); + BOOST_CHECK(Test()); + Test.disconnect(&ReturnTrue); + BOOST_CHECK(Test()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/mruset_tests.cpp b/src/test/mruset_tests.cpp index 813ec9b8b2..bd4e9c1d38 100644 --- a/src/test/mruset_tests.cpp +++ b/src/test/mruset_tests.cpp @@ -6,6 +6,7 @@ #include "random.h" #include "util.h" +#include "test/test_bitcoin.h" #include <set> @@ -34,7 +35,7 @@ public: } }; -BOOST_AUTO_TEST_SUITE(mruset_tests) +BOOST_FIXTURE_TEST_SUITE(mruset_tests, BasicTestingSetup) // Test that an mruset behaves like a set, as long as no more than MAX_SIZE elements are in it BOOST_AUTO_TEST_CASE(mruset_like_set) diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 431ad995de..6b189a6b55 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -10,6 +10,7 @@ #include "script/interpreter.h" #include "script/sign.h" #include "uint256.h" +#include "test/test_bitcoin.h" #ifdef ENABLE_WALLET #include "wallet/wallet_ismine.h" @@ -22,7 +23,7 @@ using namespace std; typedef vector<unsigned char> valtype; -BOOST_AUTO_TEST_SUITE(multisig_tests) +BOOST_FIXTURE_TEST_SUITE(multisig_tests, BasicTestingSetup) CScript sign_multisig(CScript scriptPubKey, vector<CKey> keys, CTransaction transaction, int whichIn) diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 9361459949..cb357d295c 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "netbase.h" +#include "test/test_bitcoin.h" #include <string> @@ -10,7 +11,7 @@ using namespace std; -BOOST_AUTO_TEST_SUITE(netbase_tests) +BOOST_FIXTURE_TEST_SUITE(netbase_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(netbase_networks) { diff --git a/src/test/pmt_tests.cpp b/src/test/pmt_tests.cpp index 4406b08e56..f6d06d6805 100644 --- a/src/test/pmt_tests.cpp +++ b/src/test/pmt_tests.cpp @@ -9,6 +9,7 @@ #include "arith_uint256.h" #include "version.h" #include "random.h" +#include "test/test_bitcoin.h" #include <vector> @@ -28,7 +29,7 @@ public: } }; -BOOST_AUTO_TEST_SUITE(pmt_tests) +BOOST_FIXTURE_TEST_SUITE(pmt_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(pmt_test1) { diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index e42c1b0a8b..7b197c527d 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -5,12 +5,13 @@ #include "main.h" #include "pow.h" #include "util.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> using namespace std; -BOOST_AUTO_TEST_SUITE(pow_tests) +BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup) /* Test calculation of next difficulty target with no constraints applying */ BOOST_AUTO_TEST_CASE(get_next_work) diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp index 464a8fbb8c..f5f7f381d3 100644 --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_tests.cpp @@ -4,9 +4,11 @@ #include "compat/sanity.h" #include "key.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> -BOOST_AUTO_TEST_SUITE(sanity_tests) + +BOOST_FIXTURE_TEST_SUITE(sanity_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(basic_sanity) { diff --git a/src/test/script_P2SH_tests.cpp b/src/test/script_P2SH_tests.cpp index 357c8e93c9..c8cfe28729 100644 --- a/src/test/script_P2SH_tests.cpp +++ b/src/test/script_P2SH_tests.cpp @@ -8,6 +8,7 @@ #include "script/script.h" #include "script/script_error.h" #include "script/sign.h" +#include "test/test_bitcoin.h" #ifdef ENABLE_WALLET #include "wallet/wallet_ismine.h" @@ -47,7 +48,7 @@ Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, Scri } -BOOST_AUTO_TEST_SUITE(script_P2SH_tests) +BOOST_FIXTURE_TEST_SUITE(script_P2SH_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(sign) { diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index e410b59710..c0614cca43 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -13,6 +13,7 @@ #include "script/script_error.h" #include "script/sign.h" #include "util.h" +#include "test/test_bitcoin.h" #if defined(HAVE_CONSENSUS_LIB) #include "script/bitcoinconsensus.h" @@ -53,7 +54,7 @@ read_json(const std::string& jsondata) return v.get_array(); } -BOOST_AUTO_TEST_SUITE(script_tests) +BOOST_FIXTURE_TEST_SUITE(script_tests, BasicTestingSetup) CMutableTransaction BuildCreditingTransaction(const CScript& scriptPubKey) { diff --git a/src/test/scriptnum_tests.cpp b/src/test/scriptnum_tests.cpp index cfbaf26e70..24c7dd3d5a 100644 --- a/src/test/scriptnum_tests.cpp +++ b/src/test/scriptnum_tests.cpp @@ -4,10 +4,13 @@ #include "bignum.h" #include "script/script.h" +#include "test/test_bitcoin.h" + #include <boost/test/unit_test.hpp> #include <limits.h> #include <stdint.h> -BOOST_AUTO_TEST_SUITE(scriptnum_tests) + +BOOST_FIXTURE_TEST_SUITE(scriptnum_tests, BasicTestingSetup) static const int64_t values[] = \ { 0, 1, CHAR_MIN, CHAR_MAX, UCHAR_MAX, SHRT_MIN, USHRT_MAX, INT_MIN, INT_MAX, UINT_MAX, LONG_MIN, LONG_MAX }; diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index de9510d54a..cc8f2b788d 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -5,6 +5,7 @@ #include "serialize.h" #include "streams.h" #include "hash.h" +#include "test/test_bitcoin.h" #include <stdint.h> @@ -12,7 +13,7 @@ using namespace std; -BOOST_AUTO_TEST_SUITE(serialize_tests) +BOOST_FIXTURE_TEST_SUITE(serialize_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(sizes) { diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index ea41dbcde2..afb7a41bbd 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -10,6 +10,7 @@ #include "script/interpreter.h" #include "util.h" #include "version.h" +#include "test/test_bitcoin.h" #include <iostream> @@ -115,7 +116,7 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle) { } } -BOOST_AUTO_TEST_SUITE(sighash_tests) +BOOST_FIXTURE_TEST_SUITE(sighash_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(sighash_test) { diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index 3c8264d89d..b26fed99f2 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -7,6 +7,7 @@ #include "script/script.h" #include "script/standard.h" #include "uint256.h" +#include "test/test_bitcoin.h" #include <vector> @@ -23,7 +24,7 @@ Serialize(const CScript& s) return sSerialized; } -BOOST_AUTO_TEST_SUITE(sigopcount_tests) +BOOST_FIXTURE_TEST_SUITE(sigopcount_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(GetSigOpCount) { diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp index c75e21a2ad..86a4bc6727 100644 --- a/src/test/skiplist_tests.cpp +++ b/src/test/skiplist_tests.cpp @@ -5,6 +5,7 @@ #include "main.h" #include "random.h" #include "util.h" +#include "test/test_bitcoin.h" #include <vector> @@ -12,7 +13,7 @@ #define SKIPLIST_LENGTH 300000 -BOOST_AUTO_TEST_SUITE(skiplist_tests) +BOOST_FIXTURE_TEST_SUITE(skiplist_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(skiplist_test) { diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 35f2ecd287..7d5207b11e 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -26,11 +26,17 @@ CWallet* pwalletMain; extern bool fPrintToConsole; extern void noui_connect(); -TestingSetup::TestingSetup() +BasicTestingSetup::BasicTestingSetup() { fPrintToDebugLog = false; // don't want to write to debug.log file SelectParams(CBaseChainParams::MAIN); - noui_connect(); +} +BasicTestingSetup::~BasicTestingSetup() +{ +} + +TestingSetup::TestingSetup() +{ #ifdef ENABLE_WALLET bitdb.MakeMock(); #endif diff --git a/src/test/test_bitcoin.h b/src/test/test_bitcoin.h index c1448dcdeb..2f75332d40 100644 --- a/src/test/test_bitcoin.h +++ b/src/test/test_bitcoin.h @@ -6,7 +6,19 @@ #include <boost/filesystem.hpp> #include <boost/thread.hpp> -struct TestingSetup { +/** Basic testing setup. + * This just configures logging and chain parameters. + */ +struct BasicTestingSetup { + BasicTestingSetup(); + ~BasicTestingSetup(); +}; + +/** Testing setup that configures a complete environment. + * Included are data directory, coins database, script check threads + * and wallet (if enabled) setup. + */ +struct TestingSetup: public BasicTestingSetup { CCoinsViewDB *pcoinsdbview; boost::filesystem::path pathTemp; boost::thread_group threadGroup; diff --git a/src/test/timedata_tests.cpp b/src/test/timedata_tests.cpp index 58ed963274..887cfb4761 100644 --- a/src/test/timedata_tests.cpp +++ b/src/test/timedata_tests.cpp @@ -3,12 +3,13 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include "timedata.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> using namespace std; -BOOST_AUTO_TEST_SUITE(timedata_tests) +BOOST_FIXTURE_TEST_SUITE(timedata_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(util_MedianFilter) { diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 52adfea992..2a3083316e 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -4,6 +4,7 @@ #include "data/tx_invalid.json.h" #include "data/tx_valid.json.h" +#include "test/test_bitcoin.h" #include "clientversion.h" #include "key.h" @@ -75,7 +76,7 @@ string FormatScriptFlags(unsigned int flags) return ret.substr(0, ret.size() - 1); } -BOOST_AUTO_TEST_SUITE(transaction_tests) +BOOST_FIXTURE_TEST_SUITE(transaction_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(tx_valid) { diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp index 5b33846ba9..426d296a9a 100644 --- a/src/test/uint256_tests.cpp +++ b/src/test/uint256_tests.cpp @@ -4,6 +4,7 @@ #include "arith_uint256.h" #include "uint256.h" #include "version.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> #include <stdint.h> @@ -14,7 +15,7 @@ #include <string> #include <stdio.h> -BOOST_AUTO_TEST_SUITE(uint256_tests) +BOOST_FIXTURE_TEST_SUITE(uint256_tests, BasicTestingSetup) const unsigned char R1Array[] = "\x9c\x52\x4a\xdb\xcf\x56\x11\x12\x2b\x29\x12\x5e\x5d\x35\xd2\xd2" diff --git a/src/test/univalue_tests.cpp b/src/test/univalue_tests.cpp index 5f0c1deb8e..8cecfbf651 100644 --- a/src/test/univalue_tests.cpp +++ b/src/test/univalue_tests.cpp @@ -7,12 +7,13 @@ #include <string> #include <map> #include "univalue/univalue.h" +#include "test/test_bitcoin.h" #include <boost/test/unit_test.hpp> using namespace std; -BOOST_AUTO_TEST_SUITE(univalue_tests) +BOOST_FIXTURE_TEST_SUITE(univalue_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(univalue_constructor) { diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index d829ec228d..3309e2e387 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -10,6 +10,7 @@ #include "sync.h" #include "utilstrencodings.h" #include "utilmoneystr.h" +#include "test/test_bitcoin.h" #include <stdint.h> #include <vector> @@ -18,7 +19,7 @@ using namespace std; -BOOST_AUTO_TEST_SUITE(util_tests) +BOOST_FIXTURE_TEST_SUITE(util_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(util_criticalsection) { diff --git a/src/uint256.cpp b/src/uint256.cpp index 3b1334a032..25148808c6 100644 --- a/src/uint256.cpp +++ b/src/uint256.cpp @@ -45,7 +45,7 @@ void base_blob<BITS>::SetHex(const char* psz) psz++; psz--; unsigned char* p1 = (unsigned char*)data; - unsigned char* pend = p1 + WIDTH * 4; + unsigned char* pend = p1 + WIDTH; while (psz >= pbegin && p1 < pend) { *p1 = ::HexDigit(*psz--); if (psz >= pbegin) { @@ -128,7 +128,7 @@ uint64_t uint256::GetHash(const uint256& salt) const uint32_t a, b, c; const uint32_t *pn = (const uint32_t*)data; const uint32_t *salt_pn = (const uint32_t*)salt.data; - a = b = c = 0xdeadbeef + (WIDTH << 2); + a = b = c = 0xdeadbeef + WIDTH; a += pn[0] ^ salt_pn[0]; b += pn[1] ^ salt_pn[1]; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d097b6a0fa..5502b0b261 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -317,7 +317,7 @@ Value getaddressesbyaccount(const Array& params, bool fHelp) return ret; } -static void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx& wtxNew) +static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew) { CAmount curBalance = pwalletMain->GetBalance(); @@ -335,11 +335,14 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx& CReserveKey reservekey(pwalletMain); CAmount nFeeRequired; std::string strError; - if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError)) { - if (nValue + nFeeRequired > curBalance) - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired))); - else - throw JSONRPCError(RPC_WALLET_ERROR, strError); + vector<CRecipient> vecSend; + int nChangePosRet = -1; + CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; + vecSend.push_back(recipient); + if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) { + if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance()) + strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); + throw JSONRPCError(RPC_WALLET_ERROR, strError); } if (!pwalletMain->CommitTransaction(wtxNew, reservekey)) throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); @@ -347,9 +350,9 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx& Value sendtoaddress(const Array& params, bool fHelp) { - if (fHelp || params.size() < 2 || params.size() > 4) + if (fHelp || params.size() < 2 || params.size() > 5) throw runtime_error( - "sendtoaddress \"bitcoinaddress\" amount ( \"comment\" \"comment-to\" )\n" + "sendtoaddress \"bitcoinaddress\" amount ( \"comment\" \"comment-to\" subtractfeefromamount )\n" "\nSend an amount to a given address. The amount is a real and is rounded to the nearest 0.00000001\n" + HelpRequiringPassphrase() + "\nArguments:\n" @@ -360,11 +363,14 @@ Value sendtoaddress(const Array& params, bool fHelp) "4. \"comment-to\" (string, optional) A comment to store the name of the person or organization \n" " to which you're sending the transaction. This is not part of the \n" " transaction, just kept in your wallet.\n" + "5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n" + " The recipient will receive less bitcoins than you enter in the amount field.\n" "\nResult:\n" "\"transactionid\" (string) The transaction id.\n" "\nExamples:\n" + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"") + + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") ); @@ -384,9 +390,13 @@ Value sendtoaddress(const Array& params, bool fHelp) if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) wtx.mapValue["to"] = params[3].get_str(); + bool fSubtractFeeFromAmount = false; + if (params.size() > 4) + fSubtractFeeFromAmount = params[4].get_bool(); + EnsureWalletIsUnlocked(); - SendMoney(address.Get(), nAmount, wtx); + SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, wtx); return wtx.GetHash().GetHex(); } @@ -840,7 +850,7 @@ Value sendfrom(const Array& params, bool fHelp) if (nAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); - SendMoney(address.Get(), nAmount, wtx); + SendMoney(address.Get(), nAmount, false, wtx); return wtx.GetHash().GetHex(); } @@ -848,9 +858,9 @@ Value sendfrom(const Array& params, bool fHelp) Value sendmany(const Array& params, bool fHelp) { - if (fHelp || params.size() < 2 || params.size() > 4) + if (fHelp || params.size() < 2 || params.size() > 5) throw runtime_error( - "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" )\n" + "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n" "\nSend multiple times. Amounts are double-precision floating point numbers." + HelpRequiringPassphrase() + "\n" "\nArguments:\n" @@ -862,6 +872,14 @@ Value sendmany(const Array& params, bool fHelp) " }\n" "3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n" "4. \"comment\" (string, optional) A comment\n" + "5. subtractfeefromamount (string, optional) A json array with addresses.\n" + " The fee will be equally deducted from the amount of each selected address.\n" + " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" + " If no addresses are specified here, the sender pays the fee.\n" + " [\n" + " \"address\" (string) Subtract fee from this address\n" + " ,...\n" + " ]\n" "\nResult:\n" "\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" " the number of addresses.\n" @@ -870,6 +888,8 @@ Value sendmany(const Array& params, bool fHelp) + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + "\nSend two amounts to two different addresses setting the confirmation and comment:\n" + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") + + "\nSend two amounts to two different addresses, subtract fee from amount:\n" + + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + "\nAs a json rpc call\n" + HelpExampleRpc("sendmany", "\"\", \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"") ); @@ -887,8 +907,12 @@ Value sendmany(const Array& params, bool fHelp) if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) wtx.mapValue["comment"] = params[3].get_str(); + Array subtractFeeFromAmount; + if (params.size() > 4) + subtractFeeFromAmount = params[4].get_array(); + set<CBitcoinAddress> setAddress; - vector<pair<CScript, CAmount> > vecSend; + vector<CRecipient> vecSend; CAmount totalAmount = 0; BOOST_FOREACH(const Pair& s, sendTo) @@ -905,7 +929,13 @@ Value sendmany(const Array& params, bool fHelp) CAmount nAmount = AmountFromValue(s.value_); totalAmount += nAmount; - vecSend.push_back(make_pair(scriptPubKey, nAmount)); + bool fSubtractFeeFromAmount = false; + BOOST_FOREACH(const Value& addr, subtractFeeFromAmount) + if (addr.get_str() == s.name_) + fSubtractFeeFromAmount = true; + + CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; + vecSend.push_back(recipient); } EnsureWalletIsUnlocked(); @@ -918,8 +948,9 @@ Value sendmany(const Array& params, bool fHelp) // Send CReserveKey keyChange(pwalletMain); CAmount nFeeRequired = 0; + int nChangePosRet = -1; string strFailReason; - bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason); + bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); if (!pwalletMain->CommitTransaction(wtx, keyChange)) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5b4add8431..dd33109267 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1549,21 +1549,22 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx* (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet))); } - - - -bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) +bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl) { CAmount nValue = 0; - BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) + unsigned int nSubtractFeeFromAmount = 0; + BOOST_FOREACH (const CRecipient& recipient, vecSend) { - if (nValue < 0) + if (nValue < 0 || recipient.nAmount < 0) { strFailReason = _("Transaction amounts must be positive"); return false; } - nValue += s.second; + nValue += recipient.nAmount; + + if (recipient.fSubtractFeeFromAmount) + nSubtractFeeFromAmount++; } if (vecSend.empty() || nValue < 0) { @@ -1606,16 +1607,40 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, txNew.vin.clear(); txNew.vout.clear(); wtxNew.fFromMe = true; + nChangePosRet = -1; + bool fFirst = true; - CAmount nTotalValue = nValue + nFeeRet; + CAmount nTotalValue = nValue; + if (nSubtractFeeFromAmount == 0) + nTotalValue += nFeeRet; double dPriority = 0; // vouts to the payees - BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) + BOOST_FOREACH (const CRecipient& recipient, vecSend) { - CTxOut txout(s.second, s.first); + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + + if (recipient.fSubtractFeeFromAmount) + { + txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient + + if (fFirst) // first receiver pays the remainder not divisible by output count + { + fFirst = false; + txout.nValue -= nFeeRet % nSubtractFeeFromAmount; + } + } + if (txout.IsDust(::minRelayTxFee)) { - strFailReason = _("Transaction amount too small"); + if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) + { + if (txout.nValue < 0) + strFailReason = _("The transaction amount is too small to pay the fee"); + else + strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); + } + else + strFailReason = _("Transaction amount too small"); return false; } txNew.vout.push_back(txout); @@ -1642,7 +1667,9 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, dPriority += (double)nCredit * age; } - CAmount nChange = nValueIn - nValue - nFeeRet; + CAmount nChange = nValueIn - nValue; + if (nSubtractFeeFromAmount == 0) + nChange -= nFeeRet; if (nChange > 0) { @@ -1676,6 +1703,28 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, CTxOut newTxOut(nChange, scriptChange); + // We do not move dust-change to fees, because the sender would end up paying more than requested. + // This would be against the purpose of the all-inclusive feature. + // So instead we raise the change and deduct from the recipient. + if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee)) + { + CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue; + newTxOut.nValue += nDust; // raise change until no more dust + for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient + { + if (vecSend[i].fSubtractFeeFromAmount) + { + txNew.vout[i].nValue -= nDust; + if (txNew.vout[i].IsDust(::minRelayTxFee)) + { + strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); + return false; + } + break; + } + } + } + // Never create dust outputs; if we would, just // add the dust to the fee. if (newTxOut.IsDust(::minRelayTxFee)) @@ -1686,7 +1735,8 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, else { // Insert change txn at random position: - vector<CTxOut>::iterator position = txNew.vout.begin()+GetRandInt(txNew.vout.size()+1); + nChangePosRet = GetRandInt(txNew.vout.size()+1); + vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosRet; txNew.vout.insert(position, newTxOut); } } @@ -1755,15 +1805,8 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, } } } - return true; -} -bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) -{ - vector< pair<CScript, CAmount> > vecSend; - vecSend.push_back(make_pair(scriptPubKey, nValue)); - return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl); + return true; } /** diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 093336080e..b2973250a4 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -103,6 +103,12 @@ public: StringMap destdata; }; +struct CRecipient +{ + CScript scriptPubKey; + CAmount nAmount; + bool fSubtractFeeFromAmount; +}; typedef std::map<std::string, std::string> mapValue_t; @@ -611,10 +617,8 @@ public: CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; - bool CreateTransaction(const std::vector<std::pair<CScript, CAmount> >& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); - bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); + bool CreateTransaction(const std::vector<CRecipient>& vecSend, + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); static CFeeRate minTxFee; |