diff options
40 files changed, 1100 insertions, 559 deletions
diff --git a/README b/README new file mode 100644 index 0000000000..9edd5a2121 --- /dev/null +++ b/README @@ -0,0 +1,21 @@ +AuthServiceProxy is an improved version of python-jsonrpc. + +It includes the following generic improvements: + +- HTTP connections persist for the life of the AuthServiceProxy object +- sends protocol 'version', per JSON-RPC 1.1 +- sends proper, incrementing 'id' +- uses standard Python json lib + +It also includes the following bitcoin-specific details: + +- sends Basic HTTP authentication headers +- parses all JSON numbers that look like floats as Decimal + +Installation: + +- change the first line of setup.py to point to the directory of your installation of python 2.* +- run setup.py + +Note: This will only install bitcoinrpc. If you also want to install jsonrpc to preserve +backwards compatibility, you have to replace 'bitcoinrpc' with 'jsonrpc' in setup.py and run it again. diff --git a/bitcoinrpc/.gitignore b/bitcoinrpc/.gitignore new file mode 100644 index 0000000000..2f78cf5b66 --- /dev/null +++ b/bitcoinrpc/.gitignore @@ -0,0 +1,2 @@ +*.pyc + diff --git a/bitcoinrpc/__init__.py b/bitcoinrpc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/bitcoinrpc/__init__.py diff --git a/bitcoinrpc/authproxy.py b/bitcoinrpc/authproxy.py new file mode 100644 index 0000000000..2914477170 --- /dev/null +++ b/bitcoinrpc/authproxy.py @@ -0,0 +1,140 @@ + +""" + Copyright 2011 Jeff Garzik + + AuthServiceProxy has the following improvements over python-jsonrpc's + ServiceProxy class: + + - HTTP connections persist for the life of the AuthServiceProxy object + (if server supports HTTP/1.1) + - sends protocol 'version', per JSON-RPC 1.1 + - sends proper, incrementing 'id' + - sends Basic HTTP authentication headers + - parses all JSON numbers that look like floats as Decimal + - uses standard Python json lib + + Previous copyright, from python-jsonrpc/jsonrpc/proxy.py: + + Copyright (c) 2007 Jan-Klaas Kollhof + + This file is part of jsonrpc. + + jsonrpc is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This software is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this software; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +try: + import http.client as httplib +except ImportError: + import httplib +import base64 +import json +import decimal +try: + import urllib.parse as urlparse +except ImportError: + import urlparse + +USER_AGENT = "AuthServiceProxy/0.1" + +HTTP_TIMEOUT = 30 + + +class JSONRPCException(Exception): + def __init__(self, rpc_error): + Exception.__init__(self) + self.error = rpc_error + + +class AuthServiceProxy(object): + def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None): + self.__service_url = service_url + self.__service_name = service_name + self.__url = urlparse.urlparse(service_url) + if self.__url.port is None: + port = 80 + else: + port = self.__url.port + self.__id_count = 0 + (user, passwd) = (self.__url.username, self.__url.password) + try: + user = user.encode('utf8') + except AttributeError: + pass + try: + passwd = passwd.encode('utf8') + except AttributeError: + pass + authpair = user + b':' + passwd + self.__auth_header = b'Basic ' + base64.b64encode(authpair) + + if connection: + # Callables re-use the connection of the original proxy + self.__conn = connection + elif self.__url.scheme == 'https': + self.__conn = httplib.HTTPSConnection(self.__url.hostname, port, + None, None, False, + timeout) + else: + self.__conn = httplib.HTTPConnection(self.__url.hostname, port, + False, timeout) + + def __getattr__(self, name): + if name.startswith('__') and name.endswith('__'): + # Python internal stuff + raise AttributeError + if self.__service_name is not None: + name = "%s.%s" % (self.__service_name, name) + return AuthServiceProxy(self.__service_url, name, connection=self.__conn) + + def __call__(self, *args): + self.__id_count += 1 + + postdata = json.dumps({'version': '1.1', + 'method': self.__service_name, + 'params': args, + 'id': self.__id_count}) + self.__conn.request('POST', self.__url.path, postdata, + {'Host': self.__url.hostname, + 'User-Agent': USER_AGENT, + 'Authorization': self.__auth_header, + 'Content-type': 'application/json'}) + + response = self._get_response() + if response['error'] is not None: + raise JSONRPCException(response['error']) + elif 'result' not in response: + raise JSONRPCException({ + 'code': -343, 'message': 'missing JSON-RPC result'}) + else: + return response['result'] + + def _batch(self, rpc_call_list): + postdata = json.dumps(list(rpc_call_list)) + self.__conn.request('POST', self.__url.path, postdata, + {'Host': self.__url.hostname, + 'User-Agent': USER_AGENT, + 'Authorization': self.__auth_header, + 'Content-type': 'application/json'}) + + return self._get_response() + + def _get_response(self): + http_response = self.__conn.getresponse() + if http_response is None: + raise JSONRPCException({ + 'code': -342, 'message': 'missing HTTP response from server'}) + + return json.loads(http_response.read().decode('utf8'), + parse_float=decimal.Decimal) diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index bf355fd828..01a5c3c0bf 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -48,7 +48,7 @@ script: | mkdir -p distsrc cd distsrc tar --strip-components=1 -xf ../$DISTNAME - ./configure --prefix=$STAGING --bindir=$BINDIR --with-protoc-bindir=$STAGING/host/bin --with-boost=$STAGING --disable-maintainer-mode --disable-dependency-tracking PKG_CONFIG_PATH="$STAGING/lib/pkgconfig" CPPFLAGS="-I$STAGING/include ${OPTFLAGS}" LDFLAGS="-L$STAGING/lib ${OPTFLAGS}" CXXFLAGS="-frandom-seed=bitcoin ${OPTFLAGS}" BOOST_CHRONO_EXTRALIBS="-lrt" + ./configure --enable-upnp-default --prefix=$STAGING --bindir=$BINDIR --with-protoc-bindir=$STAGING/host/bin --with-boost=$STAGING --disable-maintainer-mode --disable-dependency-tracking PKG_CONFIG_PATH="$STAGING/lib/pkgconfig" CPPFLAGS="-I$STAGING/include ${OPTFLAGS}" LDFLAGS="-L$STAGING/lib ${OPTFLAGS}" CXXFLAGS="-frandom-seed=bitcoin ${OPTFLAGS}" BOOST_CHRONO_EXTRALIBS="-lrt" make $MAKEOPTS make $MAKEOPTS install-strip diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml index 6e43c21823..ecda73e44e 100644 --- a/contrib/gitian-descriptors/gitian-win.yml +++ b/contrib/gitian-descriptors/gitian-win.yml @@ -77,7 +77,7 @@ script: | mkdir -p distsrc cd distsrc tar --strip-components=1 -xf $HOME/build/bitcoin/$DISTNAME - ./configure --bindir=$BINDIR --prefix=$STAGING --host=$HOST --with-qt-plugindir=$STAGING/plugins --with-qt-incdir=$STAGING/include --with-qt-bindir=$STAGING/host/bin --with-boost=$STAGING --disable-maintainer-mode --with-protoc-bindir=$STAGING/host/bin --disable-dependency-tracking CPPFLAGS="-I$STAGING/include ${OPTFLAGS}" LDFLAGS="-L$STAGING/lib ${OPTFLAGS}" CXXFLAGS="-frandom-seed=bitcoin ${OPTFLAGS}" + ./configure --enable-upnp-default --bindir=$BINDIR --prefix=$STAGING --host=$HOST --with-qt-plugindir=$STAGING/plugins --with-qt-incdir=$STAGING/include --with-qt-bindir=$STAGING/host/bin --with-boost=$STAGING --disable-maintainer-mode --with-protoc-bindir=$STAGING/host/bin --disable-dependency-tracking CPPFLAGS="-I$STAGING/include ${OPTFLAGS}" LDFLAGS="-L$STAGING/lib ${OPTFLAGS}" CXXFLAGS="-frandom-seed=bitcoin ${OPTFLAGS}" export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 export FAKETIME=$REFERENCE_DATETIME make $MAKEOPTS diff --git a/doc/build-osx.md b/doc/build-osx.md index fcb9a37f3d..faa31cb023 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -5,8 +5,9 @@ This guide will show you how to build bitcoind(headless client) for OSX. Notes ----- -* Tested on OS X 10.5 through 10.9 on Intel processors only. PPC is not -supported because it is big-endian. +* Tested on OS X 10.6 through 10.9 on 64-bit Intel processors only. +Older OSX releases or 32-bit processors are no longer supported. + * All of the commands should be executed in a Terminal application. The built-in one is located in `/Applications/Utilities`. @@ -47,6 +48,10 @@ Installing the dependencies using MacPorts is very straightforward. sudo port install boost db48@+no_java openssl miniupnpc autoconf pkgconfig automake +Optional: install Qt4 + + sudo port install qt4-mac qrencode protobuf-cpp + ### Building `bitcoind` 1. Clone the github tree to get the source code and go into the directory. @@ -54,7 +59,7 @@ Installing the dependencies using MacPorts is very straightforward. git clone git@github.com:bitcoin/bitcoin.git bitcoin cd bitcoin -2. Build bitcoind: +2. Build bitcoind (and Bitcoin-Qt, if configured): ./autogen.sh ./configure @@ -102,35 +107,33 @@ Rerunning "openssl version" should now return the correct version. Creating a release build ------------------------ +You can ignore this section if you are building `bitcoind` for your own use. -A bitcoind binary is not included in the Bitcoin-Qt.app bundle. You can ignore -this section if you are building `bitcoind` for your own use. +bitcoind/bitcoin-cli binaries are not included in the Bitcoin-Qt.app bundle. -If you are building `bitcoind` for others, your build machine should be set up +If you are building `bitcoind` or `Bitcoin-Qt` for others, your build machine should be set up as follows for maximum compatibility: All dependencies should be compiled with these flags: - -mmacosx-version-min=10.5 -arch i386 -isysroot /Developer/SDKs/MacOSX10.5.sdk + -mmacosx-version-min=10.6 + -arch x86_64 + -isysroot $(xcode-select --print-path)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.6.sdk For MacPorts, that means editing your macports.conf and setting `macosx_deployment_target` and `build_arch`: - macosx_deployment_target=10.5 - build_arch=i386 + macosx_deployment_target=10.6 + build_arch=x86_64 ... and then uninstalling and re-installing, or simply rebuilding, all ports. As of December 2012, the `boost` port does not obey `macosx_deployment_target`. Download `http://gavinandresen-bitcoin.s3.amazonaws.com/boost_macports_fix.zip` -for a fix. Some ports also seem to obey either `build_arch` or -`macosx_deployment_target`, but not both at the same time. For example, building -on an OS X 10.6 64-bit machine fails. Official release builds of Bitcoin-Qt are -compiled on an OS X 10.6 32-bit machine to workaround that problem. - -Once dependencies are compiled, creating `Bitcoin-Qt.app` is easy: +for a fix. - make -f Makefile.osx RELEASE=1 +Once dependencies are compiled, see release-process.md for how the Bitcoin-Qt.app +bundle is packaged and signed to create the .dmg disk image that is distributed. Running ------- @@ -145,10 +148,13 @@ commands: chmod 600 "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" When next you run it, it will start downloading the blockchain, but it won't -output anything while it's doing this. This process may take several hours. +output anything while it's doing this. This process may take several hours; +you can monitor its process by looking at the debug.log file, like this: + + tail -f $HOME/Library/Application\ Support/Bitcoin/debug.log Other commands: - ./bitcoind --help # for a list of command-line options. ./bitcoind -daemon # to start the bitcoin daemon. - ./bitcoind help # When the daemon is running, to get a list of RPC commands + ./bitcoin-cli --help # for a list of command-line options. + ./bitcoin-cli help # When the daemon is running, to get a list of RPC commands diff --git a/doc/build-unix.md b/doc/build-unix.md index 6f7a022817..c11c0138a4 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -65,6 +65,10 @@ for Ubuntu 12.04 and later: sudo apt-get install libboost-all-dev db4.8 packages are available [here](https://launchpad.net/~bitcoin/+archive/bitcoin). + You can add the repository using the following command: + + sudo add-apt-repository ppa:bitcoin/bitcoin + sudo apt-get update Ubuntu 12.04 and later have packages for libdb5.1-dev and libdb5.1++-dev, but using these will break binary wallet compatibility, and is not recommended. diff --git a/doc/release-process.md b/doc/release-process.md index 459819e596..095d20f9ea 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -103,21 +103,23 @@ repackage gitian builds for release as stand-alone zip/tar/installer exe **Perform Mac build:** - OSX binaries are created by Gavin Andresen on a 32-bit, OSX 10.6 machine. + OSX binaries are created by Gavin Andresen on a 64-bit, OSX 10.6 machine. - qmake RELEASE=1 USE_UPNP=1 USE_QRCODE=1 bitcoin-qt.pro + ./autogen.sh + SDK=$(xcode-select --print-path)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.6.sdk + CXXFLAGS="-mmacosx-version-min=10.6 -isysroot $SDK" ./configure --enable-upnp-default make export QTDIR=/opt/local/share/qt4 # needed to find translations/qt_*.qm files T=$(contrib/qt_translations.py $QTDIR/translations src/qt/locale) - python2.7 share/qt/clean_mac_info_plist.py - python2.7 contrib/macdeploy/macdeployqtplus Bitcoin-Qt.app -add-qt-tr $T -dmg -fancy contrib/macdeploy/fancy.plist + export CODESIGNARGS='--keychain ...path_to_keychain --sign "Developer ID Application: BITCOIN FOUNDATION, INC., THE"' + python2.7 contrib/macdeploy/macdeployqtplus Bitcoin-Qt.app -sign -add-qt-tr $T -dmg -fancy contrib/macdeploy/fancy.plist Build output expected: Bitcoin-Qt.dmg ###Next steps: -* Code-sign Windows -setup.exe (in a Windows virtual machine) and - OSX Bitcoin-Qt.app (Note: only Gavin has the code-signing keys currently) +* Code-sign Windows -setup.exe (in a Windows virtual machine using signtool) + Note: only Gavin has the code-signing keys currently. * upload builds to SourceForge diff --git a/jsonrpc/__init__.py b/jsonrpc/__init__.py new file mode 100644 index 0000000000..8441fa3120 --- /dev/null +++ b/jsonrpc/__init__.py @@ -0,0 +1,2 @@ +from .json import loads, dumps, JSONEncodeException, JSONDecodeException +from jsonrpc.proxy import ServiceProxy, JSONRPCException diff --git a/jsonrpc/authproxy.py b/jsonrpc/authproxy.py new file mode 100644 index 0000000000..e90ef361d0 --- /dev/null +++ b/jsonrpc/authproxy.py @@ -0,0 +1,3 @@ +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException + +__all__ = ['AuthServiceProxy', 'JSONRPCException'] diff --git a/jsonrpc/json.py b/jsonrpc/json.py new file mode 100644 index 0000000000..95398630f7 --- /dev/null +++ b/jsonrpc/json.py @@ -0,0 +1,9 @@ +_json = __import__('json') +loads = _json.loads +dumps = _json.dumps +if hasattr(_json, 'JSONEncodeException'): + JSONEncodeException = _json.JSONEncodeException + JSONDecodeException = _json.JSONDecodeException +else: + JSONEncodeException = TypeError + JSONDecodeException = ValueError diff --git a/jsonrpc/proxy.py b/jsonrpc/proxy.py new file mode 100644 index 0000000000..0d2be1e93b --- /dev/null +++ b/jsonrpc/proxy.py @@ -0,0 +1 @@ +from bitcoinrpc.authproxy import AuthServiceProxy as ServiceProxy, JSONRPCException diff --git a/qa/rpc-tests/.gitignore b/qa/rpc-tests/.gitignore new file mode 100644 index 0000000000..cb41d94423 --- /dev/null +++ b/qa/rpc-tests/.gitignore @@ -0,0 +1,2 @@ +*.pyc +cache diff --git a/qa/rpc-tests/README.md b/qa/rpc-tests/README.md index ee9e8b35ca..835ff11057 100644 --- a/qa/rpc-tests/README.md +++ b/qa/rpc-tests/README.md @@ -1,25 +1,36 @@ Regression tests of RPC interface ================================= -wallet.sh : Exercise wallet send/receive code. +python-bitcoinrpc: git subtree of https://github.com/jgarzik/python-bitcoinrpc +Changes to python-bitcoinrpc should be made upstream, and then +pulled here using git subtree -walletbackup.sh : Exercise wallet backup / dump / import +skeleton.py : Copy this to create new regression tests. -txnmall.sh : Test proper accounting of malleable transactions +listtransactions.py : Tests for the listtransactions RPC call + +util.py : generally useful functions +Bash-based tests, to be ported to Python: +----------------------------------------- +wallet.sh : Exercise wallet send/receive code. +walletbackup.sh : Exercise wallet backup / dump / import +txnmall.sh : Test proper accounting of malleable transactions conflictedbalance.sh : More testing of malleable transaction handling -util.sh : useful re-usable bash functions +Notes +===== +A 200-block -regtest blockchain and wallets for four nodes +is created the first time a regression test is run and +is stored in the cache/ directory. Each node has 25 mature +blocks (25*50=1250 BTC) in their wallet. -Tips for creating new tests -=========================== +After the first run, the cache/ blockchain and wallets are +copied into a temporary directory and used as the initial +test state. -To cleanup after a failed or interrupted test: +If you get into a bad state, you should be able +to recover with: + rm -rf cache killall bitcoind - rm -rf test.* - -The most difficult part of writing reproducible tests is -keeping multiple nodes in sync. See WaitBlocks, -WaitPeers, and WaitMemPools for how other tests -deal with this. diff --git a/qa/rpc-tests/conflictedbalance.sh b/qa/rpc-tests/conflictedbalance.sh new file mode 100755 index 0000000000..9d854d2d87 --- /dev/null +++ b/qa/rpc-tests/conflictedbalance.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash + +# Test marking of spent outputs + +# Create a transaction graph with four transactions, +# A/B/C/D +# C spends A +# D spends B and C + +# Then simulate C being mutated, to create C' +# that is mined. +# A is still (correctly) considered spent. +# B should be treated as unspent + +if [ $# -lt 1 ]; then + echo "Usage: $0 path_to_binaries" + echo "e.g. $0 ../../src" + exit 1 +fi + +set -f + +BITCOIND=${1}/bitcoind +CLI=${1}/bitcoin-cli + +DIR="${BASH_SOURCE%/*}" +SENDANDWAIT="${DIR}/send.sh" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/util.sh" + +D=$(mktemp -d test.XXXXX) + +# Two nodes; one will play the part of merchant, the +# other an evil transaction-mutating miner. + +D1=${D}/node1 +CreateDataDir $D1 port=11000 rpcport=11001 +B1ARGS="-datadir=$D1 -debug=mempool" +$BITCOIND $B1ARGS & +B1PID=$! + +D2=${D}/node2 +CreateDataDir $D2 port=11010 rpcport=11011 +B2ARGS="-datadir=$D2 -debug=mempool" +$BITCOIND $B2ARGS & +B2PID=$! + +# Wait until all four nodes are at the same block number +function WaitBlocks { + while : + do + sleep 1 + declare -i BLOCKS1=$( GetBlocks $B1ARGS ) + declare -i BLOCKS2=$( GetBlocks $B2ARGS ) + if (( BLOCKS1 == BLOCKS2 )) + then + break + fi + done +} + +# Wait until node has $N peers +function WaitPeers { + while : + do + declare -i PEERS=$( $CLI $1 getconnectioncount ) + if (( PEERS == "$2" )) + then + break + fi + sleep 1 + done +} + +echo "Generating test blockchain..." + +# Start with B2 connected to B1: +$CLI $B2ARGS addnode 127.0.0.1:11000 onetry +WaitPeers "$B1ARGS" 1 + +# 2 block, 50 XBT each == 100 XBT +# These will be transactions "A" and "B" +$CLI $B1ARGS setgenerate true 2 + +WaitBlocks +# 100 blocks, 0 mature == 0 XBT +$CLI $B2ARGS setgenerate true 100 +WaitBlocks + +CheckBalance "$B1ARGS" 100 +CheckBalance "$B2ARGS" 0 + +# restart B2 with no connection +$CLI $B2ARGS stop > /dev/null 2>&1 +wait $B2PID +$BITCOIND $B2ARGS & +B2PID=$! + +B1ADDRESS=$( $CLI $B1ARGS getnewaddress ) +B2ADDRESS=$( $CLI $B2ARGS getnewaddress ) + +# Transaction C: send-to-self, spend A +TXID_C=$( $CLI $B1ARGS sendtoaddress $B1ADDRESS 50.0) + +# Transaction D: spends B and C +TXID_D=$( $CLI $B1ARGS sendtoaddress $B2ADDRESS 100.0) + +CheckBalance "$B1ARGS" 0 + +# Mutate TXID_C and add it to B2's memory pool: +RAWTX_C=$( $CLI $B1ARGS getrawtransaction $TXID_C ) + +# ... mutate C to create C' +L=${RAWTX_C:82:2} +NEWLEN=$( printf "%x" $(( 16#$L + 1 )) ) +MUTATEDTX_C=${RAWTX_C:0:82}${NEWLEN}4c${RAWTX_C:84} +# ... give mutated tx1 to B2: +MUTATEDTXID=$( $CLI $B2ARGS sendrawtransaction $MUTATEDTX_C ) + +echo "TXID_C: " $TXID_C +echo "Mutated: " $MUTATEDTXID + +# Re-connect nodes, and have both nodes mine some blocks: +$CLI $B2ARGS addnode 127.0.0.1:11000 onetry +WaitPeers "$B1ARGS" 1 + +# Having B2 mine the next block puts the mutated +# transaction C in the chain: +$CLI $B2ARGS setgenerate true 1 +WaitBlocks + +# B1 should still be able to spend 100, because D is conflicted +# so does not count as a spend of B +CheckBalance "$B1ARGS" 100 + +$CLI $B2ARGS stop > /dev/null 2>&1 +wait $B2PID +$CLI $B1ARGS stop > /dev/null 2>&1 +wait $B1PID + +echo "Tests successful, cleaning up" +rm -rf $D +exit 0 diff --git a/qa/rpc-tests/listtransactions.py b/qa/rpc-tests/listtransactions.py new file mode 100755 index 0000000000..fec3acfbb3 --- /dev/null +++ b/qa/rpc-tests/listtransactions.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python + +# Exercise the listtransactions API + +# Add python-bitcoinrpc to module search path: +import os +import sys +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "python-bitcoinrpc")) + +import json +import shutil +import subprocess +import tempfile +import traceback + +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException +from util import * + + +def check_array_result(object_array, to_match, expected): + """ + Pass in array of JSON objects, a dictionary with key/value pairs + to match against, and another dictionary with expected key/value + pairs. + """ + num_matched = 0 + for item in object_array: + all_match = True + for key,value in to_match.items(): + if item[key] != value: + all_match = False + if not all_match: + continue + for key,value in expected.items(): + if item[key] != value: + raise AssertionError("%s : expected %s=%s"%(str(item), str(key), str(value))) + num_matched = num_matched+1 + if num_matched == 0: + raise AssertionError("No objects matched %s"%(str(to_match))) + +def run_test(nodes): + # Simple send, 0 to 1: + txid = nodes[0].sendtoaddress(nodes[1].getnewaddress(), 0.1) + sync_mempools(nodes) + check_array_result(nodes[0].listtransactions(), + {"txid":txid}, + {"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":0}) + check_array_result(nodes[1].listtransactions(), + {"txid":txid}, + {"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":0}) + # mine a block, confirmations should change: + nodes[0].setgenerate(True, 1) + sync_blocks(nodes) + check_array_result(nodes[0].listtransactions(), + {"txid":txid}, + {"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":1}) + check_array_result(nodes[1].listtransactions(), + {"txid":txid}, + {"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":1}) + + # send-to-self: + txid = nodes[0].sendtoaddress(nodes[0].getnewaddress(), 0.2) + check_array_result(nodes[0].listtransactions(), + {"txid":txid, "category":"send"}, + {"amount":Decimal("-0.2")}) + check_array_result(nodes[0].listtransactions(), + {"txid":txid, "category":"receive"}, + {"amount":Decimal("0.2")}) + + # sendmany from node1: twice to self, twice to node2: + send_to = { nodes[0].getnewaddress() : 0.11, nodes[1].getnewaddress() : 0.22, + nodes[0].getaccountaddress("from1") : 0.33, nodes[1].getaccountaddress("toself") : 0.44 } + txid = nodes[1].sendmany("", send_to) + sync_mempools(nodes) + check_array_result(nodes[1].listtransactions(), + {"category":"send","amount":Decimal("-0.11")}, + {"txid":txid} ) + check_array_result(nodes[0].listtransactions(), + {"category":"receive","amount":Decimal("0.11")}, + {"txid":txid} ) + check_array_result(nodes[1].listtransactions(), + {"category":"send","amount":Decimal("-0.22")}, + {"txid":txid} ) + check_array_result(nodes[1].listtransactions(), + {"category":"receive","amount":Decimal("0.22")}, + {"txid":txid} ) + check_array_result(nodes[1].listtransactions(), + {"category":"send","amount":Decimal("-0.33")}, + {"txid":txid} ) + check_array_result(nodes[0].listtransactions(), + {"category":"receive","amount":Decimal("0.33")}, + {"txid":txid, "account" : "from1"} ) + check_array_result(nodes[1].listtransactions(), + {"category":"send","amount":Decimal("-0.44")}, + {"txid":txid, "account" : ""} ) + check_array_result(nodes[1].listtransactions(), + {"category":"receive","amount":Decimal("0.44")}, + {"txid":txid, "account" : "toself"} ) + + +def main(): + import optparse + + parser = optparse.OptionParser(usage="%prog [options]") + parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true", + help="Leave bitcoinds and test.* datadir on exit or error") + parser.add_option("--srcdir", dest="srcdir", default="../../src", + help="Source directory containing bitcoind/bitcoin-cli (default: %default%)") + parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"), + help="Root directory for datadirs") + (options, args) = parser.parse_args() + + os.environ['PATH'] = options.srcdir+":"+os.environ['PATH'] + + check_json_precision() + + success = False + try: + print("Initializing test directory "+options.tmpdir) + if not os.path.isdir(options.tmpdir): + os.makedirs(options.tmpdir) + initialize_chain(options.tmpdir) + + nodes = start_nodes(2, options.tmpdir) + connect_nodes(nodes[1], 0) + sync_blocks(nodes) + run_test(nodes) + + success = True + + except AssertionError as e: + print("Assertion failed: "+e.message) + except Exception as e: + print("Unexpected exception caught during testing: "+str(e)) + stack = traceback.extract_tb(sys.exc_info()[2]) + print(stack[-1]) + + if not options.nocleanup: + print("Cleaning up") + stop_nodes() + shutil.rmtree(options.tmpdir) + + if success: + print("Tests successful") + sys.exit(0) + else: + print("Failed") + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/qa/rpc-tests/skeleton.py b/qa/rpc-tests/skeleton.py new file mode 100755 index 0000000000..0bace6f4eb --- /dev/null +++ b/qa/rpc-tests/skeleton.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python + +# Skeleton for python-based regression tests using +# JSON-RPC + + +# Add python-bitcoinrpc to module search path: +import os +import sys +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "python-bitcoinrpc")) + +import json +import shutil +import subprocess +import tempfile +import traceback + +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException +from util import * + + +def run_test(nodes): + # Replace this as appropriate + for node in nodes: + assert_equal(node.getblockcount(), 200) + assert_equal(node.getbalance(), 25*50) + +def main(): + import optparse + + parser = optparse.OptionParser(usage="%prog [options]") + parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true", + help="Leave bitcoinds and test.* datadir on exit or error") + parser.add_option("--srcdir", dest="srcdir", default="../../src", + help="Source directory containing bitcoind/bitcoin-cli (default: %default%)") + parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"), + help="Root directory for datadirs") + (options, args) = parser.parse_args() + + os.environ['PATH'] = options.srcdir+":"+os.environ['PATH'] + + check_json_precision() + + success = False + try: + print("Initializing test directory "+options.tmpdir) + if not os.path.isdir(options.tmpdir): + os.makedirs(options.tmpdir) + initialize_chain(options.tmpdir) + + nodes = start_nodes(2, options.tmpdir) + connect_nodes(nodes[1], 0) + sync_blocks(nodes) + + run_test(nodes) + + success = True + + except AssertionError as e: + print("Assertion failed: "+e.message) + except Exception as e: + print("Unexpected exception caught during testing: "+str(e)) + stack = traceback.extract_tb(sys.exc_info()[2]) + print(stack[-1]) + + if not options.nocleanup: + print("Cleaning up") + stop_nodes() + shutil.rmtree(options.tmpdir) + + if success: + print("Tests successful") + sys.exit(0) + else: + print("Failed") + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/qa/rpc-tests/txnmall.sh b/qa/rpc-tests/txnmall.sh index 06e4f7102d..11e0276494 100755 --- a/qa/rpc-tests/txnmall.sh +++ b/qa/rpc-tests/txnmall.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Test block generation and basic wallet sending +# Test proper accounting with malleable transactions if [ $# -lt 1 ]; then echo "Usage: $0 path_to_binaries" @@ -35,16 +35,14 @@ B2ARGS="-datadir=$D2" $BITCOIND $B2ARGS & B2PID=$! -trap "kill -9 $B1PID $B2PID; rm -rf $D" EXIT - -# Wait until all four nodes are at the same block number +# Wait until both nodes are at the same block number function WaitBlocks { while : do sleep 1 - BLOCKS1=$( GetBlocks $B1ARGS ) - BLOCKS2=$( GetBlocks $B2ARGS ) - if (( $BLOCKS1 == $BLOCKS2 )) + declare -i BLOCKS1=$( GetBlocks $B1ARGS ) + declare -i BLOCKS2=$( GetBlocks $B2ARGS ) + if (( BLOCKS1 == BLOCKS2 )) then break fi @@ -55,8 +53,8 @@ function WaitBlocks { function WaitPeers { while : do - PEERS=$( $CLI $1 getconnectioncount ) - if (( "$PEERS" == $2 )) + declare -i PEERS=$( $CLI $1 getconnectioncount ) + if (( PEERS == "$2" )) then break fi @@ -64,6 +62,8 @@ function WaitPeers { done } +echo "Generating test blockchain..." + # Start with B2 connected to B1: $CLI $B2ARGS addnode 127.0.0.1:11000 onetry WaitPeers "$B1ARGS" 1 @@ -76,8 +76,8 @@ WaitBlocks $CLI $B2ARGS setgenerate true 100 WaitBlocks -CheckBalance $B1ARGS 50 -CheckBalance $B2ARGS 0 +CheckBalance "$B1ARGS" 50 +CheckBalance "$B2ARGS" 0 # restart B2 with no connection $CLI $B2ARGS stop > /dev/null 2>&1 @@ -85,20 +85,18 @@ wait $B2PID $BITCOIND $B2ARGS & B2PID=$! -B2ADDRESS=$( $CLI $B2ARGS getnewaddress ) +B2ADDRESS=$( $CLI $B2ARGS getaccountaddress "from1" ) # Have B1 create two transactions; second will # spend change from first, since B1 starts with only a single # 50 bitcoin output: -$CLI $B1ARGS move "" "foo" 10.0 -$CLI $B1ARGS move "" "bar" 10.0 +$CLI $B1ARGS move "" "foo" 10.0 > /dev/null +$CLI $B1ARGS move "" "bar" 10.0 > /dev/null TXID1=$( $CLI $B1ARGS sendfrom foo $B2ADDRESS 1.0 0) TXID2=$( $CLI $B1ARGS sendfrom bar $B2ADDRESS 2.0 0) # Mutate TXID1 and add it to B2's memory pool: RAWTX1=$( $CLI $B1ARGS getrawtransaction $TXID1 ) -RAWTX2=$( $CLI $B1ARGS getrawtransaction $TXID2 ) -# ... mutate RAWTX1: # RAWTX1 is hex-encoded, serialized transaction. So each # byte is two characters; we'll prepend the first # "push" in the scriptsig with OP_PUSHDATA1 (0x4c), @@ -123,28 +121,28 @@ echo "TXID1: " $TXID1 echo "Mutated: " $MUTATEDTXID # Re-connect nodes, and have B2 mine a block +# containing the mutant: $CLI $B2ARGS addnode 127.0.0.1:11000 onetry -WaitPeers "$B1ARGS" 1 - -$CLI $B2ARGS setgenerate true 3 -WaitBlocks -$CLI $B1ARGS setgenerate true 3 +$CLI $B2ARGS setgenerate true 1 WaitBlocks +# B1 should have 49 BTC; the 2 BTC send is +# conflicted, and should not count in +# balances. +CheckBalance "$B1ARGS" 49 +CheckBalance "$B1ARGS" 49 "*" +CheckBalance "$B1ARGS" 9 "foo" +CheckBalance "$B1ARGS" 10 "bar" + +# B2 should have 51 BTC +CheckBalance "$B2ARGS" 51 +CheckBalance "$B2ARGS" 1 "from1" + $CLI $B2ARGS stop > /dev/null 2>&1 wait $B2PID $CLI $B1ARGS stop > /dev/null 2>&1 wait $B1PID -trap "" EXIT - -echo "Done, bitcoind's shut down. To rerun/poke around:" -echo "${1}/bitcoind -datadir=$D1 -daemon" -echo "${1}/bitcoind -datadir=$D2 -daemon -connect=127.0.0.1:11000" -echo "To cleanup:" -echo "killall bitcoind; rm -rf test.*" -exit 0 - echo "Tests successful, cleaning up" rm -rf $D exit 0 diff --git a/qa/rpc-tests/util.py b/qa/rpc-tests/util.py new file mode 100644 index 0000000000..fbb27ae2df --- /dev/null +++ b/qa/rpc-tests/util.py @@ -0,0 +1,136 @@ +# +# Helpful routines for regression testing +# + +# Add python-bitcoinrpc to module search path: +import os +import sys +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "python-bitcoinrpc")) + +from decimal import Decimal +import json +import shutil +import subprocess +import time + +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException +from util import * + +START_P2P_PORT=11000 +START_RPC_PORT=11100 + +def check_json_precision(): + """Make sure json library being used does not lose precision converting BTC values""" + n = Decimal("20000000.00000003") + satoshis = int(json.loads(json.dumps(float(n)))*1.0e8) + if satoshis != 2000000000000003: + raise RuntimeError("JSON encode/decode loses precision") + +def sync_blocks(rpc_connections): + """ + Wait until everybody has the same block count + """ + while True: + counts = [ x.getblockcount() for x in rpc_connections ] + if counts == [ counts[0] ]*len(counts): + break + time.sleep(1) + +def sync_mempools(rpc_connections): + """ + Wait until everybody has the same transactions in their memory + pools + """ + while True: + pool = set(rpc_connections[0].getrawmempool()) + num_match = 1 + for i in range(1, len(rpc_connections)): + if set(rpc_connections[i].getrawmempool()) == pool: + num_match = num_match+1 + if num_match == len(rpc_connections): + break + time.sleep(1) + + +def initialize_chain(test_dir): + """ + Create (or copy from cache) a 200-block-long chain and + 4 wallets. + bitcoind and bitcoin-cli must be in search path. + """ + + if not os.path.isdir(os.path.join("cache", "node0")): + # Create cache directories, run bitcoinds: + bitcoinds = [] + for i in range(4): + datadir = os.path.join("cache", "node"+str(i)) + os.makedirs(datadir) + with open(os.path.join(datadir, "bitcoin.conf"), 'w') as f: + f.write("regtest=1\n"); + f.write("rpcuser=rt\n"); + f.write("rpcpassword=rt\n"); + f.write("port="+str(START_P2P_PORT+i)+"\n"); + f.write("rpcport="+str(START_RPC_PORT+i)+"\n"); + args = [ "bitcoind", "-keypool=1", "-datadir="+datadir ] + if i > 0: + args.append("-connect=127.0.0.1:"+str(START_P2P_PORT)) + bitcoinds.append(subprocess.Popen(args)) + subprocess.check_output([ "bitcoin-cli", "-datadir="+datadir, + "-rpcwait", "getblockcount"]) + + rpcs = [] + for i in range(4): + try: + url = "http://rt:rt@127.0.0.1:%d"%(START_RPC_PORT+i,) + rpcs.append(AuthServiceProxy(url)) + except: + sys.stderr.write("Error connecting to "+url+"\n") + sys.exit(1) + + import pdb; pdb.set_trace() + + # Create a 200-block-long chain; each of the 4 nodes + # gets 25 mature blocks and 25 immature. + for i in range(4): + rpcs[i].setgenerate(True, 25) + sync_blocks(rpcs) + for i in range(4): + rpcs[i].setgenerate(True, 25) + sync_blocks(rpcs) + # Shut them down + for i in range(4): + rpcs[i].stop() + + for i in range(4): + from_dir = os.path.join("cache", "node"+str(i)) + to_dir = os.path.join(test_dir, "node"+str(i)) + shutil.copytree(from_dir, to_dir) + +bitcoind_processes = [] + +def start_nodes(num_nodes, dir): + # Start bitcoinds, and wait for RPC interface to be up and running: + for i in range(num_nodes): + datadir = os.path.join(dir, "node"+str(i)) + args = [ "bitcoind", "-datadir="+datadir ] + bitcoind_processes.append(subprocess.Popen(args)) + subprocess.check_output([ "bitcoin-cli", "-datadir="+datadir, + "-rpcwait", "getblockcount"]) + # Create&return JSON-RPC connections + rpc_connections = [] + for i in range(num_nodes): + url = "http://rt:rt@127.0.0.1:%d"%(START_RPC_PORT+i,) + rpc_connections.append(AuthServiceProxy(url)) + return rpc_connections + +def stop_nodes(): + for process in bitcoind_processes: + process.kill() + +def connect_nodes(from_connection, node_num): + ip_port = "127.0.0.1:"+str(START_P2P_PORT+node_num) + from_connection.addnode(ip_port, "onetry") + +def assert_equal(thing1, thing2): + if thing1 != thing2: + raise AssertionError("%s != %s"%(str(thing1),str(thing2))) diff --git a/qa/rpc-tests/util.sh b/qa/rpc-tests/util.sh index d1e4c941cc..9001c42fbc 100644 --- a/qa/rpc-tests/util.sh +++ b/qa/rpc-tests/util.sh @@ -41,8 +41,9 @@ function AssertEqual { # CheckBalance -datadir=... amount account minconf function CheckBalance { + declare -i EXPECT="$2" B=$( $CLI $1 getbalance $3 $4 ) - if (( $( echo "$B == $2" | bc ) == 0 )) + if (( $( echo "$B == $EXPECT" | bc ) == 0 )) then echoerr "bad balance: $B (expected $2)" exit 1 @@ -87,5 +88,5 @@ function SendRawTxn { # Use: GetBlocks <datadir> # returns number of blocks from getinfo function GetBlocks { - ExtractKey blocks "$( $CLI $1 getinfo )" + $CLI $1 getblockcount } diff --git a/qa/rpc-tests/wallet.sh b/qa/rpc-tests/wallet.sh index 8d5a6cdc78..2940566af9 100755 --- a/qa/rpc-tests/wallet.sh +++ b/qa/rpc-tests/wallet.sh @@ -8,6 +8,8 @@ if [ $# -lt 1 ]; then exit 1 fi +set -f + BITCOIND=${1}/bitcoind CLI=${1}/bitcoin-cli @@ -19,40 +21,40 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi D=$(mktemp -d test.XXXXX) D1=${D}/node1 -CreateDataDir $D1 port=11000 rpcport=11001 +CreateDataDir "$D1" port=11000 rpcport=11001 B1ARGS="-datadir=$D1" $BITCOIND $B1ARGS & B1PID=$! D2=${D}/node2 -CreateDataDir $D2 port=11010 rpcport=11011 connect=127.0.0.1:11000 +CreateDataDir "$D2" port=11010 rpcport=11011 connect=127.0.0.1:11000 B2ARGS="-datadir=$D2" $BITCOIND $B2ARGS & B2PID=$! D3=${D}/node3 -CreateDataDir $D3 port=11020 rpcport=11021 connect=127.0.0.1:11000 +CreateDataDir "$D3" port=11020 rpcport=11021 connect=127.0.0.1:11000 B3ARGS="-datadir=$D3" $BITCOIND $BITCOINDARGS $B3ARGS & B3PID=$! -trap "kill -9 $B1PID $B2PID $B3PID; rm -rf $D" EXIT - # Wait until all three nodes are at the same block number function WaitBlocks { while : do sleep 1 - BLOCKS1=$( GetBlocks $B1ARGS ) - BLOCKS2=$( GetBlocks $B2ARGS ) - BLOCKS3=$( GetBlocks $B3ARGS ) - if (( $BLOCKS1 == $BLOCKS2 && $BLOCKS2 == $BLOCKS3 )) + declare -i BLOCKS1=$( GetBlocks $B1ARGS ) + declare -i BLOCKS2=$( GetBlocks $B2ARGS ) + declare -i BLOCKS3=$( GetBlocks $B3ARGS ) + if (( BLOCKS1 == BLOCKS2 && BLOCKS2 == BLOCKS3 )) then break fi done } +echo "Generating test blockchain..." + # 1 block, 50 XBT each == 50 XBT $CLI $B1ARGS setgenerate true 1 WaitBlocks @@ -60,8 +62,8 @@ WaitBlocks $CLI $B2ARGS setgenerate true 101 WaitBlocks -CheckBalance $B1ARGS 50 -CheckBalance $B2ARGS 50 +CheckBalance "$B1ARGS" 50 +CheckBalance "$B2ARGS" 50 # Send 21 XBT from 1 to 3. Second # transaction will be child of first, and @@ -80,25 +82,25 @@ WaitBlocks # B1 should end up with 100 XBT in block rewards plus fees, # minus the 21 XBT sent to B3: -CheckBalance $B1ARGS "100-21" -CheckBalance $B3ARGS "21" +CheckBalance "$B1ARGS" "100-21" +CheckBalance "$B3ARGS" "21" # B1 should have two unspent outputs; create a couple # of raw transactions to send them to B3, submit them through # B2, and make sure both B1 and B3 pick them up properly: RAW1=$(CreateTxn1 $B1ARGS 1 $(Address $B3ARGS "from1" ) ) RAW2=$(CreateTxn1 $B1ARGS 2 $(Address $B3ARGS "from1" ) ) -RAWTXID1=$(SendRawTxn $B2ARGS $RAW1) -RAWTXID2=$(SendRawTxn $B2ARGS $RAW2) +RAWTXID1=$(SendRawTxn "$B2ARGS" $RAW1) +RAWTXID2=$(SendRawTxn "$B2ARGS" $RAW2) # Have B2 mine a block to confirm transactions: $CLI $B2ARGS setgenerate true 1 WaitBlocks # Check balances after confirmation -CheckBalance $B1ARGS 0 -CheckBalance $B3ARGS 100 -CheckBalance $B3ARGS "100-21" "from1" +CheckBalance "$B1ARGS" 0 +CheckBalance "$B3ARGS" 100 +CheckBalance "$B3ARGS" "100-21" "from1" $CLI $B3ARGS stop > /dev/null 2>&1 wait $B3PID @@ -108,6 +110,5 @@ $CLI $B1ARGS stop > /dev/null 2>&1 wait $B1PID echo "Tests successful, cleaning up" -trap "" EXIT rm -rf $D exit 0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..b5a217bf93 --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +from distutils.core import setup + +setup(name='python-bitcoinrpc', + version='0.1', + description='Enhanced version of python-jsonrpc for use with Bitcoin', + long_description=open('README').read(), + author='Jeff Garzik', + author_email='<jgarzik@exmulti.com>', + maintainer='Jeff Garzik', + maintainer_email='<jgarzik@exmulti.com>', + url='http://www.github.com/jgarzik/python-bitcoinrpc', + packages=['bitcoinrpc'], + classifiers=['License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'Operating System :: OS Independent']) diff --git a/share/qt/Info.plist.in b/share/qt/Info.plist.in index b1c2dcb462..4ed1f1be50 100644 --- a/share/qt/Info.plist.in +++ b/share/qt/Info.plist.in @@ -2,6 +2,14 @@ <!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> <plist version="0.9"> <dict> + <key>LSMinimumSystemVersion</key> + <string>10.6.0</string> + + <key>LSArchitecturePriority</key> + <array> + <string>x86_64</string> + </array> + <key>CFBundleIconFile</key> <string>bitcoin.icns</string> diff --git a/share/qt/clean_mac_info_plist.py b/share/qt/clean_mac_info_plist.py deleted file mode 100755 index df677f50b7..0000000000 --- a/share/qt/clean_mac_info_plist.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# Jonas Schnelli, 2013 -# make sure the Bitcoin-Qt.app contains the right plist (including the right version) -# fix made because of serval bugs in Qt mac deployment (https://bugreports.qt-project.org/browse/QTBUG-21267) - -from string import Template -from datetime import date - -bitcoinDir = "./"; - -inFile = bitcoinDir+"/share/qt/Info.plist" -outFile = "Bitcoin-Qt.app/Contents/Info.plist" -version = "unknown"; - -fileForGrabbingVersion = bitcoinDir+"bitcoin-qt.pro" -for line in open(fileForGrabbingVersion): - lineArr = line.replace(" ", "").split("="); - if lineArr[0].startswith("VERSION"): - version = lineArr[1].replace("\n", ""); - -fIn = open(inFile, "r") -fileContent = fIn.read() -s = Template(fileContent) -newFileContent = s.substitute(VERSION=version,YEAR=date.today().year) - -fOut = open(outFile, "w"); -fOut.write(newFileContent); - -print "Info.plist fresh created"
\ No newline at end of file @@ -137,7 +137,9 @@ public: return Hash(vch, vch+size()); } - // just check syntactic correctness. + // Check syntactic correctness. + // + // Note that this is consensus critical as CheckSig() calls it! bool IsValid() const { return size() > 0; } diff --git a/src/main.cpp b/src/main.cpp index 8a5b659e7c..7afaa9e7e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1866,17 +1866,23 @@ bool static DisconnectTip(CValidationState &state) { // Write the chain state to disk, if necessary. if (!WriteChainState(state)) return false; - // Ressurect mempool transactions from the disconnected block. + // Resurrect mempool transactions from the disconnected block. BOOST_FOREACH(const CTransaction &tx, block.vtx) { // ignore validation errors in resurrected transactions + list<CTransaction> removed; CValidationState stateDummy; if (!tx.IsCoinBase()) if (!AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL)) - mempool.remove(tx, true); + mempool.remove(tx, removed, true); } mempool.check(pcoinsTip); // Update chainActive and related variables. UpdateTip(pindexDelete->pprev); + // Let wallets know transactions went from 1-confirmed to + // 0-confirmed or conflicted: + BOOST_FOREACH(const CTransaction &tx, block.vtx) { + SyncWithWallets(tx.GetHash(), tx, NULL); + } return true; } @@ -1907,13 +1913,24 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew) { if (!WriteChainState(state)) return false; // Remove conflicting transactions from the mempool. + list<CTransaction> txConflicted; BOOST_FOREACH(const CTransaction &tx, block.vtx) { - mempool.remove(tx); - mempool.removeConflicts(tx); + list<CTransaction> unused; + mempool.remove(tx, unused); + mempool.removeConflicts(tx, txConflicted); } mempool.check(pcoinsTip); // Update chainActive & related variables. UpdateTip(pindexNew); + // Tell wallet about transactions that went from mempool + // to conflicted: + BOOST_FOREACH(const CTransaction &tx, txConflicted) { + SyncWithWallets(tx.GetHash(), tx, NULL); + } + // ... and about transactions that got confirmed: + BOOST_FOREACH(const CTransaction &tx, block.vtx) { + SyncWithWallets(tx.GetHash(), tx, &block); + } return true; } diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index e1a9140f45..2d8fcd7a52 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -468,11 +468,12 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) BOOST_FOREACH(const COutput& out, vOutputs) { - // unselect already spent, very unlikely scenario, this could happen when selected are spent elsewhere, like rpc or another computer - if (out.tx->IsSpent(out.i)) + // unselect already spent, very unlikely scenario, this could happen + // when selected are spent elsewhere, like rpc or another computer + uint256 txhash = out.tx->GetHash(); + COutPoint outpt(txhash, out.i); + if (model->isSpent(outpt)) { - uint256 txhash = out.tx->GetHash(); - COutPoint outpt(txhash, out.i); coinControl->UnSelect(outpt); continue; } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 8cfaed27c7..703a2b4e79 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -164,7 +164,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) (wtx.IsCoinBase() ? 1 : 0), wtx.nTimeReceived, idx); - status.confirmed = wtx.IsTrusted(); + status.countsForBalance = wtx.IsTrusted() && !(wtx.GetBlocksToMaturity() > 0); status.depth = wtx.GetDepthInMainChain(); status.cur_num_blocks = chainActive.Height(); @@ -181,33 +181,12 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) status.open_for = wtx.nLockTime; } } - else - { - if (status.depth < 0) - { - status.status = TransactionStatus::Conflicted; - } - else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) - { - status.status = TransactionStatus::Offline; - } - else if (status.depth < NumConfirmations) - { - status.status = TransactionStatus::Unconfirmed; - } - else - { - status.status = TransactionStatus::HaveConfirmations; - } - } - // For generated transactions, determine maturity - if(type == TransactionRecord::Generated) + else if(type == TransactionRecord::Generated) { - int64_t nCredit = wtx.GetCredit(true); - if (nCredit == 0) + if (wtx.GetBlocksToMaturity() > 0) { - status.maturity = TransactionStatus::Immature; + status.status = TransactionStatus::Immature; if (wtx.IsInMainChain()) { @@ -215,18 +194,42 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) // Check if the block was requested by anyone if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) - status.maturity = TransactionStatus::MaturesWarning; + status.status = TransactionStatus::MaturesWarning; } else { - status.maturity = TransactionStatus::NotAccepted; + status.status = TransactionStatus::NotAccepted; } } else { - status.maturity = TransactionStatus::Mature; + status.status = TransactionStatus::Confirmed; + } + } + else + { + if (status.depth < 0) + { + status.status = TransactionStatus::Conflicted; + } + else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) + { + status.status = TransactionStatus::Offline; + } + else if (status.depth == 0) + { + status.status = TransactionStatus::Unconfirmed; + } + else if (status.depth < RecommendedNumConfirmations) + { + status.status = TransactionStatus::Confirming; + } + else + { + status.status = TransactionStatus::Confirmed; } } + } bool TransactionRecord::statusUpdateNeeded() diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index d7be0bc438..af6fd403b3 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -19,33 +19,32 @@ class TransactionStatus { public: TransactionStatus(): - confirmed(false), sortKey(""), maturity(Mature), + countsForBalance(false), sortKey(""), matures_in(0), status(Offline), depth(0), open_for(0), cur_num_blocks(-1) { } - enum Maturity - { - Immature, - Mature, - MaturesWarning, /**< Transaction will likely not mature because no nodes have confirmed */ - NotAccepted - }; - enum Status { - OpenUntilDate, - OpenUntilBlock, - Offline, - Unconfirmed, - HaveConfirmations, - Conflicted + Confirmed, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/ + /// Normal (sent/received) transactions + OpenUntilDate, /**< Transaction not yet final, waiting for date */ + OpenUntilBlock, /**< Transaction not yet final, waiting for block */ + Offline, /**< Not sent to any other nodes **/ + Unconfirmed, /**< Not yet mined into a block **/ + Confirming, /**< Confirmed, but waiting for the recommended number of confirmations **/ + Conflicted, /**< Conflicts with other transaction or mempool **/ + /// Generated (mined) transactions + Immature, /**< Mined but waiting for maturity */ + MaturesWarning, /**< Transaction will likely not mature because no nodes have confirmed */ + NotAccepted /**< Mined but not accepted */ }; - bool confirmed; + /// Transaction counts towards available balance + bool countsForBalance; + /// Sorting key based on status std::string sortKey; /** @name Generated (mined) transactions @{*/ - Maturity maturity; int matures_in; /**@}*/ @@ -79,8 +78,8 @@ public: SendToSelf }; - /** Number of confirmation needed for transaction */ - static const int NumConfirmations = 6; + /** Number of confirmation recommended for accepting a transaction */ + static const int RecommendedNumConfirmations = 6; TransactionRecord(): hash(), time(0), type(Other), address(""), debit(0), credit(0), idx(0) diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 7d76204ba4..959987461f 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -285,45 +285,38 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons { QString status; - if(wtx->type == TransactionRecord::Generated) + switch(wtx->status.status) { - switch(wtx->status.maturity) - { - case TransactionStatus::Immature: - status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in); - break; - case TransactionStatus::Mature: - status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); - break; - case TransactionStatus::MaturesWarning: - status = tr("This block was not received by any other nodes and will probably not be accepted!"); - break; - case TransactionStatus::NotAccepted: - status = tr("Generated but not accepted"); - break; - } - } else { - switch(wtx->status.status) - { - case TransactionStatus::OpenUntilBlock: - status = tr("Open for %n more block(s)","",wtx->status.open_for); - break; - case TransactionStatus::OpenUntilDate: - status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for)); - break; - case TransactionStatus::Offline: - status = tr("Offline"); - break; - case TransactionStatus::Unconfirmed: - status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations); - break; - case TransactionStatus::HaveConfirmations: - status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); - break; - case TransactionStatus::Conflicted: - status = tr("Conflicted"); - break; - } + case TransactionStatus::OpenUntilBlock: + status = tr("Open for %n more block(s)","",wtx->status.open_for); + break; + case TransactionStatus::OpenUntilDate: + status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for)); + break; + case TransactionStatus::Offline: + status = tr("Offline"); + break; + case TransactionStatus::Unconfirmed: + status = tr("Unconfirmed"); + break; + case TransactionStatus::Confirming: + status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations); + break; + case TransactionStatus::Confirmed: + status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); + break; + case TransactionStatus::Conflicted: + status = tr("Conflicted"); + break; + case TransactionStatus::Immature: + status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in); + break; + case TransactionStatus::MaturesWarning: + status = tr("This block was not received by any other nodes and will probably not be accepted!"); + break; + case TransactionStatus::NotAccepted: + status = tr("Generated but not accepted"); + break; } return status; @@ -441,7 +434,7 @@ QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit); if(showUnconfirmed) { - if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature) + if(!wtx->status.countsForBalance) { str = QString("[") + str + QString("]"); } @@ -451,46 +444,36 @@ QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const { - if(wtx->type == TransactionRecord::Generated) - { - switch(wtx->status.maturity) - { - case TransactionStatus::Immature: { - int total = wtx->status.depth + wtx->status.matures_in; - int part = (wtx->status.depth * 4 / total) + 1; - return QIcon(QString(":/icons/transaction_%1").arg(part)); - } - case TransactionStatus::Mature: - return QIcon(":/icons/transaction_confirmed"); - case TransactionStatus::MaturesWarning: - case TransactionStatus::NotAccepted: - return QIcon(":/icons/transaction_0"); - } - } - else + switch(wtx->status.status) { - switch(wtx->status.status) + case TransactionStatus::OpenUntilBlock: + case TransactionStatus::OpenUntilDate: + return QColor(64,64,255); + case TransactionStatus::Offline: + return QColor(192,192,192); + case TransactionStatus::Unconfirmed: + return QIcon(":/icons/transaction_0"); + case TransactionStatus::Confirming: + switch(wtx->status.depth) { - case TransactionStatus::OpenUntilBlock: - case TransactionStatus::OpenUntilDate: - return QColor(64,64,255); - case TransactionStatus::Offline: - return QColor(192,192,192); - case TransactionStatus::Unconfirmed: - switch(wtx->status.depth) - { - case 0: return QIcon(":/icons/transaction_0"); - case 1: return QIcon(":/icons/transaction_1"); - case 2: return QIcon(":/icons/transaction_2"); - case 3: return QIcon(":/icons/transaction_3"); - case 4: return QIcon(":/icons/transaction_4"); - default: return QIcon(":/icons/transaction_5"); - }; - case TransactionStatus::HaveConfirmations: - return QIcon(":/icons/transaction_confirmed"); - case TransactionStatus::Conflicted: - return QIcon(":/icons/transaction_conflicted"); + case 1: return QIcon(":/icons/transaction_1"); + case 2: return QIcon(":/icons/transaction_2"); + case 3: return QIcon(":/icons/transaction_3"); + case 4: return QIcon(":/icons/transaction_4"); + default: return QIcon(":/icons/transaction_5"); + }; + case TransactionStatus::Confirmed: + return QIcon(":/icons/transaction_confirmed"); + case TransactionStatus::Conflicted: + return QIcon(":/icons/transaction_conflicted"); + case TransactionStatus::Immature: { + int total = wtx->status.depth + wtx->status.matures_in; + int part = (wtx->status.depth * 4 / total) + 1; + return QIcon(QString(":/icons/transaction_%1").arg(part)); } + case TransactionStatus::MaturesWarning: + case TransactionStatus::NotAccepted: + return QIcon(":/icons/transaction_0"); } return QColor(0,0,0); } @@ -557,8 +540,8 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const case Qt::TextAlignmentRole: return column_alignments[index.column()]; case Qt::ForegroundRole: - // Non-confirmed transactions are grey - if(!rec->status.confirmed) + // Non-confirmed (but not immature) as transactions are grey + if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature) { return COLOR_UNCONFIRMED; } @@ -586,9 +569,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const case TxIDRole: return rec->getTxID(); case ConfirmedRole: - // Return True if transaction counts for balance - return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && - rec->status.maturity != TransactionStatus::Mature); + return rec->status.countsForBalance; case FormattedAmountRole: return formatTxAmount(rec, false); case StatusRole: diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 3549cd49f0..eae448fee4 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -501,6 +501,12 @@ void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vect } } +bool WalletModel::isSpent(const COutPoint& outpoint) const +{ + LOCK(wallet->cs_wallet); + return wallet->IsSpent(outpoint.hash, outpoint.n); +} + // AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 91a6fba222..28a9169e27 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -180,6 +180,7 @@ public: bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; void getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs); + bool isSpent(const COutPoint& outpoint) const; void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const; bool isLockedCoin(uint256 hash, unsigned int n) const; diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index 9e1d47846e..635d4ac19b 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -128,7 +128,6 @@ Value importprivkey(const Array& params, bool fHelp) if (fRescan) { pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); - pwalletMain->ReacceptWalletTransactions(); } } @@ -216,7 +215,6 @@ Value importwallet(const Array& params, bool fHelp) LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1); pwalletMain->ScanForWalletTransactions(pindex); - pwalletMain->ReacceptWalletTransactions(); pwalletMain->MarkDirty(); if (!fGood) diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 7b605af589..d3b6c349a7 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -560,7 +560,7 @@ int64_t GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMi for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; - if (!IsFinalTx(wtx)) + if (!IsFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0) continue; int64_t nReceived, nSent, nFee; @@ -1324,13 +1324,14 @@ Value listaccounts(const Array& params, bool fHelp) string strSentAccount; list<pair<CTxDestination, int64_t> > listReceived; list<pair<CTxDestination, int64_t> > listSent; - if (wtx.GetBlocksToMaturity() > 0) + int nDepth = wtx.GetDepthInMainChain(); + if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0) continue; wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount); mapAccountBalances[strSentAccount] -= nFee; BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& s, listSent) mapAccountBalances[strSentAccount] -= s.second; - if (wtx.GetDepthInMainChain() >= nMinDepth) + if (nDepth >= nMinDepth) { BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& r, listReceived) if (pwalletMain->mapAddressBook.count(r.first)) diff --git a/src/tinyformat.h b/src/tinyformat.h index 04b51f0adc..b6113029f5 100644 --- a/src/tinyformat.h +++ b/src/tinyformat.h @@ -109,7 +109,7 @@ namespace tinyformat {} namespace tfm = tinyformat; // Error handling; calls assert() by default. -// #define TINYFORMAT_ERROR(reasonString) your_error_handler(reasonString) +#define TINYFORMAT_ERROR(reasonString) throw std::runtime_error(reasonString) // Define for C++11 variadic templates which make the code shorter & more // general. If you don't define this, C++11 support is autodetected below. diff --git a/src/txmempool.cpp b/src/txmempool.cpp index be251d1d64..64c9eac73d 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -86,7 +86,7 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry) } -bool CTxMemPool::remove(const CTransaction &tx, bool fRecursive) +void CTxMemPool::remove(const CTransaction &tx, std::list<CTransaction>& removed, bool fRecursive) { // Remove transaction from memory pool { @@ -95,34 +95,37 @@ bool CTxMemPool::remove(const CTransaction &tx, bool fRecursive) if (fRecursive) { for (unsigned int i = 0; i < tx.vout.size(); i++) { std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(COutPoint(hash, i)); - if (it != mapNextTx.end()) - remove(*it->second.ptx, true); + if (it == mapNextTx.end()) + continue; + remove(*it->second.ptx, removed, true); } } if (mapTx.count(hash)) { + removed.push_front(tx); BOOST_FOREACH(const CTxIn& txin, tx.vin) mapNextTx.erase(txin.prevout); mapTx.erase(hash); nTransactionsUpdated++; } } - return true; } -bool CTxMemPool::removeConflicts(const CTransaction &tx) +void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed) { // Remove transactions which depend on inputs of tx, recursively + list<CTransaction> result; LOCK(cs); BOOST_FOREACH(const CTxIn &txin, tx.vin) { std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(txin.prevout); if (it != mapNextTx.end()) { const CTransaction &txConflict = *it->second.ptx; if (txConflict != tx) - remove(txConflict, true); + { + remove(txConflict, removed, true); + } } } - return true; } void CTxMemPool::clear() diff --git a/src/txmempool.h b/src/txmempool.h index a652c424a4..4509e95778 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_TXMEMPOOL_H #define BITCOIN_TXMEMPOOL_H +#include <list> + #include "coins.h" #include "core.h" #include "sync.h" @@ -72,8 +74,8 @@ public: void setSanityCheck(bool _fSanityCheck) { fSanityCheck = _fSanityCheck; } bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry); - bool remove(const CTransaction &tx, bool fRecursive = false); - bool removeConflicts(const CTransaction &tx); + void remove(const CTransaction &tx, std::list<CTransaction>& removed, bool fRecursive = false); + void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed); void clear(); void queryHashes(std::vector<uint256>& vtxid); void pruneSpent(const uint256& hash, CCoins &coins); diff --git a/src/wallet.cpp b/src/wallet.cpp index eaf0b98467..3ecd994e9d 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -32,6 +32,15 @@ struct CompareValueOnly } }; +const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const +{ + LOCK(cs_wallet); + std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash); + if (it == mapWallet.end()) + return NULL; + return &(it->second); +} + CPubKey CWallet::GenerateNewKey() { AssertLockHeld(cs_wallet); // mapKeyMetadata @@ -239,18 +248,20 @@ set<uint256> CWallet::GetConflicts(const uint256& txid) const return result; const CWalletTx& wtx = it->second; - std::pair<TxConflicts::const_iterator, TxConflicts::const_iterator> range; + std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; BOOST_FOREACH(const CTxIn& txin, wtx.vin) { - range = mapTxConflicts.equal_range(txin.prevout); - for (TxConflicts::const_iterator it = range.first; it != range.second; ++it) + if (mapTxSpends.count(txin.prevout) <= 1) + continue; // No conflict if zero or one spends + range = mapTxSpends.equal_range(txin.prevout); + for (TxSpends::const_iterator it = range.first; it != range.second; ++it) result.insert(it->second); } return result; } -void CWallet::SyncMetaData(pair<TxConflicts::iterator, TxConflicts::iterator> range) +void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range) { // We want all the wallet transactions in range to have the same metadata as // the oldest (smallest nOrderPos). @@ -258,7 +269,7 @@ void CWallet::SyncMetaData(pair<TxConflicts::iterator, TxConflicts::iterator> ra int nMinOrderPos = std::numeric_limits<int>::max(); const CWalletTx* copyFrom = NULL; - for (TxConflicts::iterator it = range.first; it != range.second; ++it) + for (TxSpends::iterator it = range.first; it != range.second; ++it) { const uint256& hash = it->second; int n = mapWallet[hash].nOrderPos; @@ -269,7 +280,7 @@ void CWallet::SyncMetaData(pair<TxConflicts::iterator, TxConflicts::iterator> ra } } // Now copy data from copyFrom to rest: - for (TxConflicts::iterator it = range.first; it != range.second; ++it) + for (TxSpends::iterator it = range.first; it != range.second; ++it) { const uint256& hash = it->second; CWalletTx* copyTo = &mapWallet[hash]; @@ -281,28 +292,48 @@ void CWallet::SyncMetaData(pair<TxConflicts::iterator, TxConflicts::iterator> ra copyTo->nTimeSmart = copyFrom->nTimeSmart; copyTo->fFromMe = copyFrom->fFromMe; copyTo->strFromAccount = copyFrom->strFromAccount; - // vfSpent not copied on purpose // nOrderPos not copied on purpose // cached members not copied on purpose } } -void CWallet::AddToConflicts(const uint256& wtxhash) +// Outpoint is spent if any non-conflicted transaction +// spends it: +bool CWallet::IsSpent(const uint256& hash, unsigned int n) const { - assert(mapWallet.count(wtxhash)); - CWalletTx& thisTx = mapWallet[wtxhash]; - if (thisTx.IsCoinBase()) - return; + const COutPoint outpoint(hash, n); + pair<TxSpends::const_iterator, TxSpends::const_iterator> range; + range = mapTxSpends.equal_range(outpoint); - BOOST_FOREACH(const CTxIn& txin, thisTx.vin) + for (TxSpends::const_iterator it = range.first; it != range.second; ++it) { - mapTxConflicts.insert(make_pair(txin.prevout, wtxhash)); - - pair<TxConflicts::iterator, TxConflicts::iterator> range; - range = mapTxConflicts.equal_range(txin.prevout); - if (range.first != range.second) - SyncMetaData(range); + const uint256& wtxid = it->second; + std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); + if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) + return true; // Spent } + return false; +} + +void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid) +{ + mapTxSpends.insert(make_pair(outpoint, wtxid)); + + pair<TxSpends::iterator, TxSpends::iterator> range; + range = mapTxSpends.equal_range(outpoint); + SyncMetaData(range); +} + + +void CWallet::AddToSpends(const uint256& wtxid) +{ + assert(mapWallet.count(wtxid)); + CWalletTx& thisTx = mapWallet[wtxid]; + if (thisTx.IsCoinBase()) // Coinbases don't spend anything! + return; + + BOOST_FOREACH(const CTxIn& txin, thisTx.vin) + AddToSpends(txin.prevout, wtxid); } bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) @@ -423,33 +454,6 @@ CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, return txOrdered; } -void CWallet::WalletUpdateSpent(const CTransaction &tx) -{ - // Anytime a signature is successfully verified, it's proof the outpoint is spent. - // Update the wallet spent flag if it doesn't know due to wallet.dat being - // restored from backup or the user making copies of wallet.dat. - { - LOCK(cs_wallet); - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { - map<uint256, CWalletTx>::iterator mi = mapWallet.find(txin.prevout.hash); - if (mi != mapWallet.end()) - { - CWalletTx& wtx = (*mi).second; - if (txin.prevout.n >= wtx.vout.size()) - LogPrintf("WalletUpdateSpent: bad wtx %s\n", wtx.GetHash().ToString()); - else if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n])) - { - LogPrintf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()), wtx.GetHash().ToString()); - wtx.MarkSpent(txin.prevout.n); - wtx.WriteToDisk(); - NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); - } - } - } - } -} - void CWallet::MarkDirty() { { @@ -466,7 +470,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet) if (fFromLoadWallet) { mapWallet[hash] = wtxIn; - AddToConflicts(hash); + AddToSpends(hash); } else { @@ -526,7 +530,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet) wtxIn.GetHash().ToString(), wtxIn.hashBlock.ToString()); } - AddToConflicts(hash); + AddToSpends(hash); } bool fUpdated = false; @@ -549,7 +553,6 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet) wtx.fFromMe = wtxIn.fFromMe; fUpdated = true; } - fUpdated |= wtx.UpdateSpent(wtxIn.vfSpent); } //// debug print @@ -560,8 +563,8 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet) if (!wtx.WriteToDisk()) return false; - // since AddToWallet is called directly for self-originating transactions, check for consumption of own coins - WalletUpdateSpent(wtx); + // Break debit/credit balance caches: + wtx.MarkDirty(); // Notify UI of new or updated transaction NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); @@ -596,14 +599,25 @@ bool CWallet::AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& wtx.SetMerkleBranch(pblock); return AddToWallet(wtx); } - else - WalletUpdateSpent(tx); } return false; } -void CWallet::SyncTransaction(const uint256 &hash, const CTransaction& tx, const CBlock* pblock) { +void CWallet::SyncTransaction(const uint256 &hash, const CTransaction& tx, const CBlock* pblock) +{ AddToWalletIfInvolvingMe(hash, tx, pblock, true); + + if (mapWallet.count(hash) == 0) + return; // Not one of ours + + // If a transaction changes 'conflicted' state, that changes the balance + // available of the outputs it spends. So force those to be + // recomputed, also: + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + if (mapWallet.count(txin.prevout.hash)) + mapWallet[txin.prevout.hash].MarkDirty(); + } } void CWallet::EraseFromWallet(const uint256 &hash) @@ -804,78 +818,6 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64_t& nReceived, } } -void CWalletTx::AddSupportingTransactions() -{ - vtxPrev.clear(); - - const int COPY_DEPTH = 3; - if (SetMerkleBranch() < COPY_DEPTH) - { - vector<uint256> vWorkQueue; - BOOST_FOREACH(const CTxIn& txin, vin) - vWorkQueue.push_back(txin.prevout.hash); - - { - LOCK(pwallet->cs_wallet); - map<uint256, const CMerkleTx*> mapWalletPrev; - set<uint256> setAlreadyDone; - for (unsigned int i = 0; i < vWorkQueue.size(); i++) - { - uint256 hash = vWorkQueue[i]; - if (setAlreadyDone.count(hash)) - continue; - setAlreadyDone.insert(hash); - - CMerkleTx tx; - map<uint256, CWalletTx>::const_iterator mi = pwallet->mapWallet.find(hash); - if (mi != pwallet->mapWallet.end()) - { - tx = (*mi).second; - BOOST_FOREACH(const CMerkleTx& txWalletPrev, (*mi).second.vtxPrev) - mapWalletPrev[txWalletPrev.GetHash()] = &txWalletPrev; - } - else if (mapWalletPrev.count(hash)) - { - tx = *mapWalletPrev[hash]; - } - else - { - continue; - } - - int nDepth = tx.SetMerkleBranch(); - vtxPrev.push_back(tx); - - if (nDepth < COPY_DEPTH) - { - BOOST_FOREACH(const CTxIn& txin, tx.vin) - vWorkQueue.push_back(txin.prevout.hash); - } - } - } - } - - reverse(vtxPrev.begin(), vtxPrev.end()); -} - -bool CWalletTx::AcceptWalletTransaction() -{ - { - LOCK(mempool.cs); - // Add previous supporting transactions first - BOOST_FOREACH(CMerkleTx& tx, vtxPrev) - { - if (!tx.IsCoinBase()) - { - uint256 hash = tx.GetHash(); - if (!mempool.exists(hash) && pcoinsTip->HaveCoins(hash)) - tx.AcceptToMemoryPool(false); - } - } - return AcceptToMemoryPool(false); - } - return false; -} bool CWalletTx::WriteToDisk() { @@ -916,69 +858,26 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) void CWallet::ReacceptWalletTransactions() { - bool fRepeat = true; - while (fRepeat) + LOCK(cs_wallet); + BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) { - LOCK(cs_wallet); - fRepeat = false; - bool fMissing = false; - BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) - { - CWalletTx& wtx = item.second; - if (wtx.IsCoinBase() && wtx.IsSpent(0)) - continue; + const uint256& wtxid = item.first; + CWalletTx& wtx = item.second; + assert(wtx.GetHash() == wtxid); - CCoins coins; - bool fUpdated = false; - bool fFound = pcoinsTip->GetCoins(wtx.GetHash(), coins); - if (fFound || wtx.GetDepthInMainChain() > 0) - { - // Update fSpent if a tx got spent somewhere else by a copy of wallet.dat - for (unsigned int i = 0; i < wtx.vout.size(); i++) - { - if (wtx.IsSpent(i)) - continue; - if ((i >= coins.vout.size() || coins.vout[i].IsNull()) && IsMine(wtx.vout[i])) - { - wtx.MarkSpent(i); - fUpdated = true; - fMissing = true; - } - } - if (fUpdated) - { - LogPrintf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()), wtx.GetHash().ToString()); - wtx.MarkDirty(); - wtx.WriteToDisk(); - } - } - else - { - // Re-accept any txes of ours that aren't already in a block - if (!wtx.IsCoinBase()) - wtx.AcceptWalletTransaction(); - } - } - if (fMissing) + int nDepth = wtx.GetDepthInMainChain(); + + if (!wtx.IsCoinBase() && nDepth < 0) { - // TODO: optimize this to scan just part of the block chain? - if (ScanForWalletTransactions(chainActive.Genesis())) - fRepeat = true; // Found missing transactions: re-do re-accept. + // Try to add to memory pool + LOCK(mempool.cs); + wtx.AcceptToMemoryPool(false); } } } void CWalletTx::RelayWalletTransaction() { - BOOST_FOREACH(const CMerkleTx& tx, vtxPrev) - { - // Important: versions of bitcoin before 0.8.6 had a bug that inserted - // empty transactions into the vtxPrev, which will cause the node to be - // banned when retransmitted, hence the check for !tx.vin.empty() - if (!tx.IsCoinBase() && !tx.vin.empty()) - if (tx.GetDepthInMainChain() == 0) - RelayTransaction((CTransaction)tx, tx.GetHash()); - } if (!IsCoinBase()) { if (GetDepthInMainChain() == 0) { @@ -1104,6 +1003,7 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const LOCK(cs_wallet); for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const uint256& wtxid = it->first; const CWalletTx* pcoin = &(*it).second; if (!IsFinalTx(*pcoin)) @@ -1120,7 +1020,7 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const continue; for (unsigned int i = 0; i < pcoin->vout.size(); i++) { - if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && + if (!(IsSpent(wtxid, i)) && IsMine(pcoin->vout[i]) && !IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue > 0 && (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) vCoins.push_back(COutput(pcoin, i, nDepth)); @@ -1452,8 +1352,6 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, continue; } - // Fill vtxPrev by copying from previous transactions vtxPrev - wtxNew.AddSupportingTransactions(); wtxNew.fTimeReceivedIsTxTime = true; break; @@ -1490,14 +1388,12 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) // otherwise just for transaction history. AddToWallet(wtxNew); - // Mark old coins as spent + // Notify that old coins are spent set<CWalletTx*> setCoins; BOOST_FOREACH(const CTxIn& txin, wtxNew.vin) { CWalletTx &coin = mapWallet[txin.prevout.hash]; coin.BindWallet(this); - coin.MarkSpent(txin.prevout.n); - coin.WriteToDisk(); NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); } @@ -1855,7 +1751,7 @@ std::map<CTxDestination, int64_t> CWallet::GetAddressBalances() if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr)) continue; - int64_t n = pcoin->IsSpent(i) ? 0 : pcoin->vout[i].nValue; + int64_t n = IsSpent(walletEntry.first, i) ? 0 : pcoin->vout[i].nValue; if (!balances.count(addr)) balances[addr] = 0; diff --git a/src/wallet.h b/src/wallet.h index eb192f1ca6..7feb86d294 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -108,11 +108,15 @@ private: int64_t nNextResend; int64_t nLastResend; - // Used to detect and report conflicted transactions: - typedef std::multimap<COutPoint, uint256> TxConflicts; - TxConflicts mapTxConflicts; - void AddToConflicts(const uint256& wtxhash); - void SyncMetaData(std::pair<TxConflicts::iterator, TxConflicts::iterator>); + // Used to keep track of spent outpoints, and + // detect and report conflicts (double-spends or + // mutated transactions where the mutant gets mined). + typedef std::multimap<COutPoint, uint256> TxSpends; + TxSpends mapTxSpends; + void AddToSpends(const COutPoint& outpoint, const uint256& wtxid); + void AddToSpends(const uint256& wtxid); + + void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>); public: /// Main wallet lock. @@ -169,12 +173,16 @@ public: int64_t nTimeFirstKey; + const CWalletTx* GetWalletTx(const uint256& hash) const; + // check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL) const; bool SelectCoinsMinConf(int64_t nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const; + bool IsSpent(const uint256& hash, unsigned int n) const; + bool IsLockedCoin(uint256 hash, unsigned int n) const; void LockCoin(COutPoint& output); void UnlockCoin(COutPoint& output); @@ -234,7 +242,6 @@ public: void SyncTransaction(const uint256 &hash, const CTransaction& tx, const CBlock* pblock); bool AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate); void EraseFromWallet(const uint256 &hash); - void WalletUpdateSpent(const CTransaction& prevout); int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false); void ReacceptWalletTransactions(); void ResendWalletTransactions(); @@ -439,7 +446,6 @@ private: const CWallet* pwallet; public: - std::vector<CMerkleTx> vtxPrev; mapValue_t mapValue; std::vector<std::pair<std::string, std::string> > vOrderForm; unsigned int fTimeReceivedIsTxTime; @@ -447,7 +453,6 @@ public: unsigned int nTimeSmart; char fFromMe; std::string strFromAccount; - std::vector<char> vfSpent; // which outputs are already spent int64_t nOrderPos; // position in ordered transaction list // memory only @@ -485,7 +490,6 @@ public: void Init(const CWallet* pwalletIn) { pwallet = pwalletIn; - vtxPrev.clear(); mapValue.clear(); vOrderForm.clear(); fTimeReceivedIsTxTime = false; @@ -493,7 +497,6 @@ public: nTimeSmart = 0; fFromMe = false; strFromAccount.clear(); - vfSpent.clear(); fDebitCached = false; fCreditCached = false; fImmatureCreditCached = false; @@ -518,15 +521,6 @@ public: { pthis->mapValue["fromaccount"] = pthis->strFromAccount; - std::string str; - BOOST_FOREACH(char f, vfSpent) - { - str += (f ? '1' : '0'); - if (f) - fSpent = true; - } - pthis->mapValue["spent"] = str; - WriteOrderPos(pthis->nOrderPos, pthis->mapValue); if (nTimeSmart) @@ -534,7 +528,8 @@ public: } nSerSize += SerReadWrite(s, *(CMerkleTx*)this, nType, nVersion,ser_action); - READWRITE(vtxPrev); + std::vector<CMerkleTx> vUnused; // Used to be vtxPrev + READWRITE(vUnused); READWRITE(mapValue); READWRITE(vOrderForm); READWRITE(fTimeReceivedIsTxTime); @@ -546,12 +541,6 @@ public: { pthis->strFromAccount = pthis->mapValue["fromaccount"]; - if (mapValue.count("spent")) - BOOST_FOREACH(char c, pthis->mapValue["spent"]) - pthis->vfSpent.push_back(c != '0'); - else - pthis->vfSpent.assign(vout.size(), fSpent); - ReadOrderPos(pthis->nOrderPos, pthis->mapValue); pthis->nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(pthis->mapValue["timesmart"]) : 0; @@ -564,26 +553,6 @@ public: pthis->mapValue.erase("timesmart"); ) - // marks certain txout's as spent - // returns true if any update took place - bool UpdateSpent(const std::vector<char>& vfNewSpent) - { - bool fReturn = false; - for (unsigned int i = 0; i < vfNewSpent.size(); i++) - { - if (i == vfSpent.size()) - break; - - if (vfNewSpent[i] && !vfSpent[i]) - { - vfSpent[i] = true; - fReturn = true; - fAvailableCreditCached = false; - } - } - return fReturn; - } - // make sure balances are recalculated void MarkDirty() { @@ -599,27 +568,6 @@ public: MarkDirty(); } - void MarkSpent(unsigned int nOut) - { - if (nOut >= vout.size()) - throw std::runtime_error("CWalletTx::MarkSpent() : nOut out of range"); - vfSpent.resize(vout.size()); - if (!vfSpent[nOut]) - { - vfSpent[nOut] = true; - fAvailableCreditCached = false; - } - } - - bool IsSpent(unsigned int nOut) const - { - if (nOut >= vout.size()) - throw std::runtime_error("CWalletTx::IsSpent() : nOut out of range"); - if (nOut >= vfSpent.size()) - return false; - return (!!vfSpent[nOut]); - } - int64_t GetDebit() const { if (vin.empty()) @@ -661,6 +609,9 @@ public: int64_t GetAvailableCredit(bool fUseCache=true) const { + if (pwallet == 0) + return 0; + // Must wait until coinbase is safely deep enough in the chain before valuing it if (IsCoinBase() && GetBlocksToMaturity() > 0) return 0; @@ -671,7 +622,7 @@ public: int64_t nCredit = 0; for (unsigned int i = 0; i < vout.size(); i++) { - if (!IsSpent(i)) + if (!pwallet->IsSpent(GetHash(), i)) { const CTxOut &txout = vout[i]; nCredit += pwallet->GetCredit(txout); @@ -719,38 +670,14 @@ public: if (!bSpendZeroConfChange || !IsFromMe()) // using wtx's cached debit return false; - // If no confirmations but it's from us, we can still - // consider it confirmed if all dependencies are confirmed - std::map<uint256, const CMerkleTx*> mapPrev; - std::vector<const CMerkleTx*> vWorkQueue; - vWorkQueue.reserve(vtxPrev.size()+1); - vWorkQueue.push_back(this); - for (unsigned int i = 0; i < vWorkQueue.size(); i++) + // Trusted if all inputs are from us and are in the mempool: + BOOST_FOREACH(const CTxIn& txin, vin) { - const CMerkleTx* ptx = vWorkQueue[i]; - - if (!IsFinalTx(*ptx)) + // Transactions not sent by us: not trusted + const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash); + const CTxOut& parentOut = parent->vout[txin.prevout.n]; + if (parent == NULL || !pwallet->IsMine(parentOut)) return false; - int nPDepth = ptx->GetDepthInMainChain(); - if (nPDepth >= 1) - continue; - if (nPDepth < 0) - return false; - if (!pwallet->IsFromMe(*ptx)) - return false; - - if (mapPrev.empty()) - { - BOOST_FOREACH(const CMerkleTx& tx, vtxPrev) - mapPrev[tx.GetHash()] = &tx; - } - - BOOST_FOREACH(const CTxIn& txin, ptx->vin) - { - if (!mapPrev.count(txin.prevout.hash)) - return false; - vWorkQueue.push_back(mapPrev[txin.prevout.hash]); - } } return true; } @@ -760,8 +687,6 @@ public: int64_t GetTxTime() const; int GetRequestCount() const; - void AddSupportingTransactions(); - bool AcceptWalletTransaction(); void RelayWalletTransaction(); std::set<uint256> GetConflicts() const; |