aboutsummaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa')
-rw-r--r--qa/README.md87
-rwxr-xr-xqa/pull-tester/rpc-tests.py341
-rwxr-xr-xqa/pull-tester/rpc-tests.sh32
-rwxr-xr-xqa/pull-tester/run-bitcoin-cli13
-rw-r--r--[-rwxr-xr-x]qa/pull-tester/run-bitcoind-for-test.sh.in2
-rw-r--r--[-rwxr-xr-x]qa/pull-tester/tests_config.py.in (renamed from qa/pull-tester/tests-config.sh.in)10
-rw-r--r--qa/rpc-tests/README.md129
-rwxr-xr-xqa/rpc-tests/abandonconflict.py160
-rwxr-xr-xqa/rpc-tests/bip65-cltv-p2p.py181
-rwxr-xr-xqa/rpc-tests/bip65-cltv.py91
-rwxr-xr-xqa/rpc-tests/bip68-112-113-p2p.py539
-rwxr-xr-xqa/rpc-tests/bip68-sequence.py428
-rwxr-xr-xqa/rpc-tests/bip9-softforks.py240
-rwxr-xr-xqa/rpc-tests/bipdersig-p2p.py189
-rwxr-xr-xqa/rpc-tests/bipdersig.py91
-rwxr-xr-xqa/rpc-tests/blockchain.py89
-rwxr-xr-xqa/rpc-tests/conflictedbalance.sh147
-rwxr-xr-xqa/rpc-tests/create_cache.py23
-rwxr-xr-xqa/rpc-tests/decodescript.py185
-rwxr-xr-xqa/rpc-tests/disablewallet.py48
-rwxr-xr-xqa/rpc-tests/forknotify.py24
-rwxr-xr-xqa/rpc-tests/fundrawtransaction.py648
-rwxr-xr-xqa/rpc-tests/getblocktemplate_longpoll.py46
-rwxr-xr-xqa/rpc-tests/getblocktemplate_proposals.py61
-rwxr-xr-xqa/rpc-tests/getchaintips.py17
-rwxr-xr-xqa/rpc-tests/httpbasics.py149
-rwxr-xr-xqa/rpc-tests/importprunedfunds.py143
-rwxr-xr-xqa/rpc-tests/invalidateblock.py76
-rwxr-xr-xqa/rpc-tests/invalidblockrequest.py117
-rwxr-xr-xqa/rpc-tests/invalidtxrequest.py72
-rwxr-xr-xqa/rpc-tests/keypool.py187
-rwxr-xr-xqa/rpc-tests/listtransactions.py189
-rwxr-xr-xqa/rpc-tests/maxblocksinflight.py96
-rwxr-xr-xqa/rpc-tests/maxuploadtarget.py266
-rwxr-xr-xqa/rpc-tests/mempool_limit.py54
-rwxr-xr-xqa/rpc-tests/mempool_packages.py243
-rwxr-xr-xqa/rpc-tests/mempool_reorg.py (renamed from qa/rpc-tests/mempool_coinbase_spends.py)59
-rwxr-xr-xqa/rpc-tests/mempool_resurrect_test.py35
-rwxr-xr-xqa/rpc-tests/mempool_spendcoinbase.py28
-rwxr-xr-xqa/rpc-tests/merkle_blocks.py88
-rwxr-xr-xqa/rpc-tests/multi_rpc.py120
-rwxr-xr-xqa/rpc-tests/nodehandling.py86
-rwxr-xr-xqa/rpc-tests/p2p-acceptblock.py291
-rwxr-xr-xqa/rpc-tests/p2p-feefilter.py105
-rwxr-xr-xqa/rpc-tests/p2p-fullblocktest.py1293
-rwxr-xr-xqa/rpc-tests/p2p-mempool.py99
-rwxr-xr-xqa/rpc-tests/p2p-segwit.py1694
-rwxr-xr-xqa/rpc-tests/p2p-versionbits-warning.py161
-rwxr-xr-xqa/rpc-tests/prioritise_transaction.py143
-rwxr-xr-xqa/rpc-tests/proxy_test.py195
-rwxr-xr-xqa/rpc-tests/pruning.py341
-rw-r--r--qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/.gitignore1
-rw-r--r--qa/rpc-tests/python-bitcoinrpc/setup.py15
-rwxr-xr-xqa/rpc-tests/rawtransactions.py162
-rwxr-xr-xqa/rpc-tests/receivedby.py65
-rwxr-xr-xqa/rpc-tests/reindex.py43
-rwxr-xr-xqa/rpc-tests/replace-by-fee.py590
-rwxr-xr-xqa/rpc-tests/rest.py318
-rwxr-xr-xqa/rpc-tests/rpcbind_test.py25
-rwxr-xr-xqa/rpc-tests/segwit.py209
-rwxr-xr-xqa/rpc-tests/send.sh31
-rwxr-xr-xqa/rpc-tests/sendheaders.py623
-rwxr-xr-xqa/rpc-tests/signmessages.py41
-rwxr-xr-xqa/rpc-tests/signrawtransactions.py110
-rwxr-xr-xqa/rpc-tests/smartfees.py299
-rwxr-xr-xqa/rpc-tests/test_framework.py139
-rw-r--r--qa/rpc-tests/test_framework/__init__.py (renamed from qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/__init__.py)0
-rw-r--r--qa/rpc-tests/test_framework/authproxy.py (renamed from qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/authproxy.py)75
-rw-r--r--qa/rpc-tests/test_framework/bignum.py101
-rw-r--r--qa/rpc-tests/test_framework/blockstore.py161
-rw-r--r--qa/rpc-tests/test_framework/blocktools.py105
-rwxr-xr-xqa/rpc-tests/test_framework/comptool.py401
-rw-r--r--qa/rpc-tests/test_framework/coverage.py106
-rw-r--r--qa/rpc-tests/test_framework/key.py215
-rwxr-xr-xqa/rpc-tests/test_framework/mininode.py1531
-rw-r--r--qa/rpc-tests/test_framework/netutil.py (renamed from qa/rpc-tests/netutil.py)32
-rw-r--r--qa/rpc-tests/test_framework/script.py947
-rw-r--r--qa/rpc-tests/test_framework/socks5.py161
-rwxr-xr-xqa/rpc-tests/test_framework/test_framework.py214
-rw-r--r--qa/rpc-tests/test_framework/util.py641
-rwxr-xr-xqa/rpc-tests/txn_clone.py159
-rwxr-xr-xqa/rpc-tests/txn_doublespend.py109
-rw-r--r--qa/rpc-tests/util.py343
-rw-r--r--qa/rpc-tests/util.sh103
-rwxr-xr-xqa/rpc-tests/wallet-hd.py87
-rwxr-xr-xqa/rpc-tests/wallet.py330
-rwxr-xr-xqa/rpc-tests/walletbackup.py33
-rwxr-xr-xqa/rpc-tests/zapwallettxes.py77
-rwxr-xr-xqa/rpc-tests/zapwallettxes.sh165
-rwxr-xr-xqa/rpc-tests/zmq_test.py100
90 files changed, 17329 insertions, 1689 deletions
diff --git a/qa/README.md b/qa/README.md
new file mode 100644
index 0000000000..723660c6c8
--- /dev/null
+++ b/qa/README.md
@@ -0,0 +1,87 @@
+The [pull-tester](/qa/pull-tester/) folder contains a script to call
+multiple tests from the [rpc-tests](/qa/rpc-tests/) folder.
+
+Every pull request to the bitcoin repository is built and run through
+the regression test suite. You can also run all or only individual
+tests locally.
+
+Test dependencies
+=================
+Before running the tests, the following must be installed.
+
+Unix
+----
+The python3-zmq library is required. On Ubuntu or Debian it can be installed via:
+```
+sudo apt-get install python3-zmq
+```
+
+OS X
+------
+```
+pip3 install pyzmq
+```
+
+Running tests
+=============
+
+You can run any single test by calling
+
+ qa/pull-tester/rpc-tests.py <testname>
+
+Or you can run any combination of tests by calling
+
+ qa/pull-tester/rpc-tests.py <testname1> <testname2> <testname3> ...
+
+Run the regression test suite with
+
+ qa/pull-tester/rpc-tests.py
+
+Run all possible tests with
+
+ qa/pull-tester/rpc-tests.py -extended
+
+By default, tests will be run in parallel if you want to specify how many
+tests should be run in parallel, append `-parallel=n` (default n=4).
+
+If you want to create a basic coverage report for the rpc test suite, append `--coverage`.
+
+Possible options, which apply to each individual test run:
+
+```
+ -h, --help show this help message and exit
+ --nocleanup Leave bitcoinds and test.* datadir on exit or error
+ --noshutdown Don't stop bitcoinds after the test execution
+ --srcdir=SRCDIR Source directory containing bitcoind/bitcoin-cli
+ (default: ../../src)
+ --tmpdir=TMPDIR Root directory for datadirs
+ --tracerpc Print out all RPC calls as they are made
+ --coveragedir=COVERAGEDIR
+ Write tested RPC commands into this directory
+```
+
+If you set the environment variable `PYTHON_DEBUG=1` you will get some debug
+output (example: `PYTHON_DEBUG=1 qa/pull-tester/rpc-tests.py wallet`).
+
+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 its wallet.
+
+After the first run, the cache/ blockchain and wallets are
+copied into a temporary directory and used as the initial
+test state.
+
+If you get into a bad state, you should be able
+to recover with:
+
+```bash
+rm -rf cache
+killall bitcoind
+```
+
+Writing tests
+=============
+You are encouraged to write tests for new or existing features.
+Further information about the test framework and individual rpc
+tests is found in [qa/rpc-tests](/qa/rpc-tests).
diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py
new file mode 100755
index 0000000000..11b83bac14
--- /dev/null
+++ b/qa/pull-tester/rpc-tests.py
@@ -0,0 +1,341 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+"""
+Run Regression Test Suite
+
+This module calls down into individual test cases via subprocess. It will
+forward all unrecognized arguments onto the individual test scripts, other
+than:
+
+ - `-extended`: run the "extended" test suite in addition to the basic one.
+ - `-win`: signal that this is running in a Windows environment, and we
+ should run the tests.
+ - `--coverage`: this generates a basic coverage report for the RPC
+ interface.
+
+For a description of arguments recognized by test scripts, see
+`qa/pull-tester/test_framework/test_framework.py:BitcoinTestFramework.main`.
+
+"""
+
+import os
+import time
+import shutil
+import sys
+import subprocess
+import tempfile
+import re
+
+sys.path.append("qa/pull-tester/")
+from tests_config import *
+
+BOLD = ("","")
+if os.name == 'posix':
+ # primitive formatting on supported
+ # terminal via ANSI escape sequences:
+ BOLD = ('\033[0m', '\033[1m')
+
+RPC_TESTS_DIR = SRCDIR + '/qa/rpc-tests/'
+
+#If imported values are not defined then set to zero (or disabled)
+if 'ENABLE_WALLET' not in vars():
+ ENABLE_WALLET=0
+if 'ENABLE_BITCOIND' not in vars():
+ ENABLE_BITCOIND=0
+if 'ENABLE_UTILS' not in vars():
+ ENABLE_UTILS=0
+if 'ENABLE_ZMQ' not in vars():
+ ENABLE_ZMQ=0
+
+ENABLE_COVERAGE=0
+
+#Create a set to store arguments and create the passon string
+opts = set()
+passon_args = []
+PASSON_REGEX = re.compile("^--")
+PARALLEL_REGEX = re.compile('^-parallel=')
+
+print_help = False
+run_parallel = 4
+
+for arg in sys.argv[1:]:
+ if arg == "--help" or arg == "-h" or arg == "-?":
+ print_help = True
+ break
+ if arg == '--coverage':
+ ENABLE_COVERAGE = 1
+ elif PASSON_REGEX.match(arg):
+ passon_args.append(arg)
+ elif PARALLEL_REGEX.match(arg):
+ run_parallel = int(arg.split(sep='=', maxsplit=1)[1])
+ else:
+ opts.add(arg)
+
+#Set env vars
+if "BITCOIND" not in os.environ:
+ os.environ["BITCOIND"] = BUILDDIR + '/src/bitcoind' + EXEEXT
+if "BITCOINCLI" not in os.environ:
+ os.environ["BITCOINCLI"] = BUILDDIR + '/src/bitcoin-cli' + EXEEXT
+
+if EXEEXT == ".exe" and "-win" not in opts:
+ # https://github.com/bitcoin/bitcoin/commit/d52802551752140cf41f0d9a225a43e84404d3e9
+ # https://github.com/bitcoin/bitcoin/pull/5677#issuecomment-136646964
+ print("Win tests currently disabled by default. Use -win option to enable")
+ sys.exit(0)
+
+if not (ENABLE_WALLET == 1 and ENABLE_UTILS == 1 and ENABLE_BITCOIND == 1):
+ print("No rpc tests to run. Wallet, utils, and bitcoind must all be enabled")
+ sys.exit(0)
+
+# python3-zmq may not be installed. Handle this gracefully and with some helpful info
+if ENABLE_ZMQ:
+ try:
+ import zmq
+ except ImportError as e:
+ print("WARNING: \"import zmq\" failed. Set ENABLE_ZMQ=0 or " \
+ "to run zmq tests, see dependency info in /qa/README.md.")
+ ENABLE_ZMQ=0
+
+#Tests
+testScripts = [
+ # longest test should go first, to favor running tests in parallel
+ 'p2p-fullblocktest.py',
+ 'walletbackup.py',
+ 'bip68-112-113-p2p.py',
+ 'wallet.py',
+ 'wallet-hd.py',
+ 'listtransactions.py',
+ 'receivedby.py',
+ 'mempool_resurrect_test.py',
+ 'txn_doublespend.py --mineblock',
+ 'txn_clone.py',
+ 'getchaintips.py',
+ 'rawtransactions.py',
+ 'rest.py',
+ 'mempool_spendcoinbase.py',
+ 'mempool_reorg.py',
+ 'mempool_limit.py',
+ 'httpbasics.py',
+ 'multi_rpc.py',
+ 'zapwallettxes.py',
+ 'proxy_test.py',
+ 'merkle_blocks.py',
+ 'fundrawtransaction.py',
+ 'signrawtransactions.py',
+ 'nodehandling.py',
+ 'reindex.py',
+ 'decodescript.py',
+ 'blockchain.py',
+ 'disablewallet.py',
+ 'sendheaders.py',
+ 'keypool.py',
+ 'prioritise_transaction.py',
+ 'invalidblockrequest.py',
+ 'invalidtxrequest.py',
+ 'abandonconflict.py',
+ 'p2p-versionbits-warning.py',
+ 'p2p-segwit.py',
+ 'segwit.py',
+ 'importprunedfunds.py',
+ 'signmessages.py',
+]
+if ENABLE_ZMQ:
+ testScripts.append('zmq_test.py')
+
+testScriptsExt = [
+ 'bip9-softforks.py',
+ 'bip65-cltv.py',
+ 'bip65-cltv-p2p.py',
+ 'bip68-sequence.py',
+ 'bipdersig-p2p.py',
+ 'bipdersig.py',
+ 'getblocktemplate_longpoll.py',
+ 'getblocktemplate_proposals.py',
+ 'txn_doublespend.py',
+ 'txn_clone.py --mineblock',
+ 'forknotify.py',
+ 'invalidateblock.py',
+# 'rpcbind_test.py', #temporary, bug in libevent, see #6655
+ 'smartfees.py',
+ 'maxblocksinflight.py',
+ 'p2p-acceptblock.py',
+ 'mempool_packages.py',
+ 'maxuploadtarget.py',
+ 'replace-by-fee.py',
+ 'p2p-feefilter.py',
+ 'pruning.py', # leave pruning last as it takes a REALLY long time
+]
+
+
+def runtests():
+ test_list = []
+ if '-extended' in opts:
+ test_list = testScripts + testScriptsExt
+ elif len(opts) == 0 or (len(opts) == 1 and "-win" in opts):
+ test_list = testScripts
+ else:
+ for t in testScripts + testScriptsExt:
+ if t in opts or re.sub(".py$", "", t) in opts:
+ test_list.append(t)
+
+ if print_help:
+ # Only print help of the first script and exit
+ subprocess.check_call((RPC_TESTS_DIR + test_list[0]).split() + ['-h'])
+ sys.exit(0)
+
+ coverage = None
+
+ if ENABLE_COVERAGE:
+ coverage = RPCCoverage()
+ print("Initializing coverage directory at %s\n" % coverage.dir)
+ flags = ["--srcdir=%s/src" % BUILDDIR] + passon_args
+ if coverage:
+ flags.append(coverage.flag)
+
+ if len(test_list) > 1 and run_parallel > 1:
+ # Populate cache
+ subprocess.check_output([RPC_TESTS_DIR + 'create_cache.py'] + flags)
+
+ #Run Tests
+ max_len_name = len(max(test_list, key=len))
+ time_sum = 0
+ time0 = time.time()
+ job_queue = RPCTestHandler(run_parallel, test_list, flags)
+ results = BOLD[1] + "%s | %s | %s\n\n" % ("TEST".ljust(max_len_name), "PASSED", "DURATION") + BOLD[0]
+ all_passed = True
+ for _ in range(len(test_list)):
+ (name, stdout, stderr, passed, duration) = job_queue.get_next()
+ all_passed = all_passed and passed
+ time_sum += duration
+
+ print('\n' + BOLD[1] + name + BOLD[0] + ":")
+ print(stdout)
+ print('stderr:\n' if not stderr == '' else '', stderr)
+ results += "%s | %s | %s s\n" % (name.ljust(max_len_name), str(passed).ljust(6), duration)
+ print("Pass: %s%s%s, Duration: %s s\n" % (BOLD[1], passed, BOLD[0], duration))
+ results += BOLD[1] + "\n%s | %s | %s s (accumulated)" % ("ALL".ljust(max_len_name), str(all_passed).ljust(6), time_sum) + BOLD[0]
+ print(results)
+ print("\nRuntime: %s s" % (int(time.time() - time0)))
+
+ if coverage:
+ coverage.report_rpc_coverage()
+
+ print("Cleaning up coverage data")
+ coverage.cleanup()
+
+ sys.exit(not all_passed)
+
+
+class RPCTestHandler:
+ """
+ Trigger the testscrips passed in via the list.
+ """
+
+ def __init__(self, num_tests_parallel, test_list=None, flags=None):
+ assert(num_tests_parallel >= 1)
+ self.num_jobs = num_tests_parallel
+ self.test_list = test_list
+ self.flags = flags
+ self.num_running = 0
+ self.jobs = []
+
+ def get_next(self):
+ while self.num_running < self.num_jobs and self.test_list:
+ # Add tests
+ self.num_running += 1
+ t = self.test_list.pop(0)
+ port_seed = ["--portseed=%s" % len(self.test_list)]
+ self.jobs.append((t,
+ time.time(),
+ subprocess.Popen((RPC_TESTS_DIR + t).split() + self.flags + port_seed,
+ universal_newlines=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)))
+ if not self.jobs:
+ raise IndexError('pop from empty list')
+ while True:
+ # Return first proc that finishes
+ time.sleep(.5)
+ for j in self.jobs:
+ (name, time0, proc) = j
+ if proc.poll() is not None:
+ (stdout, stderr) = proc.communicate(timeout=3)
+ passed = stderr == "" and proc.returncode == 0
+ self.num_running -= 1
+ self.jobs.remove(j)
+ return name, stdout, stderr, passed, int(time.time() - time0)
+ print('.', end='', flush=True)
+
+
+class RPCCoverage(object):
+ """
+ Coverage reporting utilities for pull-tester.
+
+ Coverage calculation works by having each test script subprocess write
+ coverage files into a particular directory. These files contain the RPC
+ commands invoked during testing, as well as a complete listing of RPC
+ commands per `bitcoin-cli help` (`rpc_interface.txt`).
+
+ After all tests complete, the commands run are combined and diff'd against
+ the complete list to calculate uncovered RPC commands.
+
+ See also: qa/rpc-tests/test_framework/coverage.py
+
+ """
+ def __init__(self):
+ self.dir = tempfile.mkdtemp(prefix="coverage")
+ self.flag = '--coveragedir=%s' % self.dir
+
+ def report_rpc_coverage(self):
+ """
+ Print out RPC commands that were unexercised by tests.
+
+ """
+ uncovered = self._get_uncovered_rpc_commands()
+
+ if uncovered:
+ print("Uncovered RPC commands:")
+ print("".join((" - %s\n" % i) for i in sorted(uncovered)))
+ else:
+ print("All RPC commands covered.")
+
+ def cleanup(self):
+ return shutil.rmtree(self.dir)
+
+ def _get_uncovered_rpc_commands(self):
+ """
+ Return a set of currently untested RPC commands.
+
+ """
+ # This is shared from `qa/rpc-tests/test-framework/coverage.py`
+ REFERENCE_FILENAME = 'rpc_interface.txt'
+ COVERAGE_FILE_PREFIX = 'coverage.'
+
+ coverage_ref_filename = os.path.join(self.dir, REFERENCE_FILENAME)
+ coverage_filenames = set()
+ all_cmds = set()
+ covered_cmds = set()
+
+ if not os.path.isfile(coverage_ref_filename):
+ raise RuntimeError("No coverage reference found")
+
+ with open(coverage_ref_filename, 'r') as f:
+ all_cmds.update([i.strip() for i in f.readlines()])
+
+ for root, dirs, files in os.walk(self.dir):
+ for filename in files:
+ if filename.startswith(COVERAGE_FILE_PREFIX):
+ coverage_filenames.add(os.path.join(root, filename))
+
+ for filename in coverage_filenames:
+ with open(filename, 'r') as f:
+ covered_cmds.update([i.strip() for i in f.readlines()])
+
+ return all_cmds - covered_cmds
+
+
+if __name__ == '__main__':
+ runtests()
diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh
deleted file mode 100755
index d6ee00bb7d..0000000000
--- a/qa/pull-tester/rpc-tests.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-set -e
-
-CURDIR=$(cd $(dirname "$0"); pwd)
-# Get BUILDDIR and REAL_BITCOIND
-. "${CURDIR}/tests-config.sh"
-
-export BITCOINCLI=${BUILDDIR}/qa/pull-tester/run-bitcoin-cli
-export BITCOIND=${REAL_BITCOIND}
-
-if [ "x${EXEEXT}" = "x.exe" ]; then
- echo "Win tests currently disabled"
- exit 0
-fi
-
-#Run the tests
-
-if [ "x${ENABLE_BITCOIND}${ENABLE_UTILS}${ENABLE_WALLET}" = "x111" ]; then
- ${BUILDDIR}/qa/rpc-tests/wallet.py --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/listtransactions.py --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/mempool_resurrect_test.py --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/txn_doublespend.py --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/txn_doublespend.py --mineblock --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/getchaintips.py --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/rest.py --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/mempool_spendcoinbase.py --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/httpbasics.py --srcdir "${BUILDDIR}/src"
- ${BUILDDIR}/qa/rpc-tests/mempool_coinbase_spends.py --srcdir "${BUILDDIR}/src"
- #${BUILDDIR}/qa/rpc-tests/forknotify.py --srcdir "${BUILDDIR}/src"
-else
- echo "No rpc tests to run. Wallet, utils, and bitcoind must all be enabled"
-fi
diff --git a/qa/pull-tester/run-bitcoin-cli b/qa/pull-tester/run-bitcoin-cli
deleted file mode 100755
index 93c25bb9fc..0000000000
--- a/qa/pull-tester/run-bitcoin-cli
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-# This is a thin wrapper around bitcoin-cli that strips the Windows-style EOLs
-# from the output if present. It is necessary when using bitcoin-cli.exe on
-# Linux since shells will interpret the line-endings as part of the result.
-
-CURDIR=$(cd $(dirname "$0"); pwd)
-# Get BUILDDIR and REAL_BITCOIND
-
-# Grab the value of $REAL_BITCOINCLI which may be bitcoin-cli.exe.
-. "${CURDIR}/tests-config.sh"
-
-"${REAL_BITCOINCLI}" "$@" | sed 's/\r//'
diff --git a/qa/pull-tester/run-bitcoind-for-test.sh.in b/qa/pull-tester/run-bitcoind-for-test.sh.in
index 42d8ad52e8..14ae08e4e5 100755..100644
--- a/qa/pull-tester/run-bitcoind-for-test.sh.in
+++ b/qa/pull-tester/run-bitcoind-for-test.sh.in
@@ -10,7 +10,7 @@ touch "$DATADIR/regtest/debug.log"
tail -q -n 1 -F "$DATADIR/regtest/debug.log" | grep -m 1 -q "Done loading" &
WAITER=$!
PORT=`expr 10000 + $$ % 55536`
-"@abs_top_builddir@/src/bitcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -checkmempool=0 -port=$PORT -whitelist=127.0.0.1 -regtest -rpcport=`expr $PORT + 1` &
+"@abs_top_builddir@/src/bitcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -checkmempool=0 -relaypriority=0 -port=$PORT -whitelist=127.0.0.1 -regtest -rpcport=`expr $PORT + 1` &
BITCOIND=$!
#Install a watchdog.
diff --git a/qa/pull-tester/tests-config.sh.in b/qa/pull-tester/tests_config.py.in
index 10f4d33e47..a0d0a3d98a 100755..100644
--- a/qa/pull-tester/tests-config.sh.in
+++ b/qa/pull-tester/tests_config.py.in
@@ -1,8 +1,9 @@
-#!/bin/bash
-# Copyright (c) 2013-2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2013-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+SRCDIR="@abs_top_srcdir@"
BUILDDIR="@abs_top_builddir@"
EXEEXT="@EXEEXT@"
@@ -10,7 +11,4 @@ EXEEXT="@EXEEXT@"
@ENABLE_WALLET_TRUE@ENABLE_WALLET=1
@BUILD_BITCOIN_UTILS_TRUE@ENABLE_UTILS=1
@BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=1
-
-REAL_BITCOIND="$BUILDDIR/src/bitcoind${EXEEXT}"
-REAL_BITCOINCLI="$BUILDDIR/src/bitcoin-cli${EXEEXT}"
-
+@ENABLE_ZMQ_TRUE@ENABLE_ZMQ=1
diff --git a/qa/rpc-tests/README.md b/qa/rpc-tests/README.md
index 3e916a7688..651b01f18a 100644
--- a/qa/rpc-tests/README.md
+++ b/qa/rpc-tests/README.md
@@ -1,43 +1,108 @@
-Regression tests of RPC interface
-=================================
+Regression tests
+================
-### [python-bitcoinrpc](https://github.com/jgarzik/python-bitcoinrpc)
-Git subtree of [https://github.com/jgarzik/python-bitcoinrpc](https://github.com/jgarzik/python-bitcoinrpc).
-Changes to python-bitcoinrpc should be made upstream, and then
-pulled here using git subtree.
+### [test_framework/authproxy.py](test_framework/authproxy.py)
+Taken from the [python-bitcoinrpc repository](https://github.com/jgarzik/python-bitcoinrpc).
-### [test_framework.py](test_framework.py)
+### [test_framework/test_framework.py](test_framework/test_framework.py)
Base class for new regression tests.
-### [listtransactions.py](listtransactions.py)
-Tests for the listtransactions RPC call.
-
-### [util.py](util.sh)
+### [test_framework/util.py](test_framework/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
+### [test_framework/mininode.py](test_framework/mininode.py)
+Basic code to support p2p connectivity to a bitcoind.
+
+### [test_framework/comptool.py](test_framework/comptool.py)
+Framework for comparison-tool style, p2p tests.
+
+### [test_framework/script.py](test_framework/script.py)
+Utilities for manipulating transaction scripts (originally from python-bitcoinlib)
+
+### [test_framework/blockstore.py](test_framework/blockstore.py)
+Implements disk-backed block and tx storage.
+
+### [test_framework/key.py](test_framework/key.py)
+Wrapper around OpenSSL EC_Key (originally from python-bitcoinlib)
+
+### [test_framework/bignum.py](test_framework/bignum.py)
+Helpers for script.py
+
+### [test_framework/blocktools.py](test_framework/blocktools.py)
+Helper functions for creating blocks and transactions.
+
+P2P test design notes
+---------------------
+
+## Mininode
+
+* ```mininode.py``` contains all the definitions for objects that pass
+over the network (```CBlock```, ```CTransaction```, etc, along with the network-level
+wrappers for them, ```msg_block```, ```msg_tx```, etc).
+
+* P2P tests have two threads. One thread handles all network communication
+with the bitcoind(s) being tested (using python's asyncore package); the other
+implements the test logic.
+
+* ```NodeConn``` is the class used to connect to a bitcoind. If you implement
+a callback class that derives from ```NodeConnCB``` and pass that to the
+```NodeConn``` object, your code will receive the appropriate callbacks when
+events of interest arrive.
+
+* You can pass the same handler to multiple ```NodeConn```'s if you like, or pass
+different ones to each -- whatever makes the most sense for your test.
+
+* Call ```NetworkThread.start()``` after all ```NodeConn``` objects are created to
+start the networking thread. (Continue with the test logic in your existing
+thread.)
+
+* RPC calls are available in p2p tests.
+
+* Can be used to write free-form tests, where specific p2p-protocol behavior
+is tested. Examples: ```p2p-accept-block.py```, ```maxblocksinflight.py```.
+
+## Comptool
-Notes
-=====
+* Testing framework for writing tests that compare the block/tx acceptance
+behavior of a bitcoind against 1 or more other bitcoind instances, or against
+known outcomes, or both.
-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.
+* Set the ```num_nodes``` variable (defined in ```ComparisonTestFramework```) to start up
+1 or more nodes. If using 1 node, then ```--testbinary``` can be used as a command line
+option to change the bitcoind binary used by the test. If using 2 or more nodes,
+then ```--refbinary``` can be optionally used to change the bitcoind that will be used
+on nodes 2 and up.
-After the first run, the cache/ blockchain and wallets are
-copied into a temporary directory and used as the initial
-test state.
+* Implement a (generator) function called ```get_tests()``` which yields ```TestInstance```s.
+Each ```TestInstance``` consists of:
+ - a list of ```[object, outcome, hash]``` entries
+ * ```object``` is a ```CBlock```, ```CTransaction```, or
+ ```CBlockHeader```. ```CBlock```'s and ```CTransaction```'s are tested for
+ acceptance. ```CBlockHeader```s can be used so that the test runner can deliver
+ complete headers-chains when requested from the bitcoind, to allow writing
+ tests where blocks can be delivered out of order but still processed by
+ headers-first bitcoind's.
+ * ```outcome``` is ```True```, ```False```, or ```None```. If ```True```
+ or ```False```, the tip is compared with the expected tip -- either the
+ block passed in, or the hash specified as the optional 3rd entry. If
+ ```None``` is specified, then the test will compare all the bitcoind's
+ being tested to see if they all agree on what the best tip is.
+ * ```hash``` is the block hash of the tip to compare against. Optional to
+ specify; if left out then the hash of the block passed in will be used as
+ the expected tip. This allows for specifying an expected tip while testing
+ the handling of either invalid blocks or blocks delivered out of order,
+ which complete a longer chain.
+ - ```sync_every_block```: ```True/False```. If ```False```, then all blocks
+ are inv'ed together, and the test runner waits until the node receives the
+ last one, and tests only the last block for tip acceptance using the
+ outcome and specified tip. If ```True```, then each block is tested in
+ sequence and synced (this is slower when processing many blocks).
+ - ```sync_every_transaction```: ```True/False```. Analogous to
+ ```sync_every_block```, except if the outcome on the last tx is "None",
+ then the contents of the entire mempool are compared across all bitcoind
+ connections. If ```True``` or ```False```, then only the last tx's
+ acceptance is tested against the given outcome.
-If you get into a bad state, you should be able
-to recover with:
+* For examples of tests written in this framework, see
+ ```invalidblockrequest.py``` and ```p2p-fullblocktest.py```.
-```bash
-rm -rf cache
-killall bitcoind
-```
diff --git a/qa/rpc-tests/abandonconflict.py b/qa/rpc-tests/abandonconflict.py
new file mode 100755
index 0000000000..c50c3cc562
--- /dev/null
+++ b/qa/rpc-tests/abandonconflict.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import urllib.parse
+
+class AbandonConflictTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug","-logtimemicros","-minrelaytxfee=0.00001"]))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-debug","-logtimemicros"]))
+ connect_nodes(self.nodes[0], 1)
+
+ def run_test(self):
+ self.nodes[1].generate(100)
+ sync_blocks(self.nodes)
+ balance = self.nodes[0].getbalance()
+ txA = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
+ txB = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
+ txC = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
+ sync_mempools(self.nodes)
+ self.nodes[1].generate(1)
+
+ sync_blocks(self.nodes)
+ newbalance = self.nodes[0].getbalance()
+ assert(balance - newbalance < Decimal("0.001")) #no more than fees lost
+ balance = newbalance
+
+ url = urllib.parse.urlparse(self.nodes[1].url)
+ self.nodes[0].disconnectnode(url.hostname+":"+str(p2p_port(1)))
+
+ # Identify the 10btc outputs
+ nA = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txA, 1)["vout"]) if vout["value"] == Decimal("10"))
+ nB = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txB, 1)["vout"]) if vout["value"] == Decimal("10"))
+ nC = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txC, 1)["vout"]) if vout["value"] == Decimal("10"))
+
+ inputs =[]
+ # spend 10btc outputs from txA and txB
+ inputs.append({"txid":txA, "vout":nA})
+ inputs.append({"txid":txB, "vout":nB})
+ outputs = {}
+
+ outputs[self.nodes[0].getnewaddress()] = Decimal("14.99998")
+ outputs[self.nodes[1].getnewaddress()] = Decimal("5")
+ signed = self.nodes[0].signrawtransaction(self.nodes[0].createrawtransaction(inputs, outputs))
+ txAB1 = self.nodes[0].sendrawtransaction(signed["hex"])
+
+ # Identify the 14.99998btc output
+ nAB = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txAB1, 1)["vout"]) if vout["value"] == Decimal("14.99998"))
+
+ #Create a child tx spending AB1 and C
+ inputs = []
+ inputs.append({"txid":txAB1, "vout":nAB})
+ inputs.append({"txid":txC, "vout":nC})
+ outputs = {}
+ outputs[self.nodes[0].getnewaddress()] = Decimal("24.9996")
+ signed2 = self.nodes[0].signrawtransaction(self.nodes[0].createrawtransaction(inputs, outputs))
+ txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"])
+
+ # In mempool txs from self should increase balance from change
+ newbalance = self.nodes[0].getbalance()
+ assert(newbalance == balance - Decimal("30") + Decimal("24.9996"))
+ balance = newbalance
+
+ # Restart the node with a higher min relay fee so the parent tx is no longer in mempool
+ # TODO: redo with eviction
+ # Note had to make sure tx did not have AllowFree priority
+ stop_node(self.nodes[0],0)
+ self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug","-logtimemicros","-minrelaytxfee=0.0001"])
+
+ # Verify txs no longer in mempool
+ assert(len(self.nodes[0].getrawmempool()) == 0)
+
+ # Not in mempool txs from self should only reduce balance
+ # inputs are still spent, but change not received
+ newbalance = self.nodes[0].getbalance()
+ assert(newbalance == balance - Decimal("24.9996"))
+ # Unconfirmed received funds that are not in mempool, also shouldn't show
+ # up in unconfirmed balance
+ unconfbalance = self.nodes[0].getunconfirmedbalance() + self.nodes[0].getbalance()
+ assert(unconfbalance == newbalance)
+ # Also shouldn't show up in listunspent
+ assert(not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)])
+ balance = newbalance
+
+ # Abandon original transaction and verify inputs are available again
+ # including that the child tx was also abandoned
+ self.nodes[0].abandontransaction(txAB1)
+ newbalance = self.nodes[0].getbalance()
+ assert(newbalance == balance + Decimal("30"))
+ balance = newbalance
+
+ # Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned
+ stop_node(self.nodes[0],0)
+ self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug","-logtimemicros","-minrelaytxfee=0.00001"])
+ assert(len(self.nodes[0].getrawmempool()) == 0)
+ assert(self.nodes[0].getbalance() == balance)
+
+ # But if its received again then it is unabandoned
+ # And since now in mempool, the change is available
+ # But its child tx remains abandoned
+ self.nodes[0].sendrawtransaction(signed["hex"])
+ newbalance = self.nodes[0].getbalance()
+ assert(newbalance == balance - Decimal("20") + Decimal("14.99998"))
+ balance = newbalance
+
+ # Send child tx again so its unabandoned
+ self.nodes[0].sendrawtransaction(signed2["hex"])
+ newbalance = self.nodes[0].getbalance()
+ assert(newbalance == balance - Decimal("10") - Decimal("14.99998") + Decimal("24.9996"))
+ balance = newbalance
+
+ # Remove using high relay fee again
+ stop_node(self.nodes[0],0)
+ self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug","-logtimemicros","-minrelaytxfee=0.0001"])
+ assert(len(self.nodes[0].getrawmempool()) == 0)
+ newbalance = self.nodes[0].getbalance()
+ assert(newbalance == balance - Decimal("24.9996"))
+ balance = newbalance
+
+ # Create a double spend of AB1 by spending again from only A's 10 output
+ # Mine double spend from node 1
+ inputs =[]
+ inputs.append({"txid":txA, "vout":nA})
+ outputs = {}
+ outputs[self.nodes[1].getnewaddress()] = Decimal("9.9999")
+ tx = self.nodes[0].createrawtransaction(inputs, outputs)
+ signed = self.nodes[0].signrawtransaction(tx)
+ self.nodes[1].sendrawtransaction(signed["hex"])
+ self.nodes[1].generate(1)
+
+ connect_nodes(self.nodes[0], 1)
+ sync_blocks(self.nodes)
+
+ # Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted
+ newbalance = self.nodes[0].getbalance()
+ assert(newbalance == balance + Decimal("20"))
+ balance = newbalance
+
+ # There is currently a minor bug around this and so this test doesn't work. See Issue #7315
+ # Invalidate the block with the double spend and B's 10 BTC output should no longer be available
+ # Don't think C's should either
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+ newbalance = self.nodes[0].getbalance()
+ #assert(newbalance == balance - Decimal("10"))
+ print("If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer")
+ print("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315")
+ print(str(balance) + " -> " + str(newbalance) + " ?")
+
+if __name__ == '__main__':
+ AbandonConflictTest().main()
diff --git a/qa/rpc-tests/bip65-cltv-p2p.py b/qa/rpc-tests/bip65-cltv-p2p.py
new file mode 100755
index 0000000000..754b6873b7
--- /dev/null
+++ b/qa/rpc-tests/bip65-cltv-p2p.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.util import *
+from test_framework.mininode import CTransaction, NetworkThread
+from test_framework.blocktools import create_coinbase, create_block
+from test_framework.comptool import TestInstance, TestManager
+from test_framework.script import CScript, OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP
+from io import BytesIO
+import time
+
+def cltv_invalidate(tx):
+ '''Modify the signature in vin 0 of the tx to fail CLTV
+
+ Prepends -1 CLTV DROP in the scriptSig itself.
+ '''
+ tx.vin[0].scriptSig = CScript([OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP] +
+ list(CScript(tx.vin[0].scriptSig)))
+
+'''
+This test is meant to exercise BIP65 (CHECKLOCKTIMEVERIFY)
+Connect to a single node.
+Mine 2 (version 3) blocks (save the coinbases for later).
+Generate 98 more version 3 blocks, verify the node accepts.
+Mine 749 version 4 blocks, verify the node accepts.
+Check that the new CLTV rules are not enforced on the 750th version 4 block.
+Check that the new CLTV rules are enforced on the 751st version 4 block.
+Mine 199 new version blocks.
+Mine 1 old-version block.
+Mine 1 new version block.
+Mine 1 old version block, see that the node rejects.
+'''
+
+class BIP65Test(ComparisonTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+
+ def setup_network(self):
+ # Must set the blockversion for this test
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
+ extra_args=[['-debug', '-whitelist=127.0.0.1', '-blockversion=3']],
+ binary=[self.options.testbinary])
+
+ def run_test(self):
+ test = TestManager(self, self.options.tmpdir)
+ test.add_all_connections(self.nodes)
+ NetworkThread().start() # Start up network handling in another thread
+ test.run()
+
+ def create_transaction(self, node, coinbase, to_address, amount):
+ from_txid = node.getblock(coinbase)['tx'][0]
+ inputs = [{ "txid" : from_txid, "vout" : 0}]
+ outputs = { to_address : amount }
+ rawtx = node.createrawtransaction(inputs, outputs)
+ signresult = node.signrawtransaction(rawtx)
+ tx = CTransaction()
+ f = BytesIO(hex_str_to_bytes(signresult['hex']))
+ tx.deserialize(f)
+ return tx
+
+ def get_tests(self):
+
+ self.coinbase_blocks = self.nodes[0].generate(2)
+ height = 3 # height of the next block to build
+ self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0)
+ self.nodeaddress = self.nodes[0].getnewaddress()
+ self.last_block_time = int(time.time())
+
+ ''' 98 more version 3 blocks '''
+ test_blocks = []
+ for i in range(98):
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 3
+ block.rehash()
+ block.solve()
+ test_blocks.append([block, True])
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ ''' Mine 749 version 4 blocks '''
+ test_blocks = []
+ for i in range(749):
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 4
+ block.rehash()
+ block.solve()
+ test_blocks.append([block, True])
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ '''
+ Check that the new CLTV rules are not enforced in the 750th
+ version 3 block.
+ '''
+ spendtx = self.create_transaction(self.nodes[0],
+ self.coinbase_blocks[0], self.nodeaddress, 1.0)
+ cltv_invalidate(spendtx)
+ spendtx.rehash()
+
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 4
+ block.vtx.append(spendtx)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ block.solve()
+
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance([[block, True]])
+
+ '''
+ Check that the new CLTV rules are enforced in the 751st version 4
+ block.
+ '''
+ spendtx = self.create_transaction(self.nodes[0],
+ self.coinbase_blocks[1], self.nodeaddress, 1.0)
+ cltv_invalidate(spendtx)
+ spendtx.rehash()
+
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 4
+ block.vtx.append(spendtx)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ yield TestInstance([[block, False]])
+
+ ''' Mine 199 new version blocks on last valid tip '''
+ test_blocks = []
+ for i in range(199):
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 4
+ block.rehash()
+ block.solve()
+ test_blocks.append([block, True])
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ ''' Mine 1 old version block '''
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 3
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance([[block, True]])
+
+ ''' Mine 1 new version block '''
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 4
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance([[block, True]])
+
+ ''' Mine 1 old version block, should be invalid '''
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 3
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ yield TestInstance([[block, False]])
+
+if __name__ == '__main__':
+ BIP65Test().main()
diff --git a/qa/rpc-tests/bip65-cltv.py b/qa/rpc-tests/bip65-cltv.py
new file mode 100755
index 0000000000..abba7fc20e
--- /dev/null
+++ b/qa/rpc-tests/bip65-cltv.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test the CHECKLOCKTIMEVERIFY (BIP65) soft-fork logic
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+class BIP65Test(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 3
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, []))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-blockversion=3"]))
+ self.nodes.append(start_node(2, self.options.tmpdir, ["-blockversion=4"]))
+ connect_nodes(self.nodes[1], 0)
+ connect_nodes(self.nodes[2], 0)
+ self.is_network_split = False
+ self.sync_all()
+
+ def run_test(self):
+ cnt = self.nodes[0].getblockcount()
+
+ # Mine some old-version blocks
+ self.nodes[1].generate(100)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 100):
+ raise AssertionError("Failed to mine 100 version=3 blocks")
+
+ # Mine 750 new-version blocks
+ for i in range(15):
+ self.nodes[2].generate(50)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 850):
+ raise AssertionError("Failed to mine 750 version=4 blocks")
+
+ # TODO: check that new CHECKLOCKTIMEVERIFY rules are not enforced
+
+ # Mine 1 new-version block
+ self.nodes[2].generate(1)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 851):
+ raise AssertionError("Failed to mine a version=4 blocks")
+
+ # TODO: check that new CHECKLOCKTIMEVERIFY rules are enforced
+
+ # Mine 198 new-version blocks
+ for i in range(2):
+ self.nodes[2].generate(99)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1049):
+ raise AssertionError("Failed to mine 198 version=4 blocks")
+
+ # Mine 1 old-version block
+ self.nodes[1].generate(1)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1050):
+ raise AssertionError("Failed to mine a version=3 block after 949 version=4 blocks")
+
+ # Mine 1 new-version blocks
+ self.nodes[2].generate(1)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1051):
+ raise AssertionError("Failed to mine a version=4 block")
+
+ # Mine 1 old-version blocks
+ try:
+ self.nodes[1].generate(1)
+ raise AssertionError("Succeeded to mine a version=3 block after 950 version=4 blocks")
+ except JSONRPCException:
+ pass
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1051):
+ raise AssertionError("Accepted a version=3 block after 950 version=4 blocks")
+
+ # Mine 1 new-version blocks
+ self.nodes[2].generate(1)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1052):
+ raise AssertionError("Failed to mine a version=4 block")
+
+if __name__ == '__main__':
+ BIP65Test().main()
diff --git a/qa/rpc-tests/bip68-112-113-p2p.py b/qa/rpc-tests/bip68-112-113-p2p.py
new file mode 100755
index 0000000000..55b3e2a04a
--- /dev/null
+++ b/qa/rpc-tests/bip68-112-113-p2p.py
@@ -0,0 +1,539 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.util import *
+from test_framework.mininode import ToHex, CTransaction, NetworkThread
+from test_framework.blocktools import create_coinbase, create_block
+from test_framework.comptool import TestInstance, TestManager
+from test_framework.script import *
+from io import BytesIO
+import time
+
+'''
+This test is meant to exercise activation of the first version bits soft fork
+This soft fork will activate the following BIPS:
+BIP 68 - nSequence relative lock times
+BIP 112 - CHECKSEQUENCEVERIFY
+BIP 113 - MedianTimePast semantics for nLockTime
+
+regtest lock-in with 108/144 block signalling
+activation after a further 144 blocks
+
+mine 82 blocks whose coinbases will be used to generate inputs for our tests
+mine 61 blocks to transition from DEFINED to STARTED
+mine 144 blocks only 100 of which are signaling readiness in order to fail to change state this period
+mine 144 blocks with 108 signaling and verify STARTED->LOCKED_IN
+mine 140 blocks and seed block chain with the 82 inputs will use for our tests at height 572
+mine 3 blocks and verify still at LOCKED_IN and test that enforcement has not triggered
+mine 1 block and test that enforcement has triggered (which triggers ACTIVE)
+Test BIP 113 is enforced
+Mine 4 blocks so next height is 580 and test BIP 68 is enforced for time and height
+Mine 1 block so next height is 581 and test BIP 68 now passes time but not height
+Mine 1 block so next height is 582 and test BIP 68 now passes time and height
+Test that BIP 112 is enforced
+
+Various transactions will be used to test that the BIPs rules are not enforced before the soft fork activates
+And that after the soft fork activates transactions pass and fail as they should according to the rules.
+For each BIP, transactions of versions 1 and 2 will be tested.
+----------------
+BIP 113:
+bip113tx - modify the nLocktime variable
+
+BIP 68:
+bip68txs - 16 txs with nSequence relative locktime of 10 with various bits set as per the relative_locktimes below
+
+BIP 112:
+bip112txs_vary_nSequence - 16 txs with nSequence relative_locktimes of 10 evaluated against 10 OP_CSV OP_DROP
+bip112txs_vary_nSequence_9 - 16 txs with nSequence relative_locktimes of 9 evaluated against 10 OP_CSV OP_DROP
+bip112txs_vary_OP_CSV - 16 txs with nSequence = 10 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP
+bip112txs_vary_OP_CSV_9 - 16 txs with nSequence = 9 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP
+bip112tx_special - test negative argument to OP_CSV
+'''
+
+base_relative_locktime = 10
+seq_disable_flag = 1<<31
+seq_random_high_bit = 1<<25
+seq_type_flag = 1<<22
+seq_random_low_bit = 1<<18
+
+# b31,b25,b22,b18 represent the 31st, 25th, 22nd and 18th bits respectively in the nSequence field
+# relative_locktimes[b31][b25][b22][b18] is a base_relative_locktime with the indicated bits set if their indices are 1
+relative_locktimes = []
+for b31 in range(2):
+ b25times = []
+ for b25 in range(2):
+ b22times = []
+ for b22 in range(2):
+ b18times = []
+ for b18 in range(2):
+ rlt = base_relative_locktime
+ if (b31):
+ rlt = rlt | seq_disable_flag
+ if (b25):
+ rlt = rlt | seq_random_high_bit
+ if (b22):
+ rlt = rlt | seq_type_flag
+ if (b18):
+ rlt = rlt | seq_random_low_bit
+ b18times.append(rlt)
+ b22times.append(b18times)
+ b25times.append(b22times)
+ relative_locktimes.append(b25times)
+
+def all_rlt_txs(txarray):
+ txs = []
+ for b31 in range(2):
+ for b25 in range(2):
+ for b22 in range(2):
+ for b18 in range(2):
+ txs.append(txarray[b31][b25][b22][b18])
+ return txs
+
+class BIP68_112_113Test(ComparisonTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+
+ def setup_network(self):
+ # Must set the blockversion for this test
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
+ extra_args=[['-debug', '-whitelist=127.0.0.1', '-blockversion=4']],
+ binary=[self.options.testbinary])
+
+ def run_test(self):
+ test = TestManager(self, self.options.tmpdir)
+ test.add_all_connections(self.nodes)
+ NetworkThread().start() # Start up network handling in another thread
+ test.run()
+
+ def send_generic_input_tx(self, node, coinbases):
+ amount = Decimal("49.99")
+ return node.sendrawtransaction(ToHex(self.sign_transaction(node, self.create_transaction(node, node.getblock(coinbases.pop())['tx'][0], self.nodeaddress, amount))))
+
+ def create_transaction(self, node, txid, to_address, amount):
+ inputs = [{ "txid" : txid, "vout" : 0}]
+ outputs = { to_address : amount }
+ rawtx = node.createrawtransaction(inputs, outputs)
+ tx = CTransaction()
+ f = BytesIO(hex_str_to_bytes(rawtx))
+ tx.deserialize(f)
+ return tx
+
+ def sign_transaction(self, node, unsignedtx):
+ rawtx = ToHex(unsignedtx)
+ signresult = node.signrawtransaction(rawtx)
+ tx = CTransaction()
+ f = BytesIO(hex_str_to_bytes(signresult['hex']))
+ tx.deserialize(f)
+ return tx
+
+ def generate_blocks(self, number, version, test_blocks = []):
+ for i in range(number):
+ block = self.create_test_block([], version)
+ test_blocks.append([block, True])
+ self.last_block_time += 600
+ self.tip = block.sha256
+ self.tipheight += 1
+ return test_blocks
+
+ def create_test_block(self, txs, version = 536870912):
+ block = create_block(self.tip, create_coinbase(self.tipheight + 1), self.last_block_time + 600)
+ block.nVersion = version
+ block.vtx.extend(txs)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ block.solve()
+ return block
+
+ def create_bip68txs(self, bip68inputs, txversion, locktime_delta = 0):
+ txs = []
+ assert(len(bip68inputs) >= 16)
+ i = 0
+ for b31 in range(2):
+ b25txs = []
+ for b25 in range(2):
+ b22txs = []
+ for b22 in range(2):
+ b18txs = []
+ for b18 in range(2):
+ tx = self.create_transaction(self.nodes[0], bip68inputs[i], self.nodeaddress, Decimal("49.98"))
+ i += 1
+ tx.nVersion = txversion
+ tx.vin[0].nSequence = relative_locktimes[b31][b25][b22][b18] + locktime_delta
+ b18txs.append(self.sign_transaction(self.nodes[0], tx))
+ b22txs.append(b18txs)
+ b25txs.append(b22txs)
+ txs.append(b25txs)
+ return txs
+
+ def create_bip112special(self, input, txversion):
+ tx = self.create_transaction(self.nodes[0], input, self.nodeaddress, Decimal("49.98"))
+ tx.nVersion = txversion
+ signtx = self.sign_transaction(self.nodes[0], tx)
+ signtx.vin[0].scriptSig = CScript([-1, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(signtx.vin[0].scriptSig)))
+ return signtx
+
+ def create_bip112txs(self, bip112inputs, varyOP_CSV, txversion, locktime_delta = 0):
+ txs = []
+ assert(len(bip112inputs) >= 16)
+ i = 0
+ for b31 in range(2):
+ b25txs = []
+ for b25 in range(2):
+ b22txs = []
+ for b22 in range(2):
+ b18txs = []
+ for b18 in range(2):
+ tx = self.create_transaction(self.nodes[0], bip112inputs[i], self.nodeaddress, Decimal("49.98"))
+ i += 1
+ if (varyOP_CSV): # if varying OP_CSV, nSequence is fixed
+ tx.vin[0].nSequence = base_relative_locktime + locktime_delta
+ else: # vary nSequence instead, OP_CSV is fixed
+ tx.vin[0].nSequence = relative_locktimes[b31][b25][b22][b18] + locktime_delta
+ tx.nVersion = txversion
+ signtx = self.sign_transaction(self.nodes[0], tx)
+ if (varyOP_CSV):
+ signtx.vin[0].scriptSig = CScript([relative_locktimes[b31][b25][b22][b18], OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(signtx.vin[0].scriptSig)))
+ else:
+ signtx.vin[0].scriptSig = CScript([base_relative_locktime, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(signtx.vin[0].scriptSig)))
+ b18txs.append(signtx)
+ b22txs.append(b18txs)
+ b25txs.append(b22txs)
+ txs.append(b25txs)
+ return txs
+
+ def get_tests(self):
+ long_past_time = int(time.time()) - 600 * 1000 # enough to build up to 1000 blocks 10 minutes apart without worrying about getting into the future
+ self.nodes[0].setmocktime(long_past_time - 100) # enough so that the generated blocks will still all be before long_past_time
+ self.coinbase_blocks = self.nodes[0].generate(1 + 16 + 2*32 + 1) # 82 blocks generated for inputs
+ self.nodes[0].setmocktime(0) # set time back to present so yielded blocks aren't in the future as we advance last_block_time
+ self.tipheight = 82 # height of the next block to build
+ self.last_block_time = long_past_time
+ self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0)
+ self.nodeaddress = self.nodes[0].getnewaddress()
+
+ assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'defined')
+ test_blocks = self.generate_blocks(61, 4)
+ yield TestInstance(test_blocks, sync_every_block=False) # 1
+ # Advanced from DEFINED to STARTED, height = 143
+ assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'started')
+
+ # Fail to achieve LOCKED_IN 100 out of 144 signal bit 0
+ # using a variety of bits to simulate multiple parallel softforks
+ test_blocks = self.generate_blocks(50, 536870913) # 0x20000001 (signalling ready)
+ test_blocks = self.generate_blocks(20, 4, test_blocks) # 0x00000004 (signalling not)
+ test_blocks = self.generate_blocks(50, 536871169, test_blocks) # 0x20000101 (signalling ready)
+ test_blocks = self.generate_blocks(24, 536936448, test_blocks) # 0x20010000 (signalling not)
+ yield TestInstance(test_blocks, sync_every_block=False) # 2
+ # Failed to advance past STARTED, height = 287
+ assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'started')
+
+ # 108 out of 144 signal bit 0 to achieve lock-in
+ # using a variety of bits to simulate multiple parallel softforks
+ test_blocks = self.generate_blocks(58, 536870913) # 0x20000001 (signalling ready)
+ test_blocks = self.generate_blocks(26, 4, test_blocks) # 0x00000004 (signalling not)
+ test_blocks = self.generate_blocks(50, 536871169, test_blocks) # 0x20000101 (signalling ready)
+ test_blocks = self.generate_blocks(10, 536936448, test_blocks) # 0x20010000 (signalling not)
+ yield TestInstance(test_blocks, sync_every_block=False) # 3
+ # Advanced from STARTED to LOCKED_IN, height = 431
+ assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'locked_in')
+
+ # 140 more version 4 blocks
+ test_blocks = self.generate_blocks(140, 4)
+ yield TestInstance(test_blocks, sync_every_block=False) # 4
+
+ ### Inputs at height = 572
+ # Put inputs for all tests in the chain at height 572 (tip now = 571) (time increases by 600s per block)
+ # Note we reuse inputs for v1 and v2 txs so must test these separately
+ # 16 normal inputs
+ bip68inputs = []
+ for i in range(16):
+ bip68inputs.append(self.send_generic_input_tx(self.nodes[0], self.coinbase_blocks))
+ # 2 sets of 16 inputs with 10 OP_CSV OP_DROP (actually will be prepended to spending scriptSig)
+ bip112basicinputs = []
+ for j in range(2):
+ inputs = []
+ for i in range(16):
+ inputs.append(self.send_generic_input_tx(self.nodes[0], self.coinbase_blocks))
+ bip112basicinputs.append(inputs)
+ # 2 sets of 16 varied inputs with (relative_lock_time) OP_CSV OP_DROP (actually will be prepended to spending scriptSig)
+ bip112diverseinputs = []
+ for j in range(2):
+ inputs = []
+ for i in range(16):
+ inputs.append(self.send_generic_input_tx(self.nodes[0], self.coinbase_blocks))
+ bip112diverseinputs.append(inputs)
+ # 1 special input with -1 OP_CSV OP_DROP (actually will be prepended to spending scriptSig)
+ bip112specialinput = self.send_generic_input_tx(self.nodes[0], self.coinbase_blocks)
+ # 1 normal input
+ bip113input = self.send_generic_input_tx(self.nodes[0], self.coinbase_blocks)
+
+ self.nodes[0].setmocktime(self.last_block_time + 600)
+ inputblockhash = self.nodes[0].generate(1)[0] # 1 block generated for inputs to be in chain at height 572
+ self.nodes[0].setmocktime(0)
+ self.tip = int("0x" + inputblockhash, 0)
+ self.tipheight += 1
+ self.last_block_time += 600
+ assert_equal(len(self.nodes[0].getblock(inputblockhash,True)["tx"]), 82+1)
+
+ # 2 more version 4 blocks
+ test_blocks = self.generate_blocks(2, 4)
+ yield TestInstance(test_blocks, sync_every_block=False) # 5
+ # Not yet advanced to ACTIVE, height = 574 (will activate for block 576, not 575)
+ assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'locked_in')
+
+ # Test both version 1 and version 2 transactions for all tests
+ # BIP113 test transaction will be modified before each use to put in appropriate block time
+ bip113tx_v1 = self.create_transaction(self.nodes[0], bip113input, self.nodeaddress, Decimal("49.98"))
+ bip113tx_v1.vin[0].nSequence = 0xFFFFFFFE
+ bip113tx_v2 = self.create_transaction(self.nodes[0], bip113input, self.nodeaddress, Decimal("49.98"))
+ bip113tx_v2.vin[0].nSequence = 0xFFFFFFFE
+ bip113tx_v2.nVersion = 2
+
+ # For BIP68 test all 16 relative sequence locktimes
+ bip68txs_v1 = self.create_bip68txs(bip68inputs, 1)
+ bip68txs_v2 = self.create_bip68txs(bip68inputs, 2)
+
+ # For BIP112 test:
+ # 16 relative sequence locktimes of 10 against 10 OP_CSV OP_DROP inputs
+ bip112txs_vary_nSequence_v1 = self.create_bip112txs(bip112basicinputs[0], False, 1)
+ bip112txs_vary_nSequence_v2 = self.create_bip112txs(bip112basicinputs[0], False, 2)
+ # 16 relative sequence locktimes of 9 against 10 OP_CSV OP_DROP inputs
+ bip112txs_vary_nSequence_9_v1 = self.create_bip112txs(bip112basicinputs[1], False, 1, -1)
+ bip112txs_vary_nSequence_9_v2 = self.create_bip112txs(bip112basicinputs[1], False, 2, -1)
+ # sequence lock time of 10 against 16 (relative_lock_time) OP_CSV OP_DROP inputs
+ bip112txs_vary_OP_CSV_v1 = self.create_bip112txs(bip112diverseinputs[0], True, 1)
+ bip112txs_vary_OP_CSV_v2 = self.create_bip112txs(bip112diverseinputs[0], True, 2)
+ # sequence lock time of 9 against 16 (relative_lock_time) OP_CSV OP_DROP inputs
+ bip112txs_vary_OP_CSV_9_v1 = self.create_bip112txs(bip112diverseinputs[1], True, 1, -1)
+ bip112txs_vary_OP_CSV_9_v2 = self.create_bip112txs(bip112diverseinputs[1], True, 2, -1)
+ # -1 OP_CSV OP_DROP input
+ bip112tx_special_v1 = self.create_bip112special(bip112specialinput, 1)
+ bip112tx_special_v2 = self.create_bip112special(bip112specialinput, 2)
+
+
+ ### TESTING ###
+ ##################################
+ ### Before Soft Forks Activate ###
+ ##################################
+ # All txs should pass
+ ### Version 1 txs ###
+ success_txs = []
+ # add BIP113 tx and -1 CSV tx
+ bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
+ bip113signed1 = self.sign_transaction(self.nodes[0], bip113tx_v1)
+ success_txs.append(bip113signed1)
+ success_txs.append(bip112tx_special_v1)
+ # add BIP 68 txs
+ success_txs.extend(all_rlt_txs(bip68txs_v1))
+ # add BIP 112 with seq=10 txs
+ success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_v1))
+ success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_v1))
+ # try BIP 112 with seq=9 txs
+ success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_9_v1))
+ success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_9_v1))
+ yield TestInstance([[self.create_test_block(success_txs), True]]) # 6
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+ ### Version 2 txs ###
+ success_txs = []
+ # add BIP113 tx and -1 CSV tx
+ bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
+ bip113signed2 = self.sign_transaction(self.nodes[0], bip113tx_v2)
+ success_txs.append(bip113signed2)
+ success_txs.append(bip112tx_special_v2)
+ # add BIP 68 txs
+ success_txs.extend(all_rlt_txs(bip68txs_v2))
+ # add BIP 112 with seq=10 txs
+ success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_v2))
+ success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_v2))
+ # try BIP 112 with seq=9 txs
+ success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_9_v2))
+ success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_9_v2))
+ yield TestInstance([[self.create_test_block(success_txs), True]]) # 7
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+
+ # 1 more version 4 block to get us to height 575 so the fork should now be active for the next block
+ test_blocks = self.generate_blocks(1, 4)
+ yield TestInstance(test_blocks, sync_every_block=False) # 8
+ assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'active')
+
+
+ #################################
+ ### After Soft Forks Activate ###
+ #################################
+ ### BIP 113 ###
+ # BIP 113 tests should now fail regardless of version number if nLockTime isn't satisfied by new rules
+ bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
+ bip113signed1 = self.sign_transaction(self.nodes[0], bip113tx_v1)
+ bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
+ bip113signed2 = self.sign_transaction(self.nodes[0], bip113tx_v2)
+ for bip113tx in [bip113signed1, bip113signed2]:
+ yield TestInstance([[self.create_test_block([bip113tx]), False]]) # 9,10
+ # BIP 113 tests should now pass if the locktime is < MTP
+ bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 - 1 # < MTP of prior block
+ bip113signed1 = self.sign_transaction(self.nodes[0], bip113tx_v1)
+ bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 - 1 # < MTP of prior block
+ bip113signed2 = self.sign_transaction(self.nodes[0], bip113tx_v2)
+ for bip113tx in [bip113signed1, bip113signed2]:
+ yield TestInstance([[self.create_test_block([bip113tx]), True]]) # 11,12
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+ # Next block height = 580 after 4 blocks of random version
+ test_blocks = self.generate_blocks(4, 1234)
+ yield TestInstance(test_blocks, sync_every_block=False) # 13
+
+ ### BIP 68 ###
+ ### Version 1 txs ###
+ # All still pass
+ success_txs = []
+ success_txs.extend(all_rlt_txs(bip68txs_v1))
+ yield TestInstance([[self.create_test_block(success_txs), True]]) # 14
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+ ### Version 2 txs ###
+ bip68success_txs = []
+ # All txs with SEQUENCE_LOCKTIME_DISABLE_FLAG set pass
+ for b25 in range(2):
+ for b22 in range(2):
+ for b18 in range(2):
+ bip68success_txs.append(bip68txs_v2[1][b25][b22][b18])
+ yield TestInstance([[self.create_test_block(bip68success_txs), True]]) # 15
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+ # All txs without flag fail as we are at delta height = 8 < 10 and delta time = 8 * 600 < 10 * 512
+ bip68timetxs = []
+ for b25 in range(2):
+ for b18 in range(2):
+ bip68timetxs.append(bip68txs_v2[0][b25][1][b18])
+ for tx in bip68timetxs:
+ yield TestInstance([[self.create_test_block([tx]), False]]) # 16 - 19
+ bip68heighttxs = []
+ for b25 in range(2):
+ for b18 in range(2):
+ bip68heighttxs.append(bip68txs_v2[0][b25][0][b18])
+ for tx in bip68heighttxs:
+ yield TestInstance([[self.create_test_block([tx]), False]]) # 20 - 23
+
+ # Advance one block to 581
+ test_blocks = self.generate_blocks(1, 1234)
+ yield TestInstance(test_blocks, sync_every_block=False) # 24
+
+ # Height txs should fail and time txs should now pass 9 * 600 > 10 * 512
+ bip68success_txs.extend(bip68timetxs)
+ yield TestInstance([[self.create_test_block(bip68success_txs), True]]) # 25
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+ for tx in bip68heighttxs:
+ yield TestInstance([[self.create_test_block([tx]), False]]) # 26 - 29
+
+ # Advance one block to 582
+ test_blocks = self.generate_blocks(1, 1234)
+ yield TestInstance(test_blocks, sync_every_block=False) # 30
+
+ # All BIP 68 txs should pass
+ bip68success_txs.extend(bip68heighttxs)
+ yield TestInstance([[self.create_test_block(bip68success_txs), True]]) # 31
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+
+ ### BIP 112 ###
+ ### Version 1 txs ###
+ # -1 OP_CSV tx should fail
+ yield TestInstance([[self.create_test_block([bip112tx_special_v1]), False]]) #32
+ # If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in argument to OP_CSV, version 1 txs should still pass
+ success_txs = []
+ for b25 in range(2):
+ for b22 in range(2):
+ for b18 in range(2):
+ success_txs.append(bip112txs_vary_OP_CSV_v1[1][b25][b22][b18])
+ success_txs.append(bip112txs_vary_OP_CSV_9_v1[1][b25][b22][b18])
+ yield TestInstance([[self.create_test_block(success_txs), True]]) # 33
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+ # If SEQUENCE_LOCKTIME_DISABLE_FLAG is unset in argument to OP_CSV, version 1 txs should now fail
+ fail_txs = []
+ fail_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_v1))
+ fail_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_9_v1))
+ for b25 in range(2):
+ for b22 in range(2):
+ for b18 in range(2):
+ fail_txs.append(bip112txs_vary_OP_CSV_v1[0][b25][b22][b18])
+ fail_txs.append(bip112txs_vary_OP_CSV_9_v1[0][b25][b22][b18])
+
+ for tx in fail_txs:
+ yield TestInstance([[self.create_test_block([tx]), False]]) # 34 - 81
+
+ ### Version 2 txs ###
+ # -1 OP_CSV tx should fail
+ yield TestInstance([[self.create_test_block([bip112tx_special_v2]), False]]) #82
+
+ # If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in argument to OP_CSV, version 2 txs should pass (all sequence locks are met)
+ success_txs = []
+ for b25 in range(2):
+ for b22 in range(2):
+ for b18 in range(2):
+ success_txs.append(bip112txs_vary_OP_CSV_v2[1][b25][b22][b18]) # 8/16 of vary_OP_CSV
+ success_txs.append(bip112txs_vary_OP_CSV_9_v2[1][b25][b22][b18]) # 8/16 of vary_OP_CSV_9
+
+ yield TestInstance([[self.create_test_block(success_txs), True]]) # 83
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+ ## SEQUENCE_LOCKTIME_DISABLE_FLAG is unset in argument to OP_CSV for all remaining txs ##
+ # All txs with nSequence 9 should fail either due to earlier mismatch or failing the CSV check
+ fail_txs = []
+ fail_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_9_v2)) # 16/16 of vary_nSequence_9
+ for b25 in range(2):
+ for b22 in range(2):
+ for b18 in range(2):
+ fail_txs.append(bip112txs_vary_OP_CSV_9_v2[0][b25][b22][b18]) # 16/16 of vary_OP_CSV_9
+
+ for tx in fail_txs:
+ yield TestInstance([[self.create_test_block([tx]), False]]) # 84 - 107
+
+ # If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in nSequence, tx should fail
+ fail_txs = []
+ for b25 in range(2):
+ for b22 in range(2):
+ for b18 in range(2):
+ fail_txs.append(bip112txs_vary_nSequence_v2[1][b25][b22][b18]) # 8/16 of vary_nSequence
+ for tx in fail_txs:
+ yield TestInstance([[self.create_test_block([tx]), False]]) # 108-115
+
+ # If sequencelock types mismatch, tx should fail
+ fail_txs = []
+ for b25 in range(2):
+ for b18 in range(2):
+ fail_txs.append(bip112txs_vary_nSequence_v2[0][b25][1][b18]) # 12/16 of vary_nSequence
+ fail_txs.append(bip112txs_vary_OP_CSV_v2[0][b25][1][b18]) # 12/16 of vary_OP_CSV
+ for tx in fail_txs:
+ yield TestInstance([[self.create_test_block([tx]), False]]) # 116-123
+
+ # Remaining txs should pass, just test masking works properly
+ success_txs = []
+ for b25 in range(2):
+ for b18 in range(2):
+ success_txs.append(bip112txs_vary_nSequence_v2[0][b25][0][b18]) # 16/16 of vary_nSequence
+ success_txs.append(bip112txs_vary_OP_CSV_v2[0][b25][0][b18]) # 16/16 of vary_OP_CSV
+ yield TestInstance([[self.create_test_block(success_txs), True]]) # 124
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+ # Additional test, of checking that comparison of two time types works properly
+ time_txs = []
+ for b25 in range(2):
+ for b18 in range(2):
+ tx = bip112txs_vary_OP_CSV_v2[0][b25][1][b18]
+ tx.vin[0].nSequence = base_relative_locktime | seq_type_flag
+ signtx = self.sign_transaction(self.nodes[0], tx)
+ time_txs.append(signtx)
+ yield TestInstance([[self.create_test_block(time_txs), True]]) # 125
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+ ### Missing aspects of test
+ ## Testing empty stack fails
+
+
+if __name__ == '__main__':
+ BIP68_112_113Test().main()
diff --git a/qa/rpc-tests/bip68-sequence.py b/qa/rpc-tests/bip68-sequence.py
new file mode 100755
index 0000000000..a12bf10ebd
--- /dev/null
+++ b/qa/rpc-tests/bip68-sequence.py
@@ -0,0 +1,428 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test BIP68 implementation
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.script import *
+from test_framework.mininode import *
+from test_framework.blocktools import *
+
+SEQUENCE_LOCKTIME_DISABLE_FLAG = (1<<31)
+SEQUENCE_LOCKTIME_TYPE_FLAG = (1<<22) # this means use time (0 means height)
+SEQUENCE_LOCKTIME_GRANULARITY = 9 # this is a bit-shift
+SEQUENCE_LOCKTIME_MASK = 0x0000ffff
+
+# RPC error for non-BIP68 final transactions
+NOT_FINAL_ERROR = "64: non-BIP68-final"
+
+class BIP68Test(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-blockprioritysize=0"]))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-blockprioritysize=0", "-acceptnonstdtxn=0"]))
+ self.is_network_split = False
+ self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"]
+ connect_nodes(self.nodes[0], 1)
+
+ def run_test(self):
+ # Generate some coins
+ self.nodes[0].generate(110)
+
+ print("Running test disable flag")
+ self.test_disable_flag()
+
+ print("Running test sequence-lock-confirmed-inputs")
+ self.test_sequence_lock_confirmed_inputs()
+
+ print("Running test sequence-lock-unconfirmed-inputs")
+ self.test_sequence_lock_unconfirmed_inputs()
+
+ print("Running test BIP68 not consensus before versionbits activation")
+ self.test_bip68_not_consensus()
+
+ print("Verifying nVersion=2 transactions aren't standard")
+ self.test_version2_relay(before_activation=True)
+
+ print("Activating BIP68 (and 112/113)")
+ self.activateCSV()
+
+ print("Verifying nVersion=2 transactions are now standard")
+ self.test_version2_relay(before_activation=False)
+
+ print("Passed\n")
+
+ # Test that BIP68 is not in effect if tx version is 1, or if
+ # the first sequence bit is set.
+ def test_disable_flag(self):
+ # Create some unconfirmed inputs
+ new_addr = self.nodes[0].getnewaddress()
+ self.nodes[0].sendtoaddress(new_addr, 2) # send 2 BTC
+
+ utxos = self.nodes[0].listunspent(0, 0)
+ assert(len(utxos) > 0)
+
+ utxo = utxos[0]
+
+ tx1 = CTransaction()
+ value = int(satoshi_round(utxo["amount"] - self.relayfee)*COIN)
+
+ # Check that the disable flag disables relative locktime.
+ # If sequence locks were used, this would require 1 block for the
+ # input to mature.
+ sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1
+ tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)]
+ tx1.vout = [CTxOut(value, CScript([b'a']))]
+
+ tx1_signed = self.nodes[0].signrawtransaction(ToHex(tx1))["hex"]
+ tx1_id = self.nodes[0].sendrawtransaction(tx1_signed)
+ tx1_id = int(tx1_id, 16)
+
+ # This transaction will enable sequence-locks, so this transaction should
+ # fail
+ tx2 = CTransaction()
+ tx2.nVersion = 2
+ sequence_value = sequence_value & 0x7fffffff
+ tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)]
+ tx2.vout = [CTxOut(int(value-self.relayfee*COIN), CScript([b'a']))]
+ tx2.rehash()
+
+ try:
+ self.nodes[0].sendrawtransaction(ToHex(tx2))
+ except JSONRPCException as exp:
+ assert_equal(exp.error["message"], NOT_FINAL_ERROR)
+ else:
+ assert(False)
+
+ # Setting the version back down to 1 should disable the sequence lock,
+ # so this should be accepted.
+ tx2.nVersion = 1
+
+ self.nodes[0].sendrawtransaction(ToHex(tx2))
+
+ # Calculate the median time past of a prior block ("confirmations" before
+ # the current tip).
+ def get_median_time_past(self, confirmations):
+ block_hash = self.nodes[0].getblockhash(self.nodes[0].getblockcount()-confirmations)
+ return self.nodes[0].getblockheader(block_hash)["mediantime"]
+
+ # Test that sequence locks are respected for transactions spending confirmed inputs.
+ def test_sequence_lock_confirmed_inputs(self):
+ # Create lots of confirmed utxos, and use them to generate lots of random
+ # transactions.
+ max_outputs = 50
+ addresses = []
+ while len(addresses) < max_outputs:
+ addresses.append(self.nodes[0].getnewaddress())
+ while len(self.nodes[0].listunspent()) < 200:
+ import random
+ random.shuffle(addresses)
+ num_outputs = random.randint(1, max_outputs)
+ outputs = {}
+ for i in range(num_outputs):
+ outputs[addresses[i]] = random.randint(1, 20)*0.01
+ self.nodes[0].sendmany("", outputs)
+ self.nodes[0].generate(1)
+
+ utxos = self.nodes[0].listunspent()
+
+ # Try creating a lot of random transactions.
+ # Each time, choose a random number of inputs, and randomly set
+ # some of those inputs to be sequence locked (and randomly choose
+ # between height/time locking). Small random chance of making the locks
+ # all pass.
+ for i in range(400):
+ # Randomly choose up to 10 inputs
+ num_inputs = random.randint(1, 10)
+ random.shuffle(utxos)
+
+ # Track whether any sequence locks used should fail
+ should_pass = True
+
+ # Track whether this transaction was built with sequence locks
+ using_sequence_locks = False
+
+ tx = CTransaction()
+ tx.nVersion = 2
+ value = 0
+ for j in range(num_inputs):
+ sequence_value = 0xfffffffe # this disables sequence locks
+
+ # 50% chance we enable sequence locks
+ if random.randint(0,1):
+ using_sequence_locks = True
+
+ # 10% of the time, make the input sequence value pass
+ input_will_pass = (random.randint(1,10) == 1)
+ sequence_value = utxos[j]["confirmations"]
+ if not input_will_pass:
+ sequence_value += 1
+ should_pass = False
+
+ # Figure out what the median-time-past was for the confirmed input
+ # Note that if an input has N confirmations, we're going back N blocks
+ # from the tip so that we're looking up MTP of the block
+ # PRIOR to the one the input appears in, as per the BIP68 spec.
+ orig_time = self.get_median_time_past(utxos[j]["confirmations"])
+ cur_time = self.get_median_time_past(0) # MTP of the tip
+
+ # can only timelock this input if it's not too old -- otherwise use height
+ can_time_lock = True
+ if ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) >= SEQUENCE_LOCKTIME_MASK:
+ can_time_lock = False
+
+ # if time-lockable, then 50% chance we make this a time lock
+ if random.randint(0,1) and can_time_lock:
+ # Find first time-lock value that fails, or latest one that succeeds
+ time_delta = sequence_value << SEQUENCE_LOCKTIME_GRANULARITY
+ if input_will_pass and time_delta > cur_time - orig_time:
+ sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY)
+ elif (not input_will_pass and time_delta <= cur_time - orig_time):
+ sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY)+1
+ sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG
+ tx.vin.append(CTxIn(COutPoint(int(utxos[j]["txid"], 16), utxos[j]["vout"]), nSequence=sequence_value))
+ value += utxos[j]["amount"]*COIN
+ # Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output
+ tx_size = len(ToHex(tx))//2 + 120*num_inputs + 50
+ tx.vout.append(CTxOut(int(value-self.relayfee*tx_size*COIN/1000), CScript([b'a'])))
+ rawtx = self.nodes[0].signrawtransaction(ToHex(tx))["hex"]
+
+ try:
+ self.nodes[0].sendrawtransaction(rawtx)
+ except JSONRPCException as exp:
+ assert(not should_pass and using_sequence_locks)
+ assert_equal(exp.error["message"], NOT_FINAL_ERROR)
+ else:
+ assert(should_pass or not using_sequence_locks)
+ # Recalculate utxos if we successfully sent the transaction
+ utxos = self.nodes[0].listunspent()
+
+ # Test that sequence locks on unconfirmed inputs must have nSequence
+ # height or time of 0 to be accepted.
+ # Then test that BIP68-invalid transactions are removed from the mempool
+ # after a reorg.
+ def test_sequence_lock_unconfirmed_inputs(self):
+ # Store height so we can easily reset the chain at the end of the test
+ cur_height = self.nodes[0].getblockcount()
+
+ # Create a mempool tx.
+ txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2)
+ tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid))
+ tx1.rehash()
+
+ # Anyone-can-spend mempool tx.
+ # Sequence lock of 0 should pass.
+ tx2 = CTransaction()
+ tx2.nVersion = 2
+ tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
+ tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), CScript([b'a']))]
+ tx2_raw = self.nodes[0].signrawtransaction(ToHex(tx2))["hex"]
+ tx2 = FromHex(tx2, tx2_raw)
+ tx2.rehash()
+
+ self.nodes[0].sendrawtransaction(tx2_raw)
+
+ # Create a spend of the 0th output of orig_tx with a sequence lock
+ # of 1, and test what happens when submitting.
+ # orig_tx.vout[0] must be an anyone-can-spend output
+ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):
+ sequence_value = 1
+ if not use_height_lock:
+ sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG
+
+ tx = CTransaction()
+ tx.nVersion = 2
+ tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)]
+ tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee*COIN), CScript([b'a']))]
+ tx.rehash()
+
+ try:
+ node.sendrawtransaction(ToHex(tx))
+ except JSONRPCException as exp:
+ assert_equal(exp.error["message"], NOT_FINAL_ERROR)
+ assert(orig_tx.hash in node.getrawmempool())
+ else:
+ # orig_tx must not be in mempool
+ assert(orig_tx.hash not in node.getrawmempool())
+ return tx
+
+ test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True)
+ test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False)
+
+ # Now mine some blocks, but make sure tx2 doesn't get mined.
+ # Use prioritisetransaction to lower the effective feerate to 0
+ self.nodes[0].prioritisetransaction(tx2.hash, -1e15, int(-self.relayfee*COIN))
+ cur_time = int(time.time())
+ for i in range(10):
+ self.nodes[0].setmocktime(cur_time + 600)
+ self.nodes[0].generate(1)
+ cur_time += 600
+
+ assert(tx2.hash in self.nodes[0].getrawmempool())
+
+ test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True)
+ test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False)
+
+ # Mine tx2, and then try again
+ self.nodes[0].prioritisetransaction(tx2.hash, 1e15, int(self.relayfee*COIN))
+
+ # Advance the time on the node so that we can test timelocks
+ self.nodes[0].setmocktime(cur_time+600)
+ self.nodes[0].generate(1)
+ assert(tx2.hash not in self.nodes[0].getrawmempool())
+
+ # Now that tx2 is not in the mempool, a sequence locked spend should
+ # succeed
+ tx3 = test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False)
+ assert(tx3.hash in self.nodes[0].getrawmempool())
+
+ self.nodes[0].generate(1)
+ assert(tx3.hash not in self.nodes[0].getrawmempool())
+
+ # One more test, this time using height locks
+ tx4 = test_nonzero_locks(tx3, self.nodes[0], self.relayfee, use_height_lock=True)
+ assert(tx4.hash in self.nodes[0].getrawmempool())
+
+ # Now try combining confirmed and unconfirmed inputs
+ tx5 = test_nonzero_locks(tx4, self.nodes[0], self.relayfee, use_height_lock=True)
+ assert(tx5.hash not in self.nodes[0].getrawmempool())
+
+ utxos = self.nodes[0].listunspent()
+ tx5.vin.append(CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1))
+ tx5.vout[0].nValue += int(utxos[0]["amount"]*COIN)
+ raw_tx5 = self.nodes[0].signrawtransaction(ToHex(tx5))["hex"]
+
+ try:
+ self.nodes[0].sendrawtransaction(raw_tx5)
+ except JSONRPCException as exp:
+ assert_equal(exp.error["message"], NOT_FINAL_ERROR)
+ else:
+ assert(False)
+
+ # Test mempool-BIP68 consistency after reorg
+ #
+ # State of the transactions in the last blocks:
+ # ... -> [ tx2 ] -> [ tx3 ]
+ # tip-1 tip
+ # And currently tx4 is in the mempool.
+ #
+ # If we invalidate the tip, tx3 should get added to the mempool, causing
+ # tx4 to be removed (fails sequence-lock).
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+ assert(tx4.hash not in self.nodes[0].getrawmempool())
+ assert(tx3.hash in self.nodes[0].getrawmempool())
+
+ # Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in
+ # diagram above).
+ # This would cause tx2 to be added back to the mempool, which in turn causes
+ # tx3 to be removed.
+ tip = int(self.nodes[0].getblockhash(self.nodes[0].getblockcount()-1), 16)
+ height = self.nodes[0].getblockcount()
+ for i in range(2):
+ block = create_block(tip, create_coinbase(height), cur_time)
+ block.nVersion = 3
+ block.rehash()
+ block.solve()
+ tip = block.sha256
+ height += 1
+ self.nodes[0].submitblock(ToHex(block))
+ cur_time += 1
+
+ mempool = self.nodes[0].getrawmempool()
+ assert(tx3.hash not in mempool)
+ assert(tx2.hash in mempool)
+
+ # Reset the chain and get rid of the mocktimed-blocks
+ self.nodes[0].setmocktime(0)
+ self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height+1))
+ self.nodes[0].generate(10)
+
+ # Make sure that BIP68 isn't being used to validate blocks, prior to
+ # versionbits activation. If more blocks are mined prior to this test
+ # being run, then it's possible the test has activated the soft fork, and
+ # this test should be moved to run earlier, or deleted.
+ def test_bip68_not_consensus(self):
+ assert(get_bip9_status(self.nodes[0], 'csv')['status'] != 'active')
+ txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2)
+
+ tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid))
+ tx1.rehash()
+
+ # Make an anyone-can-spend transaction
+ tx2 = CTransaction()
+ tx2.nVersion = 1
+ tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
+ tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), CScript([b'a']))]
+
+ # sign tx2
+ tx2_raw = self.nodes[0].signrawtransaction(ToHex(tx2))["hex"]
+ tx2 = FromHex(tx2, tx2_raw)
+ tx2.rehash()
+
+ self.nodes[0].sendrawtransaction(ToHex(tx2))
+
+ # Now make an invalid spend of tx2 according to BIP68
+ sequence_value = 100 # 100 block relative locktime
+
+ tx3 = CTransaction()
+ tx3.nVersion = 2
+ tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)]
+ tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee*COIN), CScript([b'a']))]
+ tx3.rehash()
+
+ try:
+ self.nodes[0].sendrawtransaction(ToHex(tx3))
+ except JSONRPCException as exp:
+ assert_equal(exp.error["message"], NOT_FINAL_ERROR)
+ else:
+ assert(False)
+
+ # make a block that violates bip68; ensure that the tip updates
+ tip = int(self.nodes[0].getbestblockhash(), 16)
+ block = create_block(tip, create_coinbase(self.nodes[0].getblockcount()+1))
+ block.nVersion = 3
+ block.vtx.extend([tx1, tx2, tx3])
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ block.solve()
+
+ self.nodes[0].submitblock(ToHex(block))
+ assert_equal(self.nodes[0].getbestblockhash(), block.hash)
+
+ def activateCSV(self):
+ # activation should happen at block height 432 (3 periods)
+ min_activation_height = 432
+ height = self.nodes[0].getblockcount()
+ assert(height < 432)
+ self.nodes[0].generate(432-height)
+ assert(get_bip9_status(self.nodes[0], 'csv')['status'] == 'active')
+ sync_blocks(self.nodes)
+
+ # Use self.nodes[1] to test standardness relay policy
+ def test_version2_relay(self, before_activation):
+ inputs = [ ]
+ outputs = { self.nodes[1].getnewaddress() : 1.0 }
+ rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
+ rawtxfund = self.nodes[1].fundrawtransaction(rawtx)['hex']
+ tx = FromHex(CTransaction(), rawtxfund)
+ tx.nVersion = 2
+ tx_signed = self.nodes[1].signrawtransaction(ToHex(tx))["hex"]
+ try:
+ tx_id = self.nodes[1].sendrawtransaction(tx_signed)
+ assert(before_activation == False)
+ except:
+ assert(before_activation)
+
+
+if __name__ == '__main__':
+ BIP68Test().main()
diff --git a/qa/rpc-tests/bip9-softforks.py b/qa/rpc-tests/bip9-softforks.py
new file mode 100755
index 0000000000..979d1410c2
--- /dev/null
+++ b/qa/rpc-tests/bip9-softforks.py
@@ -0,0 +1,240 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.blockstore import BlockStore
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.util import *
+from test_framework.mininode import CTransaction, NetworkThread
+from test_framework.blocktools import create_coinbase, create_block
+from test_framework.comptool import TestInstance, TestManager
+from test_framework.script import CScript, OP_1NEGATE, OP_CHECKSEQUENCEVERIFY, OP_DROP
+from io import BytesIO
+import time
+import itertools
+
+'''
+This test is meant to exercise BIP forks
+Connect to a single node.
+regtest lock-in with 108/144 block signalling
+activation after a further 144 blocks
+mine 2 block and save coinbases for later use
+mine 141 blocks to transition from DEFINED to STARTED
+mine 100 blocks signalling readiness and 44 not in order to fail to change state this period
+mine 108 blocks signalling readiness and 36 blocks not signalling readiness (STARTED->LOCKED_IN)
+mine a further 143 blocks (LOCKED_IN)
+test that enforcement has not triggered (which triggers ACTIVE)
+test that enforcement has triggered
+'''
+
+
+class BIP9SoftForksTest(ComparisonTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+
+ def setup_network(self):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
+ extra_args=[['-debug', '-whitelist=127.0.0.1']],
+ binary=[self.options.testbinary])
+
+ def run_test(self):
+ self.test = TestManager(self, self.options.tmpdir)
+ self.test.add_all_connections(self.nodes)
+ NetworkThread().start() # Start up network handling in another thread
+ self.test.run()
+
+ def create_transaction(self, node, coinbase, to_address, amount):
+ from_txid = node.getblock(coinbase)['tx'][0]
+ inputs = [{ "txid" : from_txid, "vout" : 0}]
+ outputs = { to_address : amount }
+ rawtx = node.createrawtransaction(inputs, outputs)
+ tx = CTransaction()
+ f = BytesIO(hex_str_to_bytes(rawtx))
+ tx.deserialize(f)
+ tx.nVersion = 2
+ return tx
+
+ def sign_transaction(self, node, tx):
+ signresult = node.signrawtransaction(bytes_to_hex_str(tx.serialize()))
+ tx = CTransaction()
+ f = BytesIO(hex_str_to_bytes(signresult['hex']))
+ tx.deserialize(f)
+ return tx
+
+ def generate_blocks(self, number, version, test_blocks = []):
+ for i in range(number):
+ block = create_block(self.tip, create_coinbase(self.height), self.last_block_time + 1)
+ block.nVersion = version
+ block.rehash()
+ block.solve()
+ test_blocks.append([block, True])
+ self.last_block_time += 1
+ self.tip = block.sha256
+ self.height += 1
+ return test_blocks
+
+ def get_bip9_status(self, key):
+ info = self.nodes[0].getblockchaininfo()
+ return info['bip9_softforks'][key]
+
+ def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignature, bitno):
+ # generate some coins for later
+ self.coinbase_blocks = self.nodes[0].generate(2)
+ self.height = 3 # height of the next block to build
+ self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0)
+ self.nodeaddress = self.nodes[0].getnewaddress()
+ self.last_block_time = int(time.time())
+
+ assert_equal(self.get_bip9_status(bipName)['status'], 'defined')
+ tmpl = self.nodes[0].getblocktemplate({})
+ assert(bipName not in tmpl['rules'])
+ assert(bipName not in tmpl['vbavailable'])
+ assert_equal(tmpl['vbrequired'], 0)
+ assert_equal(tmpl['version'], 0x20000000)
+
+ # Test 1
+ # Advance from DEFINED to STARTED
+ test_blocks = self.generate_blocks(141, 4)
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ assert_equal(self.get_bip9_status(bipName)['status'], 'started')
+ tmpl = self.nodes[0].getblocktemplate({})
+ assert(bipName not in tmpl['rules'])
+ assert_equal(tmpl['vbavailable'][bipName], bitno)
+ assert_equal(tmpl['vbrequired'], 0)
+ assert(tmpl['version'] & activated_version)
+
+ # Test 2
+ # Fail to achieve LOCKED_IN 100 out of 144 signal bit 1
+ # using a variety of bits to simulate multiple parallel softforks
+ test_blocks = self.generate_blocks(50, activated_version) # 0x20000001 (signalling ready)
+ test_blocks = self.generate_blocks(20, 4, test_blocks) # 0x00000004 (signalling not)
+ test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready)
+ test_blocks = self.generate_blocks(24, 4, test_blocks) # 0x20010000 (signalling not)
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ assert_equal(self.get_bip9_status(bipName)['status'], 'started')
+ tmpl = self.nodes[0].getblocktemplate({})
+ assert(bipName not in tmpl['rules'])
+ assert_equal(tmpl['vbavailable'][bipName], bitno)
+ assert_equal(tmpl['vbrequired'], 0)
+ assert(tmpl['version'] & activated_version)
+
+ # Test 3
+ # 108 out of 144 signal bit 1 to achieve LOCKED_IN
+ # using a variety of bits to simulate multiple parallel softforks
+ test_blocks = self.generate_blocks(58, activated_version) # 0x20000001 (signalling ready)
+ test_blocks = self.generate_blocks(26, 4, test_blocks) # 0x00000004 (signalling not)
+ test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready)
+ test_blocks = self.generate_blocks(10, 4, test_blocks) # 0x20010000 (signalling not)
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in')
+ tmpl = self.nodes[0].getblocktemplate({})
+ assert(bipName not in tmpl['rules'])
+
+ # Test 4
+ # 143 more version 536870913 blocks (waiting period-1)
+ test_blocks = self.generate_blocks(143, 4)
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in')
+ tmpl = self.nodes[0].getblocktemplate({})
+ assert(bipName not in tmpl['rules'])
+
+ # Test 5
+ # Check that the new rule is enforced
+ spendtx = self.create_transaction(self.nodes[0],
+ self.coinbase_blocks[0], self.nodeaddress, 1.0)
+ invalidate(spendtx)
+ spendtx = self.sign_transaction(self.nodes[0], spendtx)
+ spendtx.rehash()
+ invalidatePostSignature(spendtx)
+ spendtx.rehash()
+ block = create_block(self.tip, create_coinbase(self.height), self.last_block_time + 1)
+ block.nVersion = activated_version
+ block.vtx.append(spendtx)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ block.solve()
+
+ self.last_block_time += 1
+ self.tip = block.sha256
+ self.height += 1
+ yield TestInstance([[block, True]])
+
+ assert_equal(self.get_bip9_status(bipName)['status'], 'active')
+ tmpl = self.nodes[0].getblocktemplate({})
+ assert(bipName in tmpl['rules'])
+ assert(bipName not in tmpl['vbavailable'])
+ assert_equal(tmpl['vbrequired'], 0)
+ assert(not (tmpl['version'] & (1 << bitno)))
+
+ # Test 6
+ # Check that the new sequence lock rules are enforced
+ spendtx = self.create_transaction(self.nodes[0],
+ self.coinbase_blocks[1], self.nodeaddress, 1.0)
+ invalidate(spendtx)
+ spendtx = self.sign_transaction(self.nodes[0], spendtx)
+ spendtx.rehash()
+ invalidatePostSignature(spendtx)
+ spendtx.rehash()
+
+ block = create_block(self.tip, create_coinbase(self.height), self.last_block_time + 1)
+ block.nVersion = 5
+ block.vtx.append(spendtx)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ yield TestInstance([[block, False]])
+
+ # Restart all
+ self.test.block_store.close()
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+ shutil.rmtree(self.options.tmpdir)
+ self.setup_chain()
+ self.setup_network()
+ self.test.block_store = BlockStore(self.options.tmpdir)
+ self.test.clear_all_connections()
+ self.test.add_all_connections(self.nodes)
+ NetworkThread().start() # Start up network handling in another thread
+
+
+ def get_tests(self):
+ for test in itertools.chain(
+ self.test_BIP('csv', 0x20000001, self.sequence_lock_invalidate, self.donothing, 0),
+ self.test_BIP('csv', 0x20000001, self.mtp_invalidate, self.donothing, 0),
+ self.test_BIP('csv', 0x20000001, self.donothing, self.csv_invalidate, 0)
+ ):
+ yield test
+
+ def donothing(self, tx):
+ return
+
+ def csv_invalidate(self, tx):
+ '''Modify the signature in vin 0 of the tx to fail CSV
+ Prepends -1 CSV DROP in the scriptSig itself.
+ '''
+ tx.vin[0].scriptSig = CScript([OP_1NEGATE, OP_CHECKSEQUENCEVERIFY, OP_DROP] +
+ list(CScript(tx.vin[0].scriptSig)))
+
+ def sequence_lock_invalidate(self, tx):
+ '''Modify the nSequence to make it fails once sequence lock rule is activated (high timespan)
+ '''
+ tx.vin[0].nSequence = 0x00FFFFFF
+ tx.nLockTime = 0
+
+ def mtp_invalidate(self, tx):
+ '''Modify the nLockTime to make it fails once MTP rule is activated
+ '''
+ # Disable Sequence lock, Activate nLockTime
+ tx.vin[0].nSequence = 0x90FFFFFF
+ tx.nLockTime = self.last_block_time
+
+if __name__ == '__main__':
+ BIP9SoftForksTest().main()
diff --git a/qa/rpc-tests/bipdersig-p2p.py b/qa/rpc-tests/bipdersig-p2p.py
new file mode 100755
index 0000000000..4e4936a4ae
--- /dev/null
+++ b/qa/rpc-tests/bipdersig-p2p.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.util import *
+from test_framework.mininode import CTransaction, NetworkThread
+from test_framework.blocktools import create_coinbase, create_block
+from test_framework.comptool import TestInstance, TestManager
+from test_framework.script import CScript
+from io import BytesIO
+import time
+
+# A canonical signature consists of:
+# <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
+def unDERify(tx):
+ '''
+ Make the signature in vin 0 of a tx non-DER-compliant,
+ by adding padding after the S-value.
+ '''
+ scriptSig = CScript(tx.vin[0].scriptSig)
+ newscript = []
+ for i in scriptSig:
+ if (len(newscript) == 0):
+ newscript.append(i[0:-1] + b'\0' + i[-1:])
+ else:
+ newscript.append(i)
+ tx.vin[0].scriptSig = CScript(newscript)
+
+'''
+This test is meant to exercise BIP66 (DER SIG).
+Connect to a single node.
+Mine 2 (version 2) blocks (save the coinbases for later).
+Generate 98 more version 2 blocks, verify the node accepts.
+Mine 749 version 3 blocks, verify the node accepts.
+Check that the new DERSIG rules are not enforced on the 750th version 3 block.
+Check that the new DERSIG rules are enforced on the 751st version 3 block.
+Mine 199 new version blocks.
+Mine 1 old-version block.
+Mine 1 new version block.
+Mine 1 old version block, see that the node rejects.
+'''
+
+class BIP66Test(ComparisonTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+
+ def setup_network(self):
+ # Must set the blockversion for this test
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
+ extra_args=[['-debug', '-whitelist=127.0.0.1', '-blockversion=2']],
+ binary=[self.options.testbinary])
+
+ def run_test(self):
+ test = TestManager(self, self.options.tmpdir)
+ test.add_all_connections(self.nodes)
+ NetworkThread().start() # Start up network handling in another thread
+ test.run()
+
+ def create_transaction(self, node, coinbase, to_address, amount):
+ from_txid = node.getblock(coinbase)['tx'][0]
+ inputs = [{ "txid" : from_txid, "vout" : 0}]
+ outputs = { to_address : amount }
+ rawtx = node.createrawtransaction(inputs, outputs)
+ signresult = node.signrawtransaction(rawtx)
+ tx = CTransaction()
+ f = BytesIO(hex_str_to_bytes(signresult['hex']))
+ tx.deserialize(f)
+ return tx
+
+ def get_tests(self):
+
+ self.coinbase_blocks = self.nodes[0].generate(2)
+ height = 3 # height of the next block to build
+ self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0)
+ self.nodeaddress = self.nodes[0].getnewaddress()
+ self.last_block_time = int(time.time())
+
+ ''' 98 more version 2 blocks '''
+ test_blocks = []
+ for i in range(98):
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 2
+ block.rehash()
+ block.solve()
+ test_blocks.append([block, True])
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ ''' Mine 749 version 3 blocks '''
+ test_blocks = []
+ for i in range(749):
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 3
+ block.rehash()
+ block.solve()
+ test_blocks.append([block, True])
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ '''
+ Check that the new DERSIG rules are not enforced in the 750th
+ version 3 block.
+ '''
+ spendtx = self.create_transaction(self.nodes[0],
+ self.coinbase_blocks[0], self.nodeaddress, 1.0)
+ unDERify(spendtx)
+ spendtx.rehash()
+
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 3
+ block.vtx.append(spendtx)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ block.solve()
+
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance([[block, True]])
+
+ '''
+ Check that the new DERSIG rules are enforced in the 751st version 3
+ block.
+ '''
+ spendtx = self.create_transaction(self.nodes[0],
+ self.coinbase_blocks[1], self.nodeaddress, 1.0)
+ unDERify(spendtx)
+ spendtx.rehash()
+
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 3
+ block.vtx.append(spendtx)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ yield TestInstance([[block, False]])
+
+ ''' Mine 199 new version blocks on last valid tip '''
+ test_blocks = []
+ for i in range(199):
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 3
+ block.rehash()
+ block.solve()
+ test_blocks.append([block, True])
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ ''' Mine 1 old version block '''
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 2
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance([[block, True]])
+
+ ''' Mine 1 new version block '''
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 3
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance([[block, True]])
+
+ ''' Mine 1 old version block, should be invalid '''
+ block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
+ block.nVersion = 2
+ block.rehash()
+ block.solve()
+ self.last_block_time += 1
+ yield TestInstance([[block, False]])
+
+if __name__ == '__main__':
+ BIP66Test().main()
diff --git a/qa/rpc-tests/bipdersig.py b/qa/rpc-tests/bipdersig.py
new file mode 100755
index 0000000000..17c2ced79a
--- /dev/null
+++ b/qa/rpc-tests/bipdersig.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test the BIP66 changeover logic
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+class BIP66Test(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 3
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, []))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-blockversion=2"]))
+ self.nodes.append(start_node(2, self.options.tmpdir, ["-blockversion=3"]))
+ connect_nodes(self.nodes[1], 0)
+ connect_nodes(self.nodes[2], 0)
+ self.is_network_split = False
+ self.sync_all()
+
+ def run_test(self):
+ cnt = self.nodes[0].getblockcount()
+
+ # Mine some old-version blocks
+ self.nodes[1].generate(100)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 100):
+ raise AssertionError("Failed to mine 100 version=2 blocks")
+
+ # Mine 750 new-version blocks
+ for i in range(15):
+ self.nodes[2].generate(50)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 850):
+ raise AssertionError("Failed to mine 750 version=3 blocks")
+
+ # TODO: check that new DERSIG rules are not enforced
+
+ # Mine 1 new-version block
+ self.nodes[2].generate(1)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 851):
+ raise AssertionError("Failed to mine a version=3 blocks")
+
+ # TODO: check that new DERSIG rules are enforced
+
+ # Mine 198 new-version blocks
+ for i in range(2):
+ self.nodes[2].generate(99)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1049):
+ raise AssertionError("Failed to mine 198 version=3 blocks")
+
+ # Mine 1 old-version block
+ self.nodes[1].generate(1)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1050):
+ raise AssertionError("Failed to mine a version=2 block after 949 version=3 blocks")
+
+ # Mine 1 new-version blocks
+ self.nodes[2].generate(1)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1051):
+ raise AssertionError("Failed to mine a version=3 block")
+
+ # Mine 1 old-version blocks
+ try:
+ self.nodes[1].generate(1)
+ raise AssertionError("Succeeded to mine a version=2 block after 950 version=3 blocks")
+ except JSONRPCException:
+ pass
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1051):
+ raise AssertionError("Accepted a version=2 block after 950 version=3 blocks")
+
+ # Mine 1 new-version blocks
+ self.nodes[2].generate(1)
+ self.sync_all()
+ if (self.nodes[0].getblockcount() != cnt + 1052):
+ raise AssertionError("Failed to mine a version=3 block")
+
+if __name__ == '__main__':
+ BIP66Test().main()
diff --git a/qa/rpc-tests/blockchain.py b/qa/rpc-tests/blockchain.py
new file mode 100755
index 0000000000..410b85d15e
--- /dev/null
+++ b/qa/rpc-tests/blockchain.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test RPC calls related to blockchain state. Tests correspond to code in
+# rpc/blockchain.cpp.
+#
+
+from decimal import Decimal
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.authproxy import JSONRPCException
+from test_framework.util import (
+ assert_equal,
+ assert_raises,
+ assert_is_hex_string,
+ assert_is_hash_string,
+ start_nodes,
+ connect_nodes_bi,
+)
+
+
+class BlockchainTest(BitcoinTestFramework):
+ """
+ Test blockchain-related RPC calls:
+
+ - gettxoutsetinfo
+ - verifychain
+
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = False
+ self.num_nodes = 2
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+ connect_nodes_bi(self.nodes, 0, 1)
+ self.is_network_split = False
+ self.sync_all()
+
+ def run_test(self):
+ self._test_gettxoutsetinfo()
+ self._test_getblockheader()
+ self.nodes[0].verifychain(4, 0)
+
+ def _test_gettxoutsetinfo(self):
+ node = self.nodes[0]
+ res = node.gettxoutsetinfo()
+
+ assert_equal(res['total_amount'], Decimal('8725.00000000'))
+ assert_equal(res['transactions'], 200)
+ assert_equal(res['height'], 200)
+ assert_equal(res['txouts'], 200)
+ assert_equal(res['bytes_serialized'], 13924),
+ assert_equal(len(res['bestblock']), 64)
+ assert_equal(len(res['hash_serialized']), 64)
+
+ def _test_getblockheader(self):
+ node = self.nodes[0]
+
+ assert_raises(
+ JSONRPCException, lambda: node.getblockheader('nonsense'))
+
+ besthash = node.getbestblockhash()
+ secondbesthash = node.getblockhash(199)
+ header = node.getblockheader(besthash)
+
+ assert_equal(header['hash'], besthash)
+ assert_equal(header['height'], 200)
+ assert_equal(header['confirmations'], 1)
+ assert_equal(header['previousblockhash'], secondbesthash)
+ assert_is_hex_string(header['chainwork'])
+ assert_is_hash_string(header['hash'])
+ assert_is_hash_string(header['previousblockhash'])
+ assert_is_hash_string(header['merkleroot'])
+ assert_is_hash_string(header['bits'], length=None)
+ assert isinstance(header['time'], int)
+ assert isinstance(header['mediantime'], int)
+ assert isinstance(header['nonce'], int)
+ assert isinstance(header['version'], int)
+ assert isinstance(int(header['versionHex'], 16), int)
+ assert isinstance(header['difficulty'], Decimal)
+
+if __name__ == '__main__':
+ BlockchainTest().main()
diff --git a/qa/rpc-tests/conflictedbalance.sh b/qa/rpc-tests/conflictedbalance.sh
deleted file mode 100755
index a112244c74..0000000000
--- a/qa/rpc-tests/conflictedbalance.sh
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/env bash
-# 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 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"
- echo "Env vars BITCOIND and BITCOINCLI may be used to specify the exact binaries used"
- exit 1
-fi
-
-set -f
-
-BITCOIND=${BITCOIND:-${1}/bitcoind}
-CLI=${BITCOINCLI:-${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/create_cache.py b/qa/rpc-tests/create_cache.py
new file mode 100755
index 0000000000..b6161e0917
--- /dev/null
+++ b/qa/rpc-tests/create_cache.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Helper script to create the cache
+# (see BitcoinTestFramework.setup_chain)
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+
+class CreateCache(BitcoinTestFramework):
+
+ def setup_network(self):
+ # Don't setup any test nodes
+ self.options.noshutdown = True
+
+ def run_test(self):
+ pass
+
+if __name__ == '__main__':
+ CreateCache().main()
diff --git a/qa/rpc-tests/decodescript.py b/qa/rpc-tests/decodescript.py
new file mode 100755
index 0000000000..24768c2655
--- /dev/null
+++ b/qa/rpc-tests/decodescript.py
@@ -0,0 +1,185 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.mininode import *
+from io import BytesIO
+
+class DecodeScriptTest(BitcoinTestFramework):
+ """Tests decoding scripts via RPC command "decodescript"."""
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+ self.is_network_split = False
+
+ def decodescript_script_sig(self):
+ signature = '304502207fa7a6d1e0ee81132a269ad84e68d695483745cde8b541e3bf630749894e342a022100c1f7ab20e13e22fb95281a870f3dcf38d782e53023ee313d741ad0cfbc0c509001'
+ push_signature = '48' + signature
+ public_key = '03b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb2'
+ push_public_key = '21' + public_key
+
+ # below are test cases for all of the standard transaction types
+
+ # 1) P2PK scriptSig
+ # the scriptSig of a public key scriptPubKey simply pushes a signature onto the stack
+ rpc_result = self.nodes[0].decodescript(push_signature)
+ assert_equal(signature, rpc_result['asm'])
+
+ # 2) P2PKH scriptSig
+ rpc_result = self.nodes[0].decodescript(push_signature + push_public_key)
+ assert_equal(signature + ' ' + public_key, rpc_result['asm'])
+
+ # 3) multisig scriptSig
+ # this also tests the leading portion of a P2SH multisig scriptSig
+ # OP_0 <A sig> <B sig>
+ rpc_result = self.nodes[0].decodescript('00' + push_signature + push_signature)
+ assert_equal('0 ' + signature + ' ' + signature, rpc_result['asm'])
+
+ # 4) P2SH scriptSig
+ # an empty P2SH redeemScript is valid and makes for a very simple test case.
+ # thus, such a spending scriptSig would just need to pass the outer redeemScript
+ # hash test and leave true on the top of the stack.
+ rpc_result = self.nodes[0].decodescript('5100')
+ assert_equal('1 0', rpc_result['asm'])
+
+ # 5) null data scriptSig - no such thing because null data scripts can not be spent.
+ # thus, no test case for that standard transaction type is here.
+
+ def decodescript_script_pub_key(self):
+ public_key = '03b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb2'
+ push_public_key = '21' + public_key
+ public_key_hash = '11695b6cd891484c2d49ec5aa738ec2b2f897777'
+ push_public_key_hash = '14' + public_key_hash
+
+ # below are test cases for all of the standard transaction types
+
+ # 1) P2PK scriptPubKey
+ # <pubkey> OP_CHECKSIG
+ rpc_result = self.nodes[0].decodescript(push_public_key + 'ac')
+ assert_equal(public_key + ' OP_CHECKSIG', rpc_result['asm'])
+
+ # 2) P2PKH scriptPubKey
+ # OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
+ rpc_result = self.nodes[0].decodescript('76a9' + push_public_key_hash + '88ac')
+ assert_equal('OP_DUP OP_HASH160 ' + public_key_hash + ' OP_EQUALVERIFY OP_CHECKSIG', rpc_result['asm'])
+
+ # 3) multisig scriptPubKey
+ # <m> <A pubkey> <B pubkey> <C pubkey> <n> OP_CHECKMULTISIG
+ # just imagine that the pub keys used below are different.
+ # for our purposes here it does not matter that they are the same even though it is unrealistic.
+ rpc_result = self.nodes[0].decodescript('52' + push_public_key + push_public_key + push_public_key + '53ae')
+ assert_equal('2 ' + public_key + ' ' + public_key + ' ' + public_key + ' 3 OP_CHECKMULTISIG', rpc_result['asm'])
+
+ # 4) P2SH scriptPubKey
+ # OP_HASH160 <Hash160(redeemScript)> OP_EQUAL.
+ # push_public_key_hash here should actually be the hash of a redeem script.
+ # but this works the same for purposes of this test.
+ rpc_result = self.nodes[0].decodescript('a9' + push_public_key_hash + '87')
+ assert_equal('OP_HASH160 ' + public_key_hash + ' OP_EQUAL', rpc_result['asm'])
+
+ # 5) null data scriptPubKey
+ # use a signature look-alike here to make sure that we do not decode random data as a signature.
+ # this matters if/when signature sighash decoding comes along.
+ # would want to make sure that no such decoding takes place in this case.
+ signature_imposter = '48304502207fa7a6d1e0ee81132a269ad84e68d695483745cde8b541e3bf630749894e342a022100c1f7ab20e13e22fb95281a870f3dcf38d782e53023ee313d741ad0cfbc0c509001'
+ # OP_RETURN <data>
+ rpc_result = self.nodes[0].decodescript('6a' + signature_imposter)
+ assert_equal('OP_RETURN ' + signature_imposter[2:], rpc_result['asm'])
+
+ # 6) a CLTV redeem script. redeem scripts are in-effect scriptPubKey scripts, so adding a test here.
+ # OP_NOP2 is also known as OP_CHECKLOCKTIMEVERIFY.
+ # just imagine that the pub keys used below are different.
+ # for our purposes here it does not matter that they are the same even though it is unrealistic.
+ #
+ # OP_IF
+ # <receiver-pubkey> OP_CHECKSIGVERIFY
+ # OP_ELSE
+ # <lock-until> OP_CHECKLOCKTIMEVERIFY OP_DROP
+ # OP_ENDIF
+ # <sender-pubkey> OP_CHECKSIG
+ #
+ # lock until block 500,000
+ rpc_result = self.nodes[0].decodescript('63' + push_public_key + 'ad670320a107b17568' + push_public_key + 'ac')
+ assert_equal('OP_IF ' + public_key + ' OP_CHECKSIGVERIFY OP_ELSE 500000 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_ENDIF ' + public_key + ' OP_CHECKSIG', rpc_result['asm'])
+
+ def decoderawtransaction_asm_sighashtype(self):
+ """Tests decoding scripts via RPC command "decoderawtransaction".
+
+ This test is in with the "decodescript" tests because they are testing the same "asm" script decodes.
+ """
+
+ # this test case uses a random plain vanilla mainnet transaction with a single P2PKH input and output
+ tx = '0100000001696a20784a2c70143f634e95227dbdfdf0ecd51647052e70854512235f5986ca010000008a47304402207174775824bec6c2700023309a168231ec80b82c6069282f5133e6f11cbb04460220570edc55c7c5da2ca687ebd0372d3546ebc3f810516a002350cac72dfe192dfb014104d3f898e6487787910a690410b7a917ef198905c27fb9d3b0a42da12aceae0544fc7088d239d9a48f2828a15a09e84043001f27cc80d162cb95404e1210161536ffffffff0100e1f505000000001976a914eb6c6e0cdb2d256a32d97b8df1fc75d1920d9bca88ac00000000'
+ rpc_result = self.nodes[0].decoderawtransaction(tx)
+ assert_equal('304402207174775824bec6c2700023309a168231ec80b82c6069282f5133e6f11cbb04460220570edc55c7c5da2ca687ebd0372d3546ebc3f810516a002350cac72dfe192dfb[ALL] 04d3f898e6487787910a690410b7a917ef198905c27fb9d3b0a42da12aceae0544fc7088d239d9a48f2828a15a09e84043001f27cc80d162cb95404e1210161536', rpc_result['vin'][0]['scriptSig']['asm'])
+
+ # this test case uses a mainnet transaction that has a P2SH input and both P2PKH and P2SH outputs.
+ # it's from James D'Angelo's awesome introductory videos about multisig: https://www.youtube.com/watch?v=zIbUSaZBJgU and https://www.youtube.com/watch?v=OSA1pwlaypc
+ # verify that we have not altered scriptPubKey decoding.
+ tx = '01000000018d1f5635abd06e2c7e2ddf58dc85b3de111e4ad6e0ab51bb0dcf5e84126d927300000000fdfe0000483045022100ae3b4e589dfc9d48cb82d41008dc5fa6a86f94d5c54f9935531924602730ab8002202f88cf464414c4ed9fa11b773c5ee944f66e9b05cc1e51d97abc22ce098937ea01483045022100b44883be035600e9328a01b66c7d8439b74db64187e76b99a68f7893b701d5380220225bf286493e4c4adcf928c40f785422572eb232f84a0b83b0dea823c3a19c75014c695221020743d44be989540d27b1b4bbbcfd17721c337cb6bc9af20eb8a32520b393532f2102c0120a1dda9e51a938d39ddd9fe0ebc45ea97e1d27a7cbd671d5431416d3dd87210213820eb3d5f509d7438c9eeecb4157b2f595105e7cd564b3cdbb9ead3da41eed53aeffffffff02611e0000000000001976a914dc863734a218bfe83ef770ee9d41a27f824a6e5688acee2a02000000000017a9142a5edea39971049a540474c6a99edf0aa4074c588700000000'
+ rpc_result = self.nodes[0].decoderawtransaction(tx)
+ assert_equal('8e3730608c3b0bb5df54f09076e196bc292a8e39a78e73b44b6ba08c78f5cbb0', rpc_result['txid'])
+ assert_equal('0 3045022100ae3b4e589dfc9d48cb82d41008dc5fa6a86f94d5c54f9935531924602730ab8002202f88cf464414c4ed9fa11b773c5ee944f66e9b05cc1e51d97abc22ce098937ea[ALL] 3045022100b44883be035600e9328a01b66c7d8439b74db64187e76b99a68f7893b701d5380220225bf286493e4c4adcf928c40f785422572eb232f84a0b83b0dea823c3a19c75[ALL] 5221020743d44be989540d27b1b4bbbcfd17721c337cb6bc9af20eb8a32520b393532f2102c0120a1dda9e51a938d39ddd9fe0ebc45ea97e1d27a7cbd671d5431416d3dd87210213820eb3d5f509d7438c9eeecb4157b2f595105e7cd564b3cdbb9ead3da41eed53ae', rpc_result['vin'][0]['scriptSig']['asm'])
+ assert_equal('OP_DUP OP_HASH160 dc863734a218bfe83ef770ee9d41a27f824a6e56 OP_EQUALVERIFY OP_CHECKSIG', rpc_result['vout'][0]['scriptPubKey']['asm'])
+ assert_equal('OP_HASH160 2a5edea39971049a540474c6a99edf0aa4074c58 OP_EQUAL', rpc_result['vout'][1]['scriptPubKey']['asm'])
+ txSave = CTransaction()
+ txSave.deserialize(BytesIO(hex_str_to_bytes(tx)))
+
+ # make sure that a specifically crafted op_return value will not pass all the IsDERSignature checks and then get decoded as a sighash type
+ tx = '01000000015ded05872fdbda629c7d3d02b194763ce3b9b1535ea884e3c8e765d42e316724020000006b48304502204c10d4064885c42638cbff3585915b322de33762598321145ba033fc796971e2022100bb153ad3baa8b757e30a2175bd32852d2e1cb9080f84d7e32fcdfd667934ef1b012103163c0ff73511ea1743fb5b98384a2ff09dd06949488028fd819f4d83f56264efffffffff0200000000000000000b6a0930060201000201000180380100000000001976a9141cabd296e753837c086da7a45a6c2fe0d49d7b7b88ac00000000'
+ rpc_result = self.nodes[0].decoderawtransaction(tx)
+ assert_equal('OP_RETURN 300602010002010001', rpc_result['vout'][0]['scriptPubKey']['asm'])
+
+ # verify that we have not altered scriptPubKey processing even of a specially crafted P2PKH pubkeyhash and P2SH redeem script hash that is made to pass the der signature checks
+ tx = '01000000018d1f5635abd06e2c7e2ddf58dc85b3de111e4ad6e0ab51bb0dcf5e84126d927300000000fdfe0000483045022100ae3b4e589dfc9d48cb82d41008dc5fa6a86f94d5c54f9935531924602730ab8002202f88cf464414c4ed9fa11b773c5ee944f66e9b05cc1e51d97abc22ce098937ea01483045022100b44883be035600e9328a01b66c7d8439b74db64187e76b99a68f7893b701d5380220225bf286493e4c4adcf928c40f785422572eb232f84a0b83b0dea823c3a19c75014c695221020743d44be989540d27b1b4bbbcfd17721c337cb6bc9af20eb8a32520b393532f2102c0120a1dda9e51a938d39ddd9fe0ebc45ea97e1d27a7cbd671d5431416d3dd87210213820eb3d5f509d7438c9eeecb4157b2f595105e7cd564b3cdbb9ead3da41eed53aeffffffff02611e0000000000001976a914301102070101010101010102060101010101010188acee2a02000000000017a91430110207010101010101010206010101010101018700000000'
+ rpc_result = self.nodes[0].decoderawtransaction(tx)
+ assert_equal('OP_DUP OP_HASH160 3011020701010101010101020601010101010101 OP_EQUALVERIFY OP_CHECKSIG', rpc_result['vout'][0]['scriptPubKey']['asm'])
+ assert_equal('OP_HASH160 3011020701010101010101020601010101010101 OP_EQUAL', rpc_result['vout'][1]['scriptPubKey']['asm'])
+
+ # some more full transaction tests of varying specific scriptSigs. used instead of
+ # tests in decodescript_script_sig because the decodescript RPC is specifically
+ # for working on scriptPubKeys (argh!).
+ push_signature = bytes_to_hex_str(txSave.vin[0].scriptSig)[2:(0x48*2+4)]
+ signature = push_signature[2:]
+ der_signature = signature[:-2]
+ signature_sighash_decoded = der_signature + '[ALL]'
+ signature_2 = der_signature + '82'
+ push_signature_2 = '48' + signature_2
+ signature_2_sighash_decoded = der_signature + '[NONE|ANYONECANPAY]'
+
+ # 1) P2PK scriptSig
+ txSave.vin[0].scriptSig = hex_str_to_bytes(push_signature)
+ rpc_result = self.nodes[0].decoderawtransaction(bytes_to_hex_str(txSave.serialize()))
+ assert_equal(signature_sighash_decoded, rpc_result['vin'][0]['scriptSig']['asm'])
+
+ # make sure that the sighash decodes come out correctly for a more complex / lesser used case.
+ txSave.vin[0].scriptSig = hex_str_to_bytes(push_signature_2)
+ rpc_result = self.nodes[0].decoderawtransaction(bytes_to_hex_str(txSave.serialize()))
+ assert_equal(signature_2_sighash_decoded, rpc_result['vin'][0]['scriptSig']['asm'])
+
+ # 2) multisig scriptSig
+ txSave.vin[0].scriptSig = hex_str_to_bytes('00' + push_signature + push_signature_2)
+ rpc_result = self.nodes[0].decoderawtransaction(bytes_to_hex_str(txSave.serialize()))
+ assert_equal('0 ' + signature_sighash_decoded + ' ' + signature_2_sighash_decoded, rpc_result['vin'][0]['scriptSig']['asm'])
+
+ # 3) test a scriptSig that contains more than push operations.
+ # in fact, it contains an OP_RETURN with data specially crafted to cause improper decode if the code does not catch it.
+ txSave.vin[0].scriptSig = hex_str_to_bytes('6a143011020701010101010101020601010101010101')
+ rpc_result = self.nodes[0].decoderawtransaction(bytes_to_hex_str(txSave.serialize()))
+ assert_equal('OP_RETURN 3011020701010101010101020601010101010101', rpc_result['vin'][0]['scriptSig']['asm'])
+
+ def run_test(self):
+ self.decodescript_script_sig()
+ self.decodescript_script_pub_key()
+ self.decoderawtransaction_asm_sighashtype()
+
+if __name__ == '__main__':
+ DecodeScriptTest().main()
diff --git a/qa/rpc-tests/disablewallet.py b/qa/rpc-tests/disablewallet.py
new file mode 100755
index 0000000000..36c147edad
--- /dev/null
+++ b/qa/rpc-tests/disablewallet.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Exercise API with -disablewallet.
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+
+class DisableWalletTest (BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [['-disablewallet']])
+ self.is_network_split = False
+ self.sync_all()
+
+ def run_test (self):
+ # Check regression: https://github.com/bitcoin/bitcoin/issues/6963#issuecomment-154548880
+ x = self.nodes[0].validateaddress('3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy')
+ assert(x['isvalid'] == False)
+ x = self.nodes[0].validateaddress('mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ')
+ assert(x['isvalid'] == True)
+
+ # Checking mining to an address without a wallet
+ try:
+ self.nodes[0].generatetoaddress(1, 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ')
+ except JSONRPCException as e:
+ assert("Invalid address" not in e.error['message'])
+ assert("ProcessNewBlock, block not accepted" not in e.error['message'])
+ assert("Couldn't create new block" not in e.error['message'])
+
+ try:
+ self.nodes[0].generatetoaddress(1, '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy')
+ raise AssertionError("Must not mine to invalid address!")
+ except JSONRPCException as e:
+ assert("Invalid address" in e.error['message'])
+
+if __name__ == '__main__':
+ DisableWalletTest ().main ()
diff --git a/qa/rpc-tests/forknotify.py b/qa/rpc-tests/forknotify.py
index ad2a748ca1..5a3f75c808 100755
--- a/qa/rpc-tests/forknotify.py
+++ b/qa/rpc-tests/forknotify.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -7,14 +7,16 @@
# Test -alertnotify
#
-from test_framework import BitcoinTestFramework
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
-import os
-import shutil
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
class ForkNotifyTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = False
+
alert_filename = None # Set by setup_network
def setup_network(self):
@@ -34,12 +36,12 @@ class ForkNotifyTest(BitcoinTestFramework):
def run_test(self):
# Mine 51 up-version blocks
- self.nodes[1].setgenerate(True, 51)
+ self.nodes[1].generate(51)
self.sync_all()
# -alertnotify should trigger on the 51'st,
# but mine and sync another to give
# -alertnotify time to write
- self.nodes[1].setgenerate(True, 1)
+ self.nodes[1].generate(1)
self.sync_all()
with open(self.alert_filename, 'r') as f:
@@ -49,9 +51,9 @@ class ForkNotifyTest(BitcoinTestFramework):
raise AssertionError("-alertnotify did not warn of up-version blocks")
# Mine more up-version blocks, should not get more alerts:
- self.nodes[1].setgenerate(True, 1)
+ self.nodes[1].generate(1)
self.sync_all()
- self.nodes[1].setgenerate(True, 1)
+ self.nodes[1].generate(1)
self.sync_all()
with open(self.alert_filename, 'r') as f:
diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py
new file mode 100755
index 0000000000..eeb8476634
--- /dev/null
+++ b/qa/rpc-tests/fundrawtransaction.py
@@ -0,0 +1,648 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+
+def get_unspent(listunspent, amount):
+ for utx in listunspent:
+ if utx['amount'] == amount:
+ return utx
+ raise AssertionError('Could not find unspent with amount={}'.format(amount))
+
+
+class RawTransactionsTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 4
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+ connect_nodes_bi(self.nodes,0,3)
+
+ self.is_network_split=False
+ self.sync_all()
+
+ def run_test(self):
+ print("Mining blocks...")
+
+ min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee']
+ # This test is not meant to test fee estimation and we'd like
+ # to be sure all txs are sent at a consistent desired feerate
+ for node in self.nodes:
+ node.settxfee(min_relay_tx_fee)
+
+ # if the fee's positive delta is higher than this value tests will fail,
+ # neg. delta always fail the tests.
+ # The size of the signature of every input may be at most 2 bytes larger
+ # than a minimum sized signature.
+
+ # = 2 bytes * minRelayTxFeePerByte
+ feeTolerance = 2 * min_relay_tx_fee/1000
+
+ self.nodes[2].generate(1)
+ self.sync_all()
+ self.nodes[0].generate(121)
+ self.sync_all()
+
+ watchonly_address = self.nodes[0].getnewaddress()
+ watchonly_pubkey = self.nodes[0].validateaddress(watchonly_address)["pubkey"]
+ watchonly_amount = Decimal(200)
+ self.nodes[3].importpubkey(watchonly_pubkey, "", True)
+ watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount)
+ self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10)
+
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5)
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 5.0)
+
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ ###############
+ # simple test #
+ ###############
+ inputs = [ ]
+ outputs = { self.nodes[0].getnewaddress() : 1.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ assert(len(dec_tx['vin']) > 0) #test that we have enough inputs
+
+ ##############################
+ # simple test with two coins #
+ ##############################
+ inputs = [ ]
+ outputs = { self.nodes[0].getnewaddress() : 2.2 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ assert(len(dec_tx['vin']) > 0) #test if we have enough inputs
+
+ ##############################
+ # simple test with two coins #
+ ##############################
+ inputs = [ ]
+ outputs = { self.nodes[0].getnewaddress() : 2.6 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ assert(len(dec_tx['vin']) > 0)
+ assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')
+
+
+ ################################
+ # simple test with two outputs #
+ ################################
+ inputs = [ ]
+ outputs = { self.nodes[0].getnewaddress() : 2.6, self.nodes[1].getnewaddress() : 2.5 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert(len(dec_tx['vin']) > 0)
+ assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')
+
+
+ #########################################################################
+ # test a fundrawtransaction with a VIN greater than the required amount #
+ #########################################################################
+ utx = get_unspent(self.nodes[2].listunspent(), 5)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
+ outputs = { self.nodes[0].getnewaddress() : 1.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee
+
+
+ #####################################################################
+ # test a fundrawtransaction with which will not get a change output #
+ #####################################################################
+ utx = get_unspent(self.nodes[2].listunspent(), 5)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
+ outputs = { self.nodes[0].getnewaddress() : Decimal(5.0) - fee - feeTolerance }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert_equal(rawtxfund['changepos'], -1)
+ assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee
+
+
+ ####################################################
+ # test a fundrawtransaction with an invalid option #
+ ####################################################
+ utx = get_unspent(self.nodes[2].listunspent(), 5)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
+ outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ try:
+ self.nodes[2].fundrawtransaction(rawtx, {'foo': 'bar'})
+ raise AssertionError("Accepted invalid option foo")
+ except JSONRPCException as e:
+ assert("Unexpected key foo" in e.error['message'])
+
+
+ ############################################################
+ # test a fundrawtransaction with an invalid change address #
+ ############################################################
+ utx = get_unspent(self.nodes[2].listunspent(), 5)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
+ outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ try:
+ self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': 'foobar'})
+ raise AssertionError("Accepted invalid bitcoin address")
+ except JSONRPCException as e:
+ assert("changeAddress must be a valid bitcoin address" in e.error['message'])
+
+
+ ############################################################
+ # test a fundrawtransaction with a provided change address #
+ ############################################################
+ utx = get_unspent(self.nodes[2].listunspent(), 5)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ]
+ outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ change = self.nodes[2].getnewaddress()
+ try:
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': change, 'changePosition': 2})
+ except JSONRPCException as e:
+ assert('changePosition out of bounds' == e.error['message'])
+ else:
+ assert(False)
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': change, 'changePosition': 0})
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ out = dec_tx['vout'][0];
+ assert_equal(change, out['scriptPubKey']['addresses'][0])
+
+
+ #########################################################################
+ # test a fundrawtransaction with a VIN smaller than the required amount #
+ #########################################################################
+ utx = get_unspent(self.nodes[2].listunspent(), 1)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
+ outputs = { self.nodes[0].getnewaddress() : 1.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+
+ # 4-byte version + 1-byte vin count + 36-byte prevout then script_len
+ rawtx = rawtx[:82] + "0100" + rawtx[84:]
+
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+ assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ matchingOuts = 0
+ for i, out in enumerate(dec_tx['vout']):
+ totalOut += out['value']
+ if out['scriptPubKey']['addresses'][0] in outputs:
+ matchingOuts+=1
+ else:
+ assert_equal(i, rawtxfund['changepos'])
+
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+ assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex'])
+
+ assert_equal(matchingOuts, 1)
+ assert_equal(len(dec_tx['vout']), 2)
+
+
+ ###########################################
+ # test a fundrawtransaction with two VINs #
+ ###########################################
+ utx = get_unspent(self.nodes[2].listunspent(), 1)
+ utx2 = get_unspent(self.nodes[2].listunspent(), 5)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']},{'txid' : utx2['txid'], 'vout' : utx2['vout']} ]
+ outputs = { self.nodes[0].getnewaddress() : 6.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ matchingOuts = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+ if out['scriptPubKey']['addresses'][0] in outputs:
+ matchingOuts+=1
+
+ assert_equal(matchingOuts, 1)
+ assert_equal(len(dec_tx['vout']), 2)
+
+ matchingIns = 0
+ for vinOut in dec_tx['vin']:
+ for vinIn in inputs:
+ if vinIn['txid'] == vinOut['txid']:
+ matchingIns+=1
+
+ assert_equal(matchingIns, 2) #we now must see two vins identical to vins given as params
+
+ #########################################################
+ # test a fundrawtransaction with two VINs and two vOUTs #
+ #########################################################
+ utx = get_unspent(self.nodes[2].listunspent(), 1)
+ utx2 = get_unspent(self.nodes[2].listunspent(), 5)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']},{'txid' : utx2['txid'], 'vout' : utx2['vout']} ]
+ outputs = { self.nodes[0].getnewaddress() : 6.0, self.nodes[0].getnewaddress() : 1.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ matchingOuts = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+ if out['scriptPubKey']['addresses'][0] in outputs:
+ matchingOuts+=1
+
+ assert_equal(matchingOuts, 2)
+ assert_equal(len(dec_tx['vout']), 3)
+
+ ##############################################
+ # test a fundrawtransaction with invalid vin #
+ ##############################################
+ listunspent = self.nodes[2].listunspent()
+ inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin!
+ outputs = { self.nodes[0].getnewaddress() : 1.0}
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ try:
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ raise AssertionError("Spent more than available")
+ except JSONRPCException as e:
+ assert("Insufficient" in e.error['message'])
+
+
+ ############################################################
+ #compare fee of a standard pubkeyhash transaction
+ inputs = []
+ outputs = {self.nodes[1].getnewaddress():1.1}
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[0].fundrawtransaction(rawTx)
+
+ #create same transaction over sendtoaddress
+ txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1)
+ signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance)
+ ############################################################
+
+ ############################################################
+ #compare fee of a standard pubkeyhash transaction with multiple outputs
+ inputs = []
+ outputs = {self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():0.1,self.nodes[1].getnewaddress():1.3,self.nodes[1].getnewaddress():0.2,self.nodes[1].getnewaddress():0.3}
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[0].fundrawtransaction(rawTx)
+ #create same transaction over sendtoaddress
+ txId = self.nodes[0].sendmany("", outputs)
+ signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance)
+ ############################################################
+
+
+ ############################################################
+ #compare fee of a 2of2 multisig p2sh transaction
+
+ # create 2of2 addr
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[1].getnewaddress()
+
+ addr1Obj = self.nodes[1].validateaddress(addr1)
+ addr2Obj = self.nodes[1].validateaddress(addr2)
+
+ mSigObj = self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+
+ inputs = []
+ outputs = {mSigObj:1.1}
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[0].fundrawtransaction(rawTx)
+
+ #create same transaction over sendtoaddress
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.1)
+ signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance)
+ ############################################################
+
+
+ ############################################################
+ #compare fee of a standard pubkeyhash transaction
+
+ # create 4of5 addr
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[1].getnewaddress()
+ addr3 = self.nodes[1].getnewaddress()
+ addr4 = self.nodes[1].getnewaddress()
+ addr5 = self.nodes[1].getnewaddress()
+
+ addr1Obj = self.nodes[1].validateaddress(addr1)
+ addr2Obj = self.nodes[1].validateaddress(addr2)
+ addr3Obj = self.nodes[1].validateaddress(addr3)
+ addr4Obj = self.nodes[1].validateaddress(addr4)
+ addr5Obj = self.nodes[1].validateaddress(addr5)
+
+ mSigObj = self.nodes[1].addmultisigaddress(4, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey'], addr4Obj['pubkey'], addr5Obj['pubkey']])
+
+ inputs = []
+ outputs = {mSigObj:1.1}
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[0].fundrawtransaction(rawTx)
+
+ #create same transaction over sendtoaddress
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.1)
+ signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance)
+ ############################################################
+
+
+ ############################################################
+ # spend a 2of2 multisig transaction over fundraw
+
+ # create 2of2 addr
+ addr1 = self.nodes[2].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[2].validateaddress(addr1)
+ addr2Obj = self.nodes[2].validateaddress(addr2)
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+
+
+ # send 1.2 BTC to msig addr
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.2)
+ self.sync_all()
+ self.nodes[1].generate(1)
+ self.sync_all()
+
+ oldBalance = self.nodes[1].getbalance()
+ inputs = []
+ outputs = {self.nodes[1].getnewaddress():1.1}
+ rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[2].fundrawtransaction(rawTx)
+
+ signedTx = self.nodes[2].signrawtransaction(fundedTx['hex'])
+ txId = self.nodes[2].sendrawtransaction(signedTx['hex'])
+ self.sync_all()
+ self.nodes[1].generate(1)
+ self.sync_all()
+
+ # make sure funds are received at node1
+ assert_equal(oldBalance+Decimal('1.10000000'), self.nodes[1].getbalance())
+
+ ############################################################
+ # locked wallet test
+ self.nodes[1].encryptwallet("test")
+ self.nodes.pop(1)
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+ # This test is not meant to test fee estimation and we'd like
+ # to be sure all txs are sent at a consistent desired feerate
+ for node in self.nodes:
+ node.settxfee(min_relay_tx_fee)
+
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+ connect_nodes_bi(self.nodes,0,3)
+ self.is_network_split=False
+ self.sync_all()
+
+ try:
+ self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.2)
+ raise AssertionError("Wallet unlocked without passphrase")
+ except JSONRPCException as e:
+ assert('walletpassphrase' in e.error['message'])
+
+ oldBalance = self.nodes[0].getbalance()
+
+ inputs = []
+ outputs = {self.nodes[0].getnewaddress():1.1}
+ rawTx = self.nodes[1].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[1].fundrawtransaction(rawTx)
+
+ #now we need to unlock
+ self.nodes[1].walletpassphrase("test", 100)
+ signedTx = self.nodes[1].signrawtransaction(fundedTx['hex'])
+ txId = self.nodes[1].sendrawtransaction(signedTx['hex'])
+ self.nodes[1].generate(1)
+ self.sync_all()
+
+ # make sure funds are received at node1
+ assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance())
+
+
+ ###############################################
+ # multiple (~19) inputs tx test | Compare fee #
+ ###############################################
+
+ #empty node1, send some small coins from node0 to node1
+ self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ for i in range(0,20):
+ self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ #fund a tx with ~20 small inputs
+ inputs = []
+ outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04}
+ rawTx = self.nodes[1].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[1].fundrawtransaction(rawTx)
+
+ #create same transaction over sendtoaddress
+ txId = self.nodes[1].sendmany("", outputs)
+ signedFee = self.nodes[1].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance*19) #~19 inputs
+
+
+ #############################################
+ # multiple (~19) inputs tx test | sign/send #
+ #############################################
+
+ #again, empty node1, send some small coins from node0 to node1
+ self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ for i in range(0,20):
+ self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ #fund a tx with ~20 small inputs
+ oldBalance = self.nodes[0].getbalance()
+
+ inputs = []
+ outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04}
+ rawTx = self.nodes[1].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[1].fundrawtransaction(rawTx)
+ fundedAndSignedTx = self.nodes[1].signrawtransaction(fundedTx['hex'])
+ txId = self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward
+
+ #####################################################
+ # test fundrawtransaction with OP_RETURN and no vin #
+ #####################################################
+
+ rawtx = "0100000000010000000000000000066a047465737400000000"
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ assert_equal(len(dec_tx['vin']), 0)
+ assert_equal(len(dec_tx['vout']), 1)
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+
+ assert_greater_than(len(dec_tx['vin']), 0) # at least one vin
+ assert_equal(len(dec_tx['vout']), 2) # one change output added
+
+
+ ##################################################
+ # test a fundrawtransaction using only watchonly #
+ ##################################################
+
+ inputs = []
+ outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2}
+ rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
+
+ result = self.nodes[3].fundrawtransaction(rawtx, {'includeWatching': True })
+ res_dec = self.nodes[0].decoderawtransaction(result["hex"])
+ assert_equal(len(res_dec["vin"]), 1)
+ assert_equal(res_dec["vin"][0]["txid"], watchonly_txid)
+
+ assert("fee" in result.keys())
+ assert_greater_than(result["changepos"], -1)
+
+ ###############################################################
+ # test fundrawtransaction using the entirety of watched funds #
+ ###############################################################
+
+ inputs = []
+ outputs = {self.nodes[2].getnewaddress() : watchonly_amount}
+ rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
+
+ # Backward compatibility test (2nd param is includeWatching)
+ result = self.nodes[3].fundrawtransaction(rawtx, True)
+ res_dec = self.nodes[0].decoderawtransaction(result["hex"])
+ assert_equal(len(res_dec["vin"]), 2)
+ assert(res_dec["vin"][0]["txid"] == watchonly_txid or res_dec["vin"][1]["txid"] == watchonly_txid)
+
+ assert_greater_than(result["fee"], 0)
+ assert_greater_than(result["changepos"], -1)
+ assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], watchonly_amount / 10)
+
+ signedtx = self.nodes[3].signrawtransaction(result["hex"])
+ assert(not signedtx["complete"])
+ signedtx = self.nodes[0].signrawtransaction(signedtx["hex"])
+ assert(signedtx["complete"])
+ self.nodes[0].sendrawtransaction(signedtx["hex"])
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ #######################
+ # Test feeRate option #
+ #######################
+
+ # Make sure there is exactly one input so coin selection can't skew the result
+ assert_equal(len(self.nodes[3].listunspent(1)), 1)
+
+ inputs = []
+ outputs = {self.nodes[2].getnewaddress() : 1}
+ rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
+ result = self.nodes[3].fundrawtransaction(rawtx) # uses min_relay_tx_fee (set by settxfee)
+ result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee})
+ result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10*min_relay_tx_fee})
+ result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
+ assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
+ assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
+
+if __name__ == '__main__':
+ RawTransactionsTest().main()
diff --git a/qa/rpc-tests/getblocktemplate_longpoll.py b/qa/rpc-tests/getblocktemplate_longpoll.py
index b749b260b7..3cddf4046a 100755
--- a/qa/rpc-tests/getblocktemplate_longpoll.py
+++ b/qa/rpc-tests/getblocktemplate_longpoll.py
@@ -1,33 +1,10 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-from test_framework import BitcoinTestFramework
-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)))
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
import threading
@@ -39,7 +16,7 @@ class LongpollThread(threading.Thread):
self.longpollid = templat['longpollid']
# create a new connection to the node, we can't use the same
# connection from two threads
- self.node = AuthServiceProxy(node.url, timeout=600)
+ self.node = get_rpc_proxy(node.url, 1, timeout=600)
def run(self):
self.node.getblocktemplate({'longpollid':self.longpollid})
@@ -49,9 +26,14 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
Test longpolling with getblocktemplate.
'''
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.setup_clean_chain = False
+
def run_test(self):
- print "Warning: this test will take about 70 seconds in the best case. Be patient."
- self.nodes[0].setgenerate(True, 10)
+ print("Warning: this test will take about 70 seconds in the best case. Be patient.")
+ self.nodes[0].generate(10)
templat = self.nodes[0].getblocktemplate()
longpollid = templat['longpollid']
# longpollid should not change between successive invocations if nothing else happens
@@ -66,7 +48,7 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
assert(thr.is_alive())
# Test 2: test that longpoll will terminate if another node generates a block
- self.nodes[1].setgenerate(True, 1) # generate a block on another node
+ self.nodes[1].generate(1) # generate a block on another node
# check that thread will exit now that new transaction entered mempool
thr.join(5) # wait 5 seconds or until thread exits
assert(not thr.is_alive())
@@ -74,7 +56,7 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
# Test 3: test that longpoll will terminate if we generate a block ourselves
thr = LongpollThread(self.nodes[0])
thr.start()
- self.nodes[0].setgenerate(True, 1) # generate a block on another node
+ self.nodes[0].generate(1) # generate a block on another node
thr.join(5) # wait 5 seconds or until thread exits
assert(not thr.is_alive())
diff --git a/qa/rpc-tests/getblocktemplate_proposals.py b/qa/rpc-tests/getblocktemplate_proposals.py
index 22fe2ce044..7a4f8f8fdc 100755
--- a/qa/rpc-tests/getblocktemplate_proposals.py
+++ b/qa/rpc-tests/getblocktemplate_proposals.py
@@ -1,38 +1,15 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-from test_framework import BitcoinTestFramework
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
from binascii import a2b_hex, b2a_hex
from hashlib import sha256
from struct import pack
-
-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 b2x(b):
return b2a_hex(b).decode('ascii')
@@ -69,7 +46,7 @@ def genmrklroot(leaflist):
cur = n
return cur[0]
-def template_to_bytes(tmpl, txlist):
+def template_to_bytearray(tmpl, txlist):
blkver = pack('<L', tmpl['version'])
mrklroot = genmrklroot(list(dblsha(a) for a in txlist))
timestamp = pack('<L', tmpl['curtime'])
@@ -78,10 +55,10 @@ def template_to_bytes(tmpl, txlist):
blk += varlenEncode(len(txlist))
for tx in txlist:
blk += tx
- return blk
+ return bytearray(blk)
def template_to_hex(tmpl, txlist):
- return b2x(template_to_bytes(tmpl, txlist))
+ return b2x(template_to_bytearray(tmpl, txlist))
def assert_template(node, tmpl, txlist, expect):
rsp = node.getblocktemplate({'data':template_to_hex(tmpl, txlist),'mode':'proposal'})
@@ -93,8 +70,18 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
Test block proposals with getblocktemplate.
'''
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ self.nodes = self.setup_nodes()
+ connect_nodes_bi(self.nodes, 0, 1)
+
def run_test(self):
node = self.nodes[0]
+ node.generate(1) # Mine a block to leave initial block download
tmpl = node.getblocktemplate()
if 'coinbasetxn' not in tmpl:
rawcoinbase = encodeUNum(tmpl['height'])
@@ -120,10 +107,7 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
# Test 3: Truncated final tx
lastbyte = txlist[-1].pop()
- try:
- assert_template(node, tmpl, txlist, 'n/a')
- except JSONRPCException:
- pass # Expected
+ assert_raises(JSONRPCException, assert_template, node, tmpl, txlist, 'n/a')
txlist[-1].append(lastbyte)
# Test 4: Add an invalid tx to the end (duplicate of gen tx)
@@ -133,7 +117,7 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
# Test 5: Add an invalid tx to the end (non-duplicate)
txlist.append(bytearray(txlist[0]))
- txlist[-1][4+1] = b'\xff'
+ txlist[-1][4+1] = 0xff
assert_template(node, tmpl, txlist, 'bad-txns-inputs-missingorspent')
txlist.pop()
@@ -144,10 +128,7 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
# Test 7: Bad tx count
txlist.append(b'')
- try:
- assert_template(node, tmpl, txlist, 'n/a')
- except JSONRPCException:
- pass # Expected
+ assert_raises(JSONRPCException, assert_template, node, tmpl, txlist, 'n/a')
txlist.pop()
# Test 8: Bad bits
@@ -157,7 +138,7 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
tmpl['bits'] = realbits
# Test 9: Bad merkle root
- rawtmpl = template_to_bytes(tmpl, txlist)
+ rawtmpl = template_to_bytearray(tmpl, txlist)
rawtmpl[4+32] = (rawtmpl[4+32] + 1) % 0x100
rsp = node.getblocktemplate({'data':b2x(rawtmpl),'mode':'proposal'})
if rsp != 'bad-txnmrklroot':
diff --git a/qa/rpc-tests/getchaintips.py b/qa/rpc-tests/getchaintips.py
index 84fe102d81..1c66b8c289 100755
--- a/qa/rpc-tests/getchaintips.py
+++ b/qa/rpc-tests/getchaintips.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -7,13 +7,16 @@
# on chains of different lengths, and join the network together again.
# This gives us two tips, verify that it works.
-from test_framework import BitcoinTestFramework
-from util import assert_equal
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
class GetChainTipsTest (BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.setup_clean_chain = False
def run_test (self):
- BitcoinTestFramework.run_test (self)
tips = self.nodes[0].getchaintips ()
assert_equal (len (tips), 1)
@@ -23,8 +26,8 @@ class GetChainTipsTest (BitcoinTestFramework):
# Split the network and build two chains of different lengths.
self.split_network ()
- self.nodes[0].setgenerate (True, 10);
- self.nodes[2].setgenerate (True, 20);
+ self.nodes[0].generate(10)
+ self.nodes[2].generate(20)
self.sync_all ()
tips = self.nodes[1].getchaintips ()
diff --git a/qa/rpc-tests/httpbasics.py b/qa/rpc-tests/httpbasics.py
index a94edaffa5..10bc927e1a 100755
--- a/qa/rpc-tests/httpbasics.py
+++ b/qa/rpc-tests/httpbasics.py
@@ -1,76 +1,113 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
-# Test REST interface
+# Test rpc http basics
#
-from test_framework import BitcoinTestFramework
-from util import *
-import base64
-
-try:
- import http.client as httplib
-except ImportError:
- import httplib
-try:
- import urllib.parse as urlparse
-except ImportError:
- import urlparse
-
-class RESTTest (BitcoinTestFramework):
- def run_test(self):
-
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+import http.client
+import urllib.parse
+
+class HTTPBasicsTest (BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 3
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ self.nodes = self.setup_nodes()
+
+ def run_test(self):
+
#################################################
# lowlevel check for http persistent connection #
#################################################
- url = urlparse.urlparse(self.nodes[0].url)
+ url = urllib.parse.urlparse(self.nodes[0].url)
authpair = url.username + ':' + url.password
- headers = {"Authorization": "Basic " + base64.b64encode(authpair)}
-
- conn = httplib.HTTPConnection(url.hostname, url.port)
+ headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
- conn.request('GET', '/', '{"method": "getbestblockhash"}', headers)
- out1 = conn.getresponse().read();
- assert_equal('"error":null' in out1, True)
- assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
-
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ out1 = conn.getresponse().read()
+ assert(b'"error":null' in out1)
+ assert(conn.sock!=None) #according to http/1.1 connection must still be open!
+
#send 2nd request without closing connection
- conn.request('GET', '/', '{"method": "getchaintips"}', headers)
- out2 = conn.getresponse().read();
- assert_equal('"error":null' in out1, True) #must also response with a correct json-rpc message
- assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
+ conn.request('POST', '/', '{"method": "getchaintips"}', headers)
+ out1 = conn.getresponse().read()
+ assert(b'"error":null' in out1) #must also response with a correct json-rpc message
+ assert(conn.sock!=None) #according to http/1.1 connection must still be open!
conn.close()
-
+
#same should be if we add keep-alive because this should be the std. behaviour
- headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection": "keep-alive"}
-
- conn = httplib.HTTPConnection(url.hostname, url.port)
+ headers = {"Authorization": "Basic " + str_to_b64str(authpair), "Connection": "keep-alive"}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
- conn.request('GET', '/', '{"method": "getbestblockhash"}', headers)
- out1 = conn.getresponse().read();
- assert_equal('"error":null' in out1, True)
- assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
-
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ out1 = conn.getresponse().read()
+ assert(b'"error":null' in out1)
+ assert(conn.sock!=None) #according to http/1.1 connection must still be open!
+
#send 2nd request without closing connection
- conn.request('GET', '/', '{"method": "getchaintips"}', headers)
- out2 = conn.getresponse().read();
- assert_equal('"error":null' in out1, True) #must also response with a correct json-rpc message
- assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
+ conn.request('POST', '/', '{"method": "getchaintips"}', headers)
+ out1 = conn.getresponse().read()
+ assert(b'"error":null' in out1) #must also response with a correct json-rpc message
+ assert(conn.sock!=None) #according to http/1.1 connection must still be open!
conn.close()
-
+
#now do the same with "Connection: close"
- headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection":"close"}
-
- conn = httplib.HTTPConnection(url.hostname, url.port)
+ headers = {"Authorization": "Basic " + str_to_b64str(authpair), "Connection":"close"}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
- conn.request('GET', '/', '{"method": "getbestblockhash"}', headers)
- out1 = conn.getresponse().read();
- assert_equal('"error":null' in out1, True)
- assert_equal(conn.sock!=None, False) #now the connection must be closed after the response
-
-
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ out1 = conn.getresponse().read()
+ assert(b'"error":null' in out1)
+ assert(conn.sock==None) #now the connection must be closed after the response
+
+ #node1 (2nd node) is running with disabled keep-alive option
+ urlNode1 = urllib.parse.urlparse(self.nodes[1].url)
+ authpair = urlNode1.username + ':' + urlNode1.password
+ headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+
+ conn = http.client.HTTPConnection(urlNode1.hostname, urlNode1.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ out1 = conn.getresponse().read()
+ assert(b'"error":null' in out1)
+
+ #node2 (third node) is running with standard keep-alive parameters which means keep-alive is on
+ urlNode2 = urllib.parse.urlparse(self.nodes[2].url)
+ authpair = urlNode2.username + ':' + urlNode2.password
+ headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+
+ conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ out1 = conn.getresponse().read()
+ assert(b'"error":null' in out1)
+ assert(conn.sock!=None) #connection must be closed because bitcoind should use keep-alive by default
+
+ # Check excessive request size
+ conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
+ conn.connect()
+ conn.request('GET', '/' + ('x'*1000), '', headers)
+ out1 = conn.getresponse()
+ assert_equal(out1.status, http.client.NOT_FOUND)
+
+ conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
+ conn.connect()
+ conn.request('GET', '/' + ('x'*10000), '', headers)
+ out1 = conn.getresponse()
+ assert_equal(out1.status, http.client.BAD_REQUEST)
+
+
if __name__ == '__main__':
- RESTTest ().main ()
+ HTTPBasicsTest ().main ()
diff --git a/qa/rpc-tests/importprunedfunds.py b/qa/rpc-tests/importprunedfunds.py
new file mode 100755
index 0000000000..d86f51b7f3
--- /dev/null
+++ b/qa/rpc-tests/importprunedfunds.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import decimal
+
+class ImportPrunedFundsTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+ connect_nodes_bi(self.nodes,0,1)
+ self.is_network_split=False
+ self.sync_all()
+
+ def run_test (self):
+ import time
+ begintime = int(time.time())
+
+ print("Mining blocks...")
+ self.nodes[0].generate(101)
+
+ # sync
+ self.sync_all()
+
+ # address
+ address1 = self.nodes[0].getnewaddress()
+ # pubkey
+ address2 = self.nodes[0].getnewaddress()
+ address2_pubkey = self.nodes[0].validateaddress(address2)['pubkey'] # Using pubkey
+ # privkey
+ address3 = self.nodes[0].getnewaddress()
+ address3_privkey = self.nodes[0].dumpprivkey(address3) # Using privkey
+
+ #Check only one address
+ address_info = self.nodes[0].validateaddress(address1)
+ assert_equal(address_info['ismine'], True)
+
+ self.sync_all()
+
+ #Node 1 sync test
+ assert_equal(self.nodes[1].getblockcount(),101)
+
+ #Address Test - before import
+ address_info = self.nodes[1].validateaddress(address1)
+ assert_equal(address_info['iswatchonly'], False)
+ assert_equal(address_info['ismine'], False)
+
+ address_info = self.nodes[1].validateaddress(address2)
+ assert_equal(address_info['iswatchonly'], False)
+ assert_equal(address_info['ismine'], False)
+
+ address_info = self.nodes[1].validateaddress(address3)
+ assert_equal(address_info['iswatchonly'], False)
+ assert_equal(address_info['ismine'], False)
+
+ #Send funds to self
+ txnid1 = self.nodes[0].sendtoaddress(address1, 0.1)
+ self.nodes[0].generate(1)
+ rawtxn1 = self.nodes[0].gettransaction(txnid1)['hex']
+ proof1 = self.nodes[0].gettxoutproof([txnid1])
+
+ txnid2 = self.nodes[0].sendtoaddress(address2, 0.05)
+ self.nodes[0].generate(1)
+ rawtxn2 = self.nodes[0].gettransaction(txnid2)['hex']
+ proof2 = self.nodes[0].gettxoutproof([txnid2])
+
+
+ txnid3 = self.nodes[0].sendtoaddress(address3, 0.025)
+ self.nodes[0].generate(1)
+ rawtxn3 = self.nodes[0].gettransaction(txnid3)['hex']
+ proof3 = self.nodes[0].gettxoutproof([txnid3])
+
+ self.sync_all()
+
+ #Import with no affiliated address
+ try:
+ result1 = self.nodes[1].importprunedfunds(rawtxn1, proof1, "")
+ except JSONRPCException as e:
+ assert('No addresses' in e.error['message'])
+ else:
+ assert(False)
+
+
+ balance1 = self.nodes[1].getbalance("", 0, True)
+ assert_equal(balance1, Decimal(0))
+
+ #Import with affiliated address with no rescan
+ self.nodes[1].importaddress(address2, "", False)
+ result2 = self.nodes[1].importprunedfunds(rawtxn2, proof2, "")
+ balance2 = Decimal(self.nodes[1].getbalance("", 0, True))
+ assert_equal(balance2, Decimal('0.05'))
+
+ #Import with private key with no rescan
+ self.nodes[1].importprivkey(address3_privkey, "", False)
+ result3 = self.nodes[1].importprunedfunds(rawtxn3, proof3, "")
+ balance3 = Decimal(self.nodes[1].getbalance("", 0, False))
+ assert_equal(balance3, Decimal('0.025'))
+ balance3 = Decimal(self.nodes[1].getbalance("", 0, True))
+ assert_equal(balance3, Decimal('0.075'))
+
+ #Addresses Test - after import
+ address_info = self.nodes[1].validateaddress(address1)
+ assert_equal(address_info['iswatchonly'], False)
+ assert_equal(address_info['ismine'], False)
+ address_info = self.nodes[1].validateaddress(address2)
+ assert_equal(address_info['iswatchonly'], True)
+ assert_equal(address_info['ismine'], False)
+ address_info = self.nodes[1].validateaddress(address3)
+ assert_equal(address_info['iswatchonly'], False)
+ assert_equal(address_info['ismine'], True)
+
+ #Remove transactions
+
+ try:
+ self.nodes[1].removeprunedfunds(txnid1)
+ except JSONRPCException as e:
+ assert('does not exist' in e.error['message'])
+ else:
+ assert(False)
+
+
+ balance1 = Decimal(self.nodes[1].getbalance("", 0, True))
+ assert_equal(balance1, Decimal('0.075'))
+
+
+ self.nodes[1].removeprunedfunds(txnid2)
+ balance2 = Decimal(self.nodes[1].getbalance("", 0, True))
+ assert_equal(balance2, Decimal('0.025'))
+
+ self.nodes[1].removeprunedfunds(txnid3)
+ balance3 = Decimal(self.nodes[1].getbalance("", 0, True))
+ assert_equal(balance3, Decimal('0.0'))
+
+if __name__ == '__main__':
+ ImportPrunedFundsTest ().main ()
diff --git a/qa/rpc-tests/invalidateblock.py b/qa/rpc-tests/invalidateblock.py
new file mode 100755
index 0000000000..0faadd33ab
--- /dev/null
+++ b/qa/rpc-tests/invalidateblock.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test InvalidateBlock code
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+class InvalidateTest(BitcoinTestFramework):
+
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 3
+
+ 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"]))
+ self.nodes.append(start_node(2, self.options.tmpdir, ["-debug"]))
+
+ def run_test(self):
+ print("Make sure we repopulate setBlockIndexCandidates after InvalidateBlock:")
+ print("Mine 4 blocks on Node 0")
+ self.nodes[0].generate(4)
+ assert(self.nodes[0].getblockcount() == 4)
+ besthash = self.nodes[0].getbestblockhash()
+
+ print("Mine competing 6 blocks on Node 1")
+ self.nodes[1].generate(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[0:2])
+ 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))
+
+ print("\nMake sure we won't reorg to a lower work chain:")
+ connect_nodes_bi(self.nodes,1,2)
+ print("Sync node 2 to node 1 so both have 6 blocks")
+ sync_blocks(self.nodes[1:3])
+ assert(self.nodes[2].getblockcount() == 6)
+ print("Invalidate block 5 on node 1 so its tip is now at 4")
+ self.nodes[1].invalidateblock(self.nodes[1].getblockhash(5))
+ assert(self.nodes[1].getblockcount() == 4)
+ print("Invalidate block 3 on node 2, so its tip is now 2")
+ self.nodes[2].invalidateblock(self.nodes[2].getblockhash(3))
+ assert(self.nodes[2].getblockcount() == 2)
+ print("..and then mine a block")
+ self.nodes[2].generate(1)
+ print("Verify all nodes are at the right height")
+ time.sleep(5)
+ for i in range(3):
+ print(i,self.nodes[i].getblockcount())
+ assert(self.nodes[2].getblockcount() == 3)
+ assert(self.nodes[0].getblockcount() == 4)
+ node1height = self.nodes[1].getblockcount()
+ if node1height < 4:
+ raise AssertionError("Node 1 reorged to a lower height: %d"%node1height)
+
+if __name__ == '__main__':
+ InvalidateTest().main()
diff --git a/qa/rpc-tests/invalidblockrequest.py b/qa/rpc-tests/invalidblockrequest.py
new file mode 100755
index 0000000000..3d8107a76c
--- /dev/null
+++ b/qa/rpc-tests/invalidblockrequest.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.util import *
+from test_framework.comptool import TestManager, TestInstance, RejectResult
+from test_framework.blocktools import *
+import copy
+import time
+
+
+'''
+In this test we connect to one node over p2p, and test block requests:
+1) Valid blocks should be requested and become chain tip.
+2) Invalid block with duplicated transaction should be re-requested.
+3) Invalid block with bad coinbase value should be rejected and not
+re-requested.
+'''
+
+# Use the ComparisonTestFramework with 1 node: only use --testbinary.
+class InvalidBlockRequestTest(ComparisonTestFramework):
+
+ ''' Can either run this test as 1 node with expected answers, or two and compare them.
+ Change the "outcome" variable from each TestInstance object to only do the comparison. '''
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+
+ def run_test(self):
+ test = TestManager(self, self.options.tmpdir)
+ test.add_all_connections(self.nodes)
+ self.tip = None
+ self.block_time = None
+ NetworkThread().start() # Start up network handling in another thread
+ test.run()
+
+ def get_tests(self):
+ if self.tip is None:
+ self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0)
+ self.block_time = int(time.time())+1
+
+ '''
+ Create a new block with an anyone-can-spend coinbase
+ '''
+ height = 1
+ block = create_block(self.tip, create_coinbase(height), self.block_time)
+ self.block_time += 1
+ block.solve()
+ # Save the coinbase for later
+ self.block1 = block
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance([[block, True]])
+
+ '''
+ Now we need that block to mature so we can spend the coinbase.
+ '''
+ test = TestInstance(sync_every_block=False)
+ for i in range(100):
+ block = create_block(self.tip, create_coinbase(height), self.block_time)
+ block.solve()
+ self.tip = block.sha256
+ self.block_time += 1
+ test.blocks_and_transactions.append([block, True])
+ height += 1
+ yield test
+
+ '''
+ Now we use merkle-root malleability to generate an invalid block with
+ same blockheader.
+ Manufacture a block with 3 transactions (coinbase, spend of prior
+ coinbase, spend of that spend). Duplicate the 3rd transaction to
+ leave merkle root and blockheader unchanged but invalidate the block.
+ '''
+ block2 = create_block(self.tip, create_coinbase(height), self.block_time)
+ self.block_time += 1
+
+ # b'0x51' is OP_TRUE
+ tx1 = create_transaction(self.block1.vtx[0], 0, b'\x51', 50 * COIN)
+ tx2 = create_transaction(tx1, 0, b'\x51', 50 * COIN)
+
+ block2.vtx.extend([tx1, tx2])
+ block2.hashMerkleRoot = block2.calc_merkle_root()
+ block2.rehash()
+ block2.solve()
+ orig_hash = block2.sha256
+ block2_orig = copy.deepcopy(block2)
+
+ # Mutate block 2
+ block2.vtx.append(tx2)
+ assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root())
+ assert_equal(orig_hash, block2.rehash())
+ assert(block2_orig.vtx != block2.vtx)
+
+ self.tip = block2.sha256
+ yield TestInstance([[block2, RejectResult(16, b'bad-txns-duplicate')], [block2_orig, True]])
+ height += 1
+
+ '''
+ Make sure that a totally screwed up block is not valid.
+ '''
+ block3 = create_block(self.tip, create_coinbase(height), self.block_time)
+ self.block_time += 1
+ block3.vtx[0].vout[0].nValue = 100 * COIN # Too high!
+ block3.vtx[0].sha256=None
+ block3.vtx[0].calc_sha256()
+ block3.hashMerkleRoot = block3.calc_merkle_root()
+ block3.rehash()
+ block3.solve()
+
+ yield TestInstance([[block3, RejectResult(16, b'bad-cb-amount')]])
+
+
+if __name__ == '__main__':
+ InvalidBlockRequestTest().main()
diff --git a/qa/rpc-tests/invalidtxrequest.py b/qa/rpc-tests/invalidtxrequest.py
new file mode 100755
index 0000000000..93205d79de
--- /dev/null
+++ b/qa/rpc-tests/invalidtxrequest.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.comptool import TestManager, TestInstance, RejectResult
+from test_framework.blocktools import *
+import time
+
+
+'''
+In this test we connect to one node over p2p, and test tx requests.
+'''
+
+# Use the ComparisonTestFramework with 1 node: only use --testbinary.
+class InvalidTxRequestTest(ComparisonTestFramework):
+
+ ''' Can either run this test as 1 node with expected answers, or two and compare them.
+ Change the "outcome" variable from each TestInstance object to only do the comparison. '''
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+
+ def run_test(self):
+ test = TestManager(self, self.options.tmpdir)
+ test.add_all_connections(self.nodes)
+ self.tip = None
+ self.block_time = None
+ NetworkThread().start() # Start up network handling in another thread
+ test.run()
+
+ def get_tests(self):
+ if self.tip is None:
+ self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0)
+ self.block_time = int(time.time())+1
+
+ '''
+ Create a new block with an anyone-can-spend coinbase
+ '''
+ height = 1
+ block = create_block(self.tip, create_coinbase(height), self.block_time)
+ self.block_time += 1
+ block.solve()
+ # Save the coinbase for later
+ self.block1 = block
+ self.tip = block.sha256
+ height += 1
+ yield TestInstance([[block, True]])
+
+ '''
+ Now we need that block to mature so we can spend the coinbase.
+ '''
+ test = TestInstance(sync_every_block=False)
+ for i in range(100):
+ block = create_block(self.tip, create_coinbase(height), self.block_time)
+ block.solve()
+ self.tip = block.sha256
+ self.block_time += 1
+ test.blocks_and_transactions.append([block, True])
+ height += 1
+ yield test
+
+ # b'\x64' is OP_NOTIF
+ # Transaction will be rejected with code 16 (REJECT_INVALID)
+ tx1 = create_transaction(self.block1.vtx[0], 0, b'\x64', 50 * COIN - 12000)
+ yield TestInstance([[tx1, RejectResult(16, b'mandatory-script-verify-flag-failed')]])
+
+ # TODO: test further transactions...
+
+if __name__ == '__main__':
+ InvalidTxRequestTest().main()
diff --git a/qa/rpc-tests/keypool.py b/qa/rpc-tests/keypool.py
index 3840ea39d3..c75303ecbf 100755
--- a/qa/rpc-tests/keypool.py
+++ b/qa/rpc-tests/keypool.py
@@ -1,132 +1,75 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Exercise the wallet keypool, and interaction with wallet encryption/locking
-# 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 test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
-import json
-import shutil
-import subprocess
-import tempfile
-import traceback
+class KeyPoolTest(BitcoinTestFramework):
-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, tmpdir):
- # Encrypt wallet and wait to terminate
- nodes[0].encryptwallet('test')
- bitcoind_processes[0].wait()
- # Restart node 0
- nodes[0] = start_node(0, tmpdir)
- # Keep creating keys
- addr = nodes[0].getnewaddress()
- try:
+ def run_test(self):
+ nodes = self.nodes
+ # Encrypt wallet and wait to terminate
+ nodes[0].encryptwallet('test')
+ bitcoind_processes[0].wait()
+ # Restart node 0
+ nodes[0] = start_node(0, self.options.tmpdir)
+ # Keep creating keys
addr = nodes[0].getnewaddress()
- raise AssertionError('Keypool should be exhausted after one address')
- except JSONRPCException,e:
- assert(e.error['code']==-12)
-
- # put three new keys in the keypool
- nodes[0].walletpassphrase('test', 12000)
- nodes[0].keypoolrefill(3)
- nodes[0].walletlock()
-
- # drain the keys
- addr = set()
- addr.add(nodes[0].getrawchangeaddress())
- addr.add(nodes[0].getrawchangeaddress())
- addr.add(nodes[0].getrawchangeaddress())
- addr.add(nodes[0].getrawchangeaddress())
- # assert that four unique addresses were returned
- assert(len(addr) == 4)
- # the next one should fail
- try:
- addr = nodes[0].getrawchangeaddress()
- raise AssertionError('Keypool should be exhausted after three addresses')
- except JSONRPCException,e:
- assert(e.error['code']==-12)
-
-
-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
- nodes = []
- 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(1, options.tmpdir)
-
- run_test(nodes, options.tmpdir)
-
- success = True
-
- except AssertionError as e:
- print("Assertion failed: "+e.message)
- except JSONRPCException as e:
- print("JSONRPC error: "+e.error['message'])
- traceback.print_tb(sys.exc_info()[2])
- except Exception as e:
- print("Unexpected exception caught during testing: "+str(sys.exc_info()[0]))
- traceback.print_tb(sys.exc_info()[2])
-
- if not options.nocleanup:
- print("Cleaning up")
- stop_nodes(nodes)
- wait_bitcoinds()
- shutil.rmtree(options.tmpdir)
-
- if success:
- print("Tests successful")
- sys.exit(0)
- else:
- print("Failed")
- sys.exit(1)
+ try:
+ addr = nodes[0].getnewaddress()
+ raise AssertionError('Keypool should be exhausted after one address')
+ except JSONRPCException as e:
+ assert(e.error['code']==-12)
+
+ # put three new keys in the keypool
+ nodes[0].walletpassphrase('test', 12000)
+ nodes[0].keypoolrefill(3)
+ nodes[0].walletlock()
+
+ # drain the keys
+ addr = set()
+ addr.add(nodes[0].getrawchangeaddress())
+ addr.add(nodes[0].getrawchangeaddress())
+ addr.add(nodes[0].getrawchangeaddress())
+ addr.add(nodes[0].getrawchangeaddress())
+ # assert that four unique addresses were returned
+ assert(len(addr) == 4)
+ # the next one should fail
+ try:
+ addr = nodes[0].getrawchangeaddress()
+ raise AssertionError('Keypool should be exhausted after three addresses')
+ except JSONRPCException as e:
+ assert(e.error['code']==-12)
+
+ # refill keypool with three new addresses
+ nodes[0].walletpassphrase('test', 1)
+ nodes[0].keypoolrefill(3)
+ # test walletpassphrase timeout
+ time.sleep(1.1)
+ assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0)
+
+ # drain them by mining
+ nodes[0].generate(1)
+ nodes[0].generate(1)
+ nodes[0].generate(1)
+ nodes[0].generate(1)
+ try:
+ nodes[0].generate(1)
+ raise AssertionError('Keypool should be exhausted after three addesses')
+ except JSONRPCException as e:
+ assert(e.error['code']==-12)
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = False
+ self.num_nodes = 1
+
+ def setup_network(self):
+ self.nodes = self.setup_nodes()
if __name__ == '__main__':
- main()
+ KeyPoolTest().main()
diff --git a/qa/rpc-tests/listtransactions.py b/qa/rpc-tests/listtransactions.py
index 8ee9d66a29..5ec6ce17e0 100755
--- a/qa/rpc-tests/listtransactions.py
+++ b/qa/rpc-tests/listtransactions.py
@@ -1,64 +1,58 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Exercise the listtransactions API
-from test_framework import BitcoinTestFramework
-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)))
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.mininode import CTransaction, COIN
+from io import BytesIO
+
+def txFromHex(hexstring):
+ tx = CTransaction()
+ f = BytesIO(hex_str_to_bytes(hexstring))
+ tx.deserialize(f)
+ return tx
class ListTransactionsTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.setup_clean_chain = False
+
+ def setup_nodes(self):
+ #This test requires mocktime
+ enable_mocktime()
+ return start_nodes(self.num_nodes, self.options.tmpdir)
def run_test(self):
# Simple send, 0 to 1:
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
self.sync_all()
- check_array_result(self.nodes[0].listtransactions(),
+ assert_array_result(self.nodes[0].listtransactions(),
{"txid":txid},
{"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":0})
- check_array_result(self.nodes[1].listtransactions(),
+ assert_array_result(self.nodes[1].listtransactions(),
{"txid":txid},
{"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":0})
# mine a block, confirmations should change:
- self.nodes[0].setgenerate(True, 1)
+ self.nodes[0].generate(1)
self.sync_all()
- check_array_result(self.nodes[0].listtransactions(),
+ assert_array_result(self.nodes[0].listtransactions(),
{"txid":txid},
{"category":"send","account":"","amount":Decimal("-0.1"),"confirmations":1})
- check_array_result(self.nodes[1].listtransactions(),
+ assert_array_result(self.nodes[1].listtransactions(),
{"txid":txid},
{"category":"receive","account":"","amount":Decimal("0.1"),"confirmations":1})
# send-to-self:
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2)
- check_array_result(self.nodes[0].listtransactions(),
+ assert_array_result(self.nodes[0].listtransactions(),
{"txid":txid, "category":"send"},
{"amount":Decimal("-0.2")})
- check_array_result(self.nodes[0].listtransactions(),
+ assert_array_result(self.nodes[0].listtransactions(),
{"txid":txid, "category":"receive"},
{"amount":Decimal("0.2")})
@@ -69,31 +63,142 @@ class ListTransactionsTest(BitcoinTestFramework):
self.nodes[1].getaccountaddress("toself") : 0.44 }
txid = self.nodes[1].sendmany("", send_to)
self.sync_all()
- check_array_result(self.nodes[1].listtransactions(),
+ assert_array_result(self.nodes[1].listtransactions(),
{"category":"send","amount":Decimal("-0.11")},
{"txid":txid} )
- check_array_result(self.nodes[0].listtransactions(),
+ assert_array_result(self.nodes[0].listtransactions(),
{"category":"receive","amount":Decimal("0.11")},
{"txid":txid} )
- check_array_result(self.nodes[1].listtransactions(),
+ assert_array_result(self.nodes[1].listtransactions(),
{"category":"send","amount":Decimal("-0.22")},
{"txid":txid} )
- check_array_result(self.nodes[1].listtransactions(),
+ assert_array_result(self.nodes[1].listtransactions(),
{"category":"receive","amount":Decimal("0.22")},
{"txid":txid} )
- check_array_result(self.nodes[1].listtransactions(),
+ assert_array_result(self.nodes[1].listtransactions(),
{"category":"send","amount":Decimal("-0.33")},
{"txid":txid} )
- check_array_result(self.nodes[0].listtransactions(),
+ assert_array_result(self.nodes[0].listtransactions(),
{"category":"receive","amount":Decimal("0.33")},
{"txid":txid, "account" : "from1"} )
- check_array_result(self.nodes[1].listtransactions(),
+ assert_array_result(self.nodes[1].listtransactions(),
{"category":"send","amount":Decimal("-0.44")},
{"txid":txid, "account" : ""} )
- check_array_result(self.nodes[1].listtransactions(),
+ assert_array_result(self.nodes[1].listtransactions(),
{"category":"receive","amount":Decimal("0.44")},
{"txid":txid, "account" : "toself"} )
+ multisig = self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()])
+ self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True)
+ txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
+ self.nodes[1].generate(1)
+ self.sync_all()
+ assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0)
+ assert_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True),
+ {"category":"receive","amount":Decimal("0.1")},
+ {"txid":txid, "account" : "watchonly"} )
+
+ self.run_rbf_opt_in_test()
+
+ # Check that the opt-in-rbf flag works properly, for sent and received
+ # transactions.
+ def run_rbf_opt_in_test(self):
+ # Check whether a transaction signals opt-in RBF itself
+ def is_opt_in(node, txid):
+ rawtx = node.getrawtransaction(txid, 1)
+ for x in rawtx["vin"]:
+ if x["sequence"] < 0xfffffffe:
+ return True
+ return False
+
+ # Find an unconfirmed output matching a certain txid
+ def get_unconfirmed_utxo_entry(node, txid_to_match):
+ utxo = node.listunspent(0, 0)
+ for i in utxo:
+ if i["txid"] == txid_to_match:
+ return i
+ return None
+
+ # 1. Chain a few transactions that don't opt-in.
+ txid_1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
+ assert(not is_opt_in(self.nodes[0], txid_1))
+ assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"})
+ sync_mempools(self.nodes)
+ assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable":"no"})
+
+ # Tx2 will build off txid_1, still not opting in to RBF.
+ utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1)
+
+ # Create tx2 using createrawtransaction
+ inputs = [{"txid":utxo_to_use["txid"], "vout":utxo_to_use["vout"]}]
+ outputs = {self.nodes[0].getnewaddress(): 0.999}
+ tx2 = self.nodes[1].createrawtransaction(inputs, outputs)
+ tx2_signed = self.nodes[1].signrawtransaction(tx2)["hex"]
+ txid_2 = self.nodes[1].sendrawtransaction(tx2_signed)
+
+ # ...and check the result
+ assert(not is_opt_in(self.nodes[1], txid_2))
+ assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"})
+ sync_mempools(self.nodes)
+ assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_2}, {"bip125-replaceable":"no"})
+
+ # Tx3 will opt-in to RBF
+ utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_2)
+ inputs = [{"txid": txid_2, "vout":utxo_to_use["vout"]}]
+ outputs = {self.nodes[1].getnewaddress(): 0.998}
+ tx3 = self.nodes[0].createrawtransaction(inputs, outputs)
+ tx3_modified = txFromHex(tx3)
+ tx3_modified.vin[0].nSequence = 0
+ tx3 = bytes_to_hex_str(tx3_modified.serialize())
+ tx3_signed = self.nodes[0].signrawtransaction(tx3)['hex']
+ txid_3 = self.nodes[0].sendrawtransaction(tx3_signed)
+
+ assert(is_opt_in(self.nodes[0], txid_3))
+ assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"})
+ sync_mempools(self.nodes)
+ assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_3}, {"bip125-replaceable":"yes"})
+
+ # Tx4 will chain off tx3. Doesn't signal itself, but depends on one
+ # that does.
+ utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_3)
+ inputs = [{"txid": txid_3, "vout":utxo_to_use["vout"]}]
+ outputs = {self.nodes[0].getnewaddress(): 0.997}
+ tx4 = self.nodes[1].createrawtransaction(inputs, outputs)
+ tx4_signed = self.nodes[1].signrawtransaction(tx4)["hex"]
+ txid_4 = self.nodes[1].sendrawtransaction(tx4_signed)
+
+ assert(not is_opt_in(self.nodes[1], txid_4))
+ assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"})
+ sync_mempools(self.nodes)
+ assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"yes"})
+
+ # Replace tx3, and check that tx4 becomes unknown
+ tx3_b = tx3_modified
+ tx3_b.vout[0].nValue -= int(Decimal("0.004") * COIN) # bump the fee
+ tx3_b = bytes_to_hex_str(tx3_b.serialize())
+ tx3_b_signed = self.nodes[0].signrawtransaction(tx3_b)['hex']
+ txid_3b = self.nodes[0].sendrawtransaction(tx3_b_signed, True)
+ assert(is_opt_in(self.nodes[0], txid_3b))
+
+ assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"})
+ sync_mempools(self.nodes)
+ assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable":"unknown"})
+
+ # Check gettransaction as well:
+ for n in self.nodes[0:2]:
+ assert_equal(n.gettransaction(txid_1)["bip125-replaceable"], "no")
+ assert_equal(n.gettransaction(txid_2)["bip125-replaceable"], "no")
+ assert_equal(n.gettransaction(txid_3)["bip125-replaceable"], "yes")
+ assert_equal(n.gettransaction(txid_3b)["bip125-replaceable"], "yes")
+ assert_equal(n.gettransaction(txid_4)["bip125-replaceable"], "unknown")
+
+ # After mining a transaction, it's no longer BIP125-replaceable
+ self.nodes[0].generate(1)
+ assert(txid_3b not in self.nodes[0].getrawmempool())
+ assert_equal(self.nodes[0].gettransaction(txid_3b)["bip125-replaceable"], "no")
+ assert_equal(self.nodes[0].gettransaction(txid_4)["bip125-replaceable"], "unknown")
+
+
if __name__ == '__main__':
ListTransactionsTest().main()
diff --git a/qa/rpc-tests/maxblocksinflight.py b/qa/rpc-tests/maxblocksinflight.py
new file mode 100755
index 0000000000..1df1c484be
--- /dev/null
+++ b/qa/rpc-tests/maxblocksinflight.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.mininode import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import logging
+
+'''
+In this test we connect to one node over p2p, send it numerous inv's, and
+compare the resulting number of getdata requests to a max allowed value. We
+test for exceeding 128 blocks in flight, which was the limit an 0.9 client will
+reach. [0.10 clients shouldn't request more than 16 from a single peer.]
+'''
+MAX_REQUESTS = 128
+
+class TestManager(NodeConnCB):
+ # set up NodeConnCB callbacks, overriding base class
+ def on_getdata(self, conn, message):
+ self.log.debug("got getdata %s" % repr(message))
+ # Log the requests
+ for inv in message.inv:
+ if inv.hash not in self.blockReqCounts:
+ self.blockReqCounts[inv.hash] = 0
+ self.blockReqCounts[inv.hash] += 1
+
+ def on_close(self, conn):
+ if not self.disconnectOkay:
+ raise EarlyDisconnectError(0)
+
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.log = logging.getLogger("BlockRelayTest")
+
+ def add_new_connection(self, connection):
+ self.connection = connection
+ self.blockReqCounts = {}
+ self.disconnectOkay = False
+
+ def run(self):
+ self.connection.rpc.generate(1) # Leave IBD
+
+ numBlocksToGenerate = [8, 16, 128, 1024]
+ for count in range(len(numBlocksToGenerate)):
+ current_invs = []
+ for i in range(numBlocksToGenerate[count]):
+ current_invs.append(CInv(2, random.randrange(0, 1 << 256)))
+ if len(current_invs) >= 50000:
+ self.connection.send_message(msg_inv(current_invs))
+ current_invs = []
+ if len(current_invs) > 0:
+ self.connection.send_message(msg_inv(current_invs))
+
+ # Wait and see how many blocks were requested
+ time.sleep(2)
+
+ total_requests = 0
+ with mininode_lock:
+ for key in self.blockReqCounts:
+ total_requests += self.blockReqCounts[key]
+ if self.blockReqCounts[key] > 1:
+ raise AssertionError("Error, test failed: block %064x requested more than once" % key)
+ if total_requests > MAX_REQUESTS:
+ raise AssertionError("Error, too many blocks (%d) requested" % total_requests)
+ print("Round %d: success (total requests: %d)" % (count, total_requests))
+
+ self.disconnectOkay = True
+ self.connection.disconnect_node()
+
+
+class MaxBlocksInFlightTest(BitcoinTestFramework):
+ def add_options(self, parser):
+ parser.add_option("--testbinary", dest="testbinary",
+ default=os.getenv("BITCOIND", "bitcoind"),
+ help="Binary to test max block requests behavior")
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def setup_network(self):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
+ extra_args=[['-debug', '-whitelist=127.0.0.1']],
+ binary=[self.options.testbinary])
+
+ def run_test(self):
+ test = TestManager()
+ test.add_new_connection(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test))
+ NetworkThread().start() # Start up network handling in another thread
+ test.run()
+
+if __name__ == '__main__':
+ MaxBlocksInFlightTest().main()
diff --git a/qa/rpc-tests/maxuploadtarget.py b/qa/rpc-tests/maxuploadtarget.py
new file mode 100755
index 0000000000..125d4eb275
--- /dev/null
+++ b/qa/rpc-tests/maxuploadtarget.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.mininode import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import time
+
+'''
+Test behavior of -maxuploadtarget.
+
+* Verify that getdata requests for old blocks (>1week) are dropped
+if uploadtarget has been reached.
+* Verify that getdata requests for recent blocks are respecteved even
+if uploadtarget has been reached.
+* Verify that the upload counters are reset after 24 hours.
+'''
+
+# TestNode: bare-bones "peer". Used mostly as a conduit for a test to sending
+# p2p messages to a node, generating the messages in the main testing logic.
+class TestNode(NodeConnCB):
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.connection = None
+ self.ping_counter = 1
+ self.last_pong = msg_pong()
+ self.block_receive_map = {}
+
+ def add_connection(self, conn):
+ self.connection = conn
+ self.peer_disconnected = False
+
+ def on_inv(self, conn, message):
+ pass
+
+ # Track the last getdata message we receive (used in the test)
+ def on_getdata(self, conn, message):
+ self.last_getdata = message
+
+ def on_block(self, conn, message):
+ message.block.calc_sha256()
+ try:
+ self.block_receive_map[message.block.sha256] += 1
+ except KeyError as e:
+ self.block_receive_map[message.block.sha256] = 1
+
+ # Spin until verack message is received from the node.
+ # We use this to signal that our test can begin. This
+ # is called from the testing thread, so it needs to acquire
+ # the global lock.
+ def wait_for_verack(self):
+ def veracked():
+ return self.verack_received
+ return wait_until(veracked, timeout=10)
+
+ def wait_for_disconnect(self):
+ def disconnected():
+ return self.peer_disconnected
+ return wait_until(disconnected, timeout=10)
+
+ # Wrapper for the NodeConn's send_message function
+ def send_message(self, message):
+ self.connection.send_message(message)
+
+ def on_pong(self, conn, message):
+ self.last_pong = message
+
+ def on_close(self, conn):
+ self.peer_disconnected = True
+
+ # Sync up with the node after delivery of a block
+ def sync_with_ping(self, timeout=30):
+ def received_pong():
+ return (self.last_pong.nonce == self.ping_counter)
+ self.connection.send_message(msg_ping(nonce=self.ping_counter))
+ success = wait_until(received_pong, timeout)
+ self.ping_counter += 1
+ return success
+
+class MaxUploadTest(BitcoinTestFramework):
+
+ def add_options(self, parser):
+ parser.add_option("--testbinary", dest="testbinary",
+ default=os.getenv("BITCOIND", "bitcoind"),
+ help="bitcoind binary to test")
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ self.utxo = []
+ self.txouts = gen_return_txouts()
+
+ def setup_network(self):
+ # Start a node with maxuploadtarget of 200 MB (/24h)
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-maxuploadtarget=800", "-blockmaxsize=999000"]))
+
+ def mine_full_block(self, node, address):
+ # Want to create a full block
+ # We'll generate a 66k transaction below, and 14 of them is close to the 1MB block limit
+ for j in range(14):
+ if len(self.utxo) < 14:
+ self.utxo = node.listunspent()
+ inputs=[]
+ outputs = {}
+ t = self.utxo.pop()
+ inputs.append({ "txid" : t["txid"], "vout" : t["vout"]})
+ remchange = t["amount"] - Decimal("0.001000")
+ outputs[address]=remchange
+ # Create a basic transaction that will send change back to ourself after account for a fee
+ # And then insert the 128 generated transaction outs in the middle rawtx[92] is where the #
+ # of txouts is stored and is the only thing we overwrite from the original transaction
+ rawtx = node.createrawtransaction(inputs, outputs)
+ newtx = rawtx[0:92]
+ newtx = newtx + self.txouts
+ newtx = newtx + rawtx[94:]
+ # Appears to be ever so slightly faster to sign with SIGHASH_NONE
+ signresult = node.signrawtransaction(newtx,None,None,"NONE")
+ txid = node.sendrawtransaction(signresult["hex"], True)
+ # Mine a full sized block which will be these transactions we just created
+ node.generate(1)
+
+ def run_test(self):
+ # Before we connect anything, we first set the time on the node
+ # to be in the past, otherwise things break because the CNode
+ # time counters can't be reset backward after initialization
+ old_time = int(time.time() - 2*60*60*24*7)
+ self.nodes[0].setmocktime(old_time)
+
+ # Generate some old blocks
+ self.nodes[0].generate(130)
+
+ # test_nodes[0] will only request old blocks
+ # test_nodes[1] will only request new blocks
+ # test_nodes[2] will test resetting the counters
+ test_nodes = []
+ connections = []
+
+ for i in range(3):
+ test_nodes.append(TestNode())
+ connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_nodes[i]))
+ test_nodes[i].add_connection(connections[i])
+
+ NetworkThread().start() # Start up network handling in another thread
+ [x.wait_for_verack() for x in test_nodes]
+
+ # Test logic begins here
+
+ # Now mine a big block
+ self.mine_full_block(self.nodes[0], self.nodes[0].getnewaddress())
+
+ # Store the hash; we'll request this later
+ big_old_block = self.nodes[0].getbestblockhash()
+ old_block_size = self.nodes[0].getblock(big_old_block, True)['size']
+ big_old_block = int(big_old_block, 16)
+
+ # Advance to two days ago
+ self.nodes[0].setmocktime(int(time.time()) - 2*60*60*24)
+
+ # Mine one more block, so that the prior block looks old
+ self.mine_full_block(self.nodes[0], self.nodes[0].getnewaddress())
+
+ # We'll be requesting this new block too
+ big_new_block = self.nodes[0].getbestblockhash()
+ new_block_size = self.nodes[0].getblock(big_new_block)['size']
+ big_new_block = int(big_new_block, 16)
+
+ # test_nodes[0] will test what happens if we just keep requesting the
+ # the same big old block too many times (expect: disconnect)
+
+ getdata_request = msg_getdata()
+ getdata_request.inv.append(CInv(2, big_old_block))
+
+ max_bytes_per_day = 800*1024*1024
+ daily_buffer = 144 * 4000000
+ max_bytes_available = max_bytes_per_day - daily_buffer
+ success_count = max_bytes_available // old_block_size
+
+ # 576MB will be reserved for relaying new blocks, so expect this to
+ # succeed for ~235 tries.
+ for i in range(success_count):
+ test_nodes[0].send_message(getdata_request)
+ test_nodes[0].sync_with_ping()
+ assert_equal(test_nodes[0].block_receive_map[big_old_block], i+1)
+
+ assert_equal(len(self.nodes[0].getpeerinfo()), 3)
+ # At most a couple more tries should succeed (depending on how long
+ # the test has been running so far).
+ for i in range(3):
+ test_nodes[0].send_message(getdata_request)
+ test_nodes[0].wait_for_disconnect()
+ assert_equal(len(self.nodes[0].getpeerinfo()), 2)
+ print("Peer 0 disconnected after downloading old block too many times")
+
+ # Requesting the current block on test_nodes[1] should succeed indefinitely,
+ # even when over the max upload target.
+ # We'll try 800 times
+ getdata_request.inv = [CInv(2, big_new_block)]
+ for i in range(800):
+ test_nodes[1].send_message(getdata_request)
+ test_nodes[1].sync_with_ping()
+ assert_equal(test_nodes[1].block_receive_map[big_new_block], i+1)
+
+ print("Peer 1 able to repeatedly download new block")
+
+ # But if test_nodes[1] tries for an old block, it gets disconnected too.
+ getdata_request.inv = [CInv(2, big_old_block)]
+ test_nodes[1].send_message(getdata_request)
+ test_nodes[1].wait_for_disconnect()
+ assert_equal(len(self.nodes[0].getpeerinfo()), 1)
+
+ print("Peer 1 disconnected after trying to download old block")
+
+ print("Advancing system time on node to clear counters...")
+
+ # If we advance the time by 24 hours, then the counters should reset,
+ # and test_nodes[2] should be able to retrieve the old block.
+ self.nodes[0].setmocktime(int(time.time()))
+ test_nodes[2].sync_with_ping()
+ test_nodes[2].send_message(getdata_request)
+ test_nodes[2].sync_with_ping()
+ assert_equal(test_nodes[2].block_receive_map[big_old_block], 1)
+
+ print("Peer 2 able to download old block")
+
+ [c.disconnect_node() for c in connections]
+
+ #stop and start node 0 with 1MB maxuploadtarget, whitelist 127.0.0.1
+ print("Restarting nodes with -whitelist=127.0.0.1")
+ stop_node(self.nodes[0], 0)
+ self.nodes[0] = start_node(0, self.options.tmpdir, ["-debug", "-whitelist=127.0.0.1", "-maxuploadtarget=1", "-blockmaxsize=999000"])
+
+ #recreate/reconnect 3 test nodes
+ test_nodes = []
+ connections = []
+
+ for i in range(3):
+ test_nodes.append(TestNode())
+ connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_nodes[i]))
+ test_nodes[i].add_connection(connections[i])
+
+ NetworkThread().start() # Start up network handling in another thread
+ [x.wait_for_verack() for x in test_nodes]
+
+ #retrieve 20 blocks which should be enough to break the 1MB limit
+ getdata_request.inv = [CInv(2, big_new_block)]
+ for i in range(20):
+ test_nodes[1].send_message(getdata_request)
+ test_nodes[1].sync_with_ping()
+ assert_equal(test_nodes[1].block_receive_map[big_new_block], i+1)
+
+ getdata_request.inv = [CInv(2, big_old_block)]
+ test_nodes[1].send_message(getdata_request)
+ test_nodes[1].wait_for_disconnect()
+ assert_equal(len(self.nodes[0].getpeerinfo()), 3) #node is still connected because of the whitelist
+
+ print("Peer 1 still connected after trying to download old block (whitelisted)")
+
+ [c.disconnect_node() for c in connections]
+
+if __name__ == '__main__':
+ MaxUploadTest().main()
diff --git a/qa/rpc-tests/mempool_limit.py b/qa/rpc-tests/mempool_limit.py
new file mode 100755
index 0000000000..4438c152df
--- /dev/null
+++ b/qa/rpc-tests/mempool_limit.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+# Test mempool limiting together/eviction with the wallet
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+class MempoolLimitTest(BitcoinTestFramework):
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-maxmempool=5", "-spendzeroconfchange=0", "-debug"]))
+ self.is_network_split = False
+ self.sync_all()
+ self.relayfee = self.nodes[0].getnetworkinfo()['relayfee']
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ self.txouts = gen_return_txouts()
+
+ def run_test(self):
+ txids = []
+ utxos = create_confirmed_utxos(self.relayfee, self.nodes[0], 90)
+
+ #create a mempool tx that will be evicted
+ us0 = utxos.pop()
+ inputs = [{ "txid" : us0["txid"], "vout" : us0["vout"]}]
+ outputs = {self.nodes[0].getnewaddress() : 0.0001}
+ tx = self.nodes[0].createrawtransaction(inputs, outputs)
+ self.nodes[0].settxfee(self.relayfee) # specifically fund this tx with low fee
+ txF = self.nodes[0].fundrawtransaction(tx)
+ self.nodes[0].settxfee(0) # return to automatic fee selection
+ txFS = self.nodes[0].signrawtransaction(txF['hex'])
+ txid = self.nodes[0].sendrawtransaction(txFS['hex'])
+
+ relayfee = self.nodes[0].getnetworkinfo()['relayfee']
+ base_fee = relayfee*100
+ for i in range (4):
+ txids.append([])
+ txids[i] = create_lots_of_big_transactions(self.nodes[0], self.txouts, utxos[30*i:30*i+30], (i+1)*base_fee)
+
+ # by now, the tx should be evicted, check confirmation state
+ assert(txid not in self.nodes[0].getrawmempool())
+ txdata = self.nodes[0].gettransaction(txid)
+ assert(txdata['confirmations'] == 0) #confirmation should still be 0
+
+if __name__ == '__main__':
+ MempoolLimitTest().main()
diff --git a/qa/rpc-tests/mempool_packages.py b/qa/rpc-tests/mempool_packages.py
new file mode 100755
index 0000000000..45dc0e65c4
--- /dev/null
+++ b/qa/rpc-tests/mempool_packages.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+# Test descendant package tracking code
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.mininode import COIN
+
+MAX_ANCESTORS = 25
+MAX_DESCENDANTS = 25
+
+class MempoolPackagesTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000", "-relaypriority=0", "-debug"]))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-maxorphantx=1000", "-relaypriority=0", "-limitancestorcount=5", "-debug"]))
+ connect_nodes(self.nodes[0], 1)
+ self.is_network_split = False
+ self.sync_all()
+
+ # Build a transaction that spends parent_txid:vout
+ # Return amount sent
+ def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs):
+ send_value = satoshi_round((value - fee)/num_outputs)
+ inputs = [ {'txid' : parent_txid, 'vout' : vout} ]
+ outputs = {}
+ for i in range(num_outputs):
+ outputs[node.getnewaddress()] = send_value
+ rawtx = node.createrawtransaction(inputs, outputs)
+ signedtx = node.signrawtransaction(rawtx)
+ txid = node.sendrawtransaction(signedtx['hex'])
+ fulltx = node.getrawtransaction(txid, 1)
+ assert(len(fulltx['vout']) == num_outputs) # make sure we didn't generate a change output
+ return (txid, send_value)
+
+ def run_test(self):
+ ''' Mine some blocks and have them mature. '''
+ self.nodes[0].generate(101)
+ utxo = self.nodes[0].listunspent(10)
+ txid = utxo[0]['txid']
+ vout = utxo[0]['vout']
+ value = utxo[0]['amount']
+
+ fee = Decimal("0.0001")
+ # MAX_ANCESTORS transactions off a confirmed tx should be fine
+ chain = []
+ for i in range(MAX_ANCESTORS):
+ (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1)
+ value = sent_value
+ chain.append(txid)
+
+ # Check mempool has MAX_ANCESTORS transactions in it, and descendant
+ # count and fees should look correct
+ mempool = self.nodes[0].getrawmempool(True)
+ assert_equal(len(mempool), MAX_ANCESTORS)
+ descendant_count = 1
+ descendant_fees = 0
+ descendant_size = 0
+
+ descendants = []
+ ancestors = list(chain)
+ for x in reversed(chain):
+ # Check that getmempoolentry is consistent with getrawmempool
+ entry = self.nodes[0].getmempoolentry(x)
+ assert_equal(entry, mempool[x])
+
+ # Check that the descendant calculations are correct
+ assert_equal(mempool[x]['descendantcount'], descendant_count)
+ descendant_fees += mempool[x]['fee']
+ assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
+ assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN)
+ descendant_size += mempool[x]['size']
+ assert_equal(mempool[x]['descendantsize'], descendant_size)
+ descendant_count += 1
+
+ # Check that getmempooldescendants is correct
+ assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x)))
+ descendants.append(x)
+
+ # Check that getmempoolancestors is correct
+ ancestors.remove(x)
+ assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x)))
+
+ # Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true
+ v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True)
+ assert_equal(len(v_ancestors), len(chain)-1)
+ for x in v_ancestors.keys():
+ assert_equal(mempool[x], v_ancestors[x])
+ assert(chain[-1] not in v_ancestors.keys())
+
+ v_descendants = self.nodes[0].getmempooldescendants(chain[0], True)
+ assert_equal(len(v_descendants), len(chain)-1)
+ for x in v_descendants.keys():
+ assert_equal(mempool[x], v_descendants[x])
+ assert(chain[0] not in v_descendants.keys())
+
+ # Check that descendant modified fees includes fee deltas from
+ # prioritisetransaction
+ self.nodes[0].prioritisetransaction(chain[-1], 0, 1000)
+ mempool = self.nodes[0].getrawmempool(True)
+
+ descendant_fees = 0
+ for x in reversed(chain):
+ descendant_fees += mempool[x]['fee']
+ assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000)
+
+ # Adding one more transaction on to the chain should fail.
+ try:
+ self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1)
+ except JSONRPCException as e:
+ print("too-long-ancestor-chain successfully rejected")
+
+ # Check that prioritising a tx before it's added to the mempool works
+ # First clear the mempool by mining a block.
+ self.nodes[0].generate(1)
+ sync_blocks(self.nodes)
+ assert_equal(len(self.nodes[0].getrawmempool()), 0)
+ # Prioritise a transaction that has been mined, then add it back to the
+ # mempool by using invalidateblock.
+ self.nodes[0].prioritisetransaction(chain[-1], 0, 2000)
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+ # Keep node1's tip synced with node0
+ self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
+
+ # Now check that the transaction is in the mempool, with the right modified fee
+ mempool = self.nodes[0].getrawmempool(True)
+
+ descendant_fees = 0
+ for x in reversed(chain):
+ descendant_fees += mempool[x]['fee']
+ if (x == chain[-1]):
+ assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']+satoshi_round(0.00002))
+ assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 2000)
+
+ # TODO: check that node1's mempool is as expected
+
+ # TODO: test ancestor size limits
+
+ # Now test descendant chain limits
+ txid = utxo[1]['txid']
+ value = utxo[1]['amount']
+ vout = utxo[1]['vout']
+
+ transaction_package = []
+ # First create one parent tx with 10 children
+ (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 10)
+ parent_transaction = txid
+ for i in range(10):
+ transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value})
+
+ for i in range(MAX_DESCENDANTS):
+ utxo = transaction_package.pop(0)
+ try:
+ (txid, sent_value) = self.chain_transaction(self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10)
+ for j in range(10):
+ transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value})
+ if i == MAX_DESCENDANTS - 2:
+ mempool = self.nodes[0].getrawmempool(True)
+ assert_equal(mempool[parent_transaction]['descendantcount'], MAX_DESCENDANTS)
+ except JSONRPCException as e:
+ print(e.error['message'])
+ assert_equal(i, MAX_DESCENDANTS - 1)
+ print("tx that would create too large descendant package successfully rejected")
+
+ # TODO: check that node1's mempool is as expected
+
+ # TODO: test descendant size limits
+
+ # Test reorg handling
+ # First, the basics:
+ self.nodes[0].generate(1)
+ sync_blocks(self.nodes)
+ self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash())
+ self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash())
+
+ # Now test the case where node1 has a transaction T in its mempool that
+ # depends on transactions A and B which are in a mined block, and the
+ # block containing A and B is disconnected, AND B is not accepted back
+ # into node1's mempool because its ancestor count is too high.
+
+ # Create 8 transactions, like so:
+ # Tx0 -> Tx1 (vout0)
+ # \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7
+ #
+ # Mine them in the next block, then generate a new tx8 that spends
+ # Tx1 and Tx7, and add to node1's mempool, then disconnect the
+ # last block.
+
+ # Create tx0 with 2 outputs
+ utxo = self.nodes[0].listunspent()
+ txid = utxo[0]['txid']
+ value = utxo[0]['amount']
+ vout = utxo[0]['vout']
+
+ send_value = satoshi_round((value - fee)/2)
+ inputs = [ {'txid' : txid, 'vout' : vout} ]
+ outputs = {}
+ for i in range(2):
+ outputs[self.nodes[0].getnewaddress()] = send_value
+ rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
+ signedtx = self.nodes[0].signrawtransaction(rawtx)
+ txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
+ tx0_id = txid
+ value = send_value
+
+ # Create tx1
+ (tx1_id, tx1_value) = self.chain_transaction(self.nodes[0], tx0_id, 0, value, fee, 1)
+
+ # Create tx2-7
+ vout = 1
+ txid = tx0_id
+ for i in range(6):
+ (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1)
+ vout = 0
+ value = sent_value
+
+ # Mine these in a block
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # Now generate tx8, with a big fee
+ inputs = [ {'txid' : tx1_id, 'vout': 0}, {'txid' : txid, 'vout': 0} ]
+ outputs = { self.nodes[0].getnewaddress() : send_value + value - 4*fee }
+ rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
+ signedtx = self.nodes[0].signrawtransaction(rawtx)
+ txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
+ sync_mempools(self.nodes)
+
+ # Now try to disconnect the tip on each node...
+ self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+ sync_blocks(self.nodes)
+
+if __name__ == '__main__':
+ MempoolPackagesTest().main()
diff --git a/qa/rpc-tests/mempool_coinbase_spends.py b/qa/rpc-tests/mempool_reorg.py
index 7b43712768..301b094eb0 100755
--- a/qa/rpc-tests/mempool_coinbase_spends.py
+++ b/qa/rpc-tests/mempool_reorg.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -8,14 +8,15 @@
# that spend (directly or indirectly) coinbase transactions.
#
-from test_framework import BitcoinTestFramework
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
-import os
-import shutil
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
# Create one-input, one-output, no-fee transaction:
class MempoolCoinbaseTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = False
alert_filename = None # Set by setup_network
@@ -26,22 +27,14 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
self.nodes.append(start_node(1, self.options.tmpdir, args))
connect_nodes(self.nodes[1], 0)
self.is_network_split = False
- self.sync_all
-
- def create_tx(self, from_txid, to_address, amount):
- inputs = [{ "txid" : from_txid, "vout" : 0}]
- outputs = { to_address : amount }
- rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
- signresult = self.nodes[0].signrawtransaction(rawtx)
- assert_equal(signresult["complete"], True)
- return signresult["hex"]
+ self.sync_all()
def run_test(self):
start_count = self.nodes[0].getblockcount()
# Mine three blocks. After this, nodes[0] blocks
# 101, 102, and 103 are spend-able.
- new_blocks = self.nodes[1].setgenerate(True, 4)
+ new_blocks = self.nodes[1].generate(4)
self.sync_all()
node0_address = self.nodes[0].getnewaddress()
@@ -53,24 +46,34 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
# 3. Indirect (coinbase and child both in chain) : spend_103 and spend_103_1
# Use invalidatblock to make all of the above coinbase spends invalid (immature coinbase),
# and make sure the mempool code behaves correctly.
- b = [ self.nodes[0].getblockhash(n) for n in range(102, 105) ]
+ b = [ self.nodes[0].getblockhash(n) for n in range(101, 105) ]
coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ]
- spend_101_raw = self.create_tx(coinbase_txids[0], node1_address, 50)
- spend_102_raw = self.create_tx(coinbase_txids[1], node0_address, 50)
- spend_103_raw = self.create_tx(coinbase_txids[2], node0_address, 50)
+ spend_101_raw = create_tx(self.nodes[0], coinbase_txids[1], node1_address, 49.99)
+ spend_102_raw = create_tx(self.nodes[0], coinbase_txids[2], node0_address, 49.99)
+ spend_103_raw = create_tx(self.nodes[0], coinbase_txids[3], node0_address, 49.99)
+
+ # Create a block-height-locked transaction which will be invalid after reorg
+ timelock_tx = self.nodes[0].createrawtransaction([{"txid": coinbase_txids[0], "vout": 0}], {node0_address: 49.99})
+ # Set the time lock
+ timelock_tx = timelock_tx.replace("ffffffff", "11111111", 1)
+ timelock_tx = timelock_tx[:-8] + hex(self.nodes[0].getblockcount() + 2)[2:] + "000000"
+ timelock_tx = self.nodes[0].signrawtransaction(timelock_tx)["hex"]
+ assert_raises(JSONRPCException, self.nodes[0].sendrawtransaction, timelock_tx)
# Broadcast and mine spend_102 and 103:
spend_102_id = self.nodes[0].sendrawtransaction(spend_102_raw)
spend_103_id = self.nodes[0].sendrawtransaction(spend_103_raw)
- self.nodes[0].setgenerate(True, 1)
+ self.nodes[0].generate(1)
+ assert_raises(JSONRPCException, self.nodes[0].sendrawtransaction, timelock_tx)
# Create 102_1 and 103_1:
- spend_102_1_raw = self.create_tx(spend_102_id, node1_address, 50)
- spend_103_1_raw = self.create_tx(spend_103_id, node1_address, 50)
+ spend_102_1_raw = create_tx(self.nodes[0], spend_102_id, node1_address, 49.98)
+ spend_103_1_raw = create_tx(self.nodes[0], spend_103_id, node1_address, 49.98)
# Broadcast and mine 103_1:
spend_103_1_id = self.nodes[0].sendrawtransaction(spend_103_1_raw)
- self.nodes[0].setgenerate(True, 1)
+ last_block = self.nodes[0].generate(1)
+ timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx)
# ... now put spend_101 and spend_102_1 in memory pools:
spend_101_id = self.nodes[0].sendrawtransaction(spend_101_raw)
@@ -78,7 +81,11 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
self.sync_all()
- assert_equal(set(self.nodes[0].getrawmempool()), set([ spend_101_id, spend_102_1_id ]))
+ assert_equal(set(self.nodes[0].getrawmempool()), {spend_101_id, spend_102_1_id, timelock_tx_id})
+
+ for node in self.nodes:
+ node.invalidateblock(last_block[0])
+ assert_equal(set(self.nodes[0].getrawmempool()), {spend_101_id, spend_102_1_id, spend_103_1_id})
# Use invalidateblock to re-org back and make all those coinbase spends
# immature/invalid:
diff --git a/qa/rpc-tests/mempool_resurrect_test.py b/qa/rpc-tests/mempool_resurrect_test.py
index 81db812bfc..3db12cbf76 100755
--- a/qa/rpc-tests/mempool_resurrect_test.py
+++ b/qa/rpc-tests/mempool_resurrect_test.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -8,15 +8,17 @@
# the blockchain is re-organized.
#
-from test_framework import BitcoinTestFramework
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
-import os
-import shutil
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
# Create one-input, one-output, no-fee transaction:
class MempoolCoinbaseTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+ self.setup_clean_chain = False
+
def setup_network(self):
# Just need one node for this test
args = ["-checkmempool", "-debug=mempool"]
@@ -24,17 +26,8 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
self.nodes.append(start_node(0, self.options.tmpdir, args))
self.is_network_split = False
- def create_tx(self, from_txid, to_address, amount):
- inputs = [{ "txid" : from_txid, "vout" : 0}]
- outputs = { to_address : amount }
- rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
- signresult = self.nodes[0].signrawtransaction(rawtx)
- assert_equal(signresult["complete"], True)
- return signresult["hex"]
-
def run_test(self):
node0_address = self.nodes[0].getnewaddress()
-
# Spend block 1/2/3's coinbase transactions
# Mine a block.
# Create three more transactions, spending the spends
@@ -47,16 +40,16 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
b = [ self.nodes[0].getblockhash(n) for n in range(1, 4) ]
coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ]
- spends1_raw = [ self.create_tx(txid, node0_address, 50) for txid in coinbase_txids ]
+ spends1_raw = [ create_tx(self.nodes[0], txid, node0_address, 49.99) for txid in coinbase_txids ]
spends1_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends1_raw ]
blocks = []
- blocks.extend(self.nodes[0].setgenerate(True, 1))
+ blocks.extend(self.nodes[0].generate(1))
- spends2_raw = [ self.create_tx(txid, node0_address, 49.99) for txid in spends1_id ]
+ spends2_raw = [ create_tx(self.nodes[0], txid, node0_address, 49.98) for txid in spends1_id ]
spends2_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends2_raw ]
- blocks.extend(self.nodes[0].setgenerate(True, 1))
+ blocks.extend(self.nodes[0].generate(1))
# mempool should be empty, all txns confirmed
assert_equal(set(self.nodes[0].getrawmempool()), set())
@@ -76,7 +69,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
assert(tx["confirmations"] == 0)
# Generate another block, they should all get mined
- self.nodes[0].setgenerate(True, 1)
+ self.nodes[0].generate(1)
# mempool should be empty, all txns confirmed
assert_equal(set(self.nodes[0].getrawmempool()), set())
for txid in spends1_id+spends2_id:
diff --git a/qa/rpc-tests/mempool_spendcoinbase.py b/qa/rpc-tests/mempool_spendcoinbase.py
index f0b34f2904..d5e4bf52d2 100755
--- a/qa/rpc-tests/mempool_spendcoinbase.py
+++ b/qa/rpc-tests/mempool_spendcoinbase.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -13,15 +13,17 @@
# but less mature coinbase spends are NOT.
#
-from test_framework import BitcoinTestFramework
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
-import os
-import shutil
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
# Create one-input, one-output, no-fee transaction:
class MempoolSpendCoinbaseTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+ self.setup_clean_chain = False
+
def setup_network(self):
# Just need one node for this test
args = ["-checkmempool", "-debug=mempool"]
@@ -29,14 +31,6 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework):
self.nodes.append(start_node(0, self.options.tmpdir, args))
self.is_network_split = False
- def create_tx(self, from_txid, to_address, amount):
- inputs = [{ "txid" : from_txid, "vout" : 0}]
- outputs = { to_address : amount }
- rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
- signresult = self.nodes[0].signrawtransaction(rawtx)
- assert_equal(signresult["complete"], True)
- return signresult["hex"]
-
def run_test(self):
chain_height = self.nodes[0].getblockcount()
assert_equal(chain_height, 200)
@@ -47,7 +41,7 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework):
# is too immature to spend.
b = [ self.nodes[0].getblockhash(n) for n in range(101, 103) ]
coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ]
- spends_raw = [ self.create_tx(txid, node0_address, 50) for txid in coinbase_txids ]
+ spends_raw = [ create_tx(self.nodes[0], txid, node0_address, 49.99) for txid in coinbase_txids ]
spend_101_id = self.nodes[0].sendrawtransaction(spends_raw[0])
@@ -58,7 +52,7 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getrawmempool(), [ spend_101_id ])
# mine a block, spend_101 should get confirmed
- self.nodes[0].setgenerate(True, 1)
+ self.nodes[0].generate(1)
assert_equal(set(self.nodes[0].getrawmempool()), set())
# ... and now height 102 can be spent:
diff --git a/qa/rpc-tests/merkle_blocks.py b/qa/rpc-tests/merkle_blocks.py
new file mode 100755
index 0000000000..b2155d7fc3
--- /dev/null
+++ b/qa/rpc-tests/merkle_blocks.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test merkleblock fetch/validation
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+class MerkleBlockTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 4
+
+ def setup_network(self):
+ self.nodes = []
+ # Nodes 0/1 are "wallet" nodes
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"]))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-debug"]))
+ # Nodes 2/3 are used for testing
+ self.nodes.append(start_node(2, self.options.tmpdir, ["-debug"]))
+ self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-txindex"]))
+ connect_nodes(self.nodes[0], 1)
+ connect_nodes(self.nodes[0], 2)
+ connect_nodes(self.nodes[0], 3)
+
+ self.is_network_split = False
+ self.sync_all()
+
+ def run_test(self):
+ print("Mining blocks...")
+ self.nodes[0].generate(105)
+ self.sync_all()
+
+ chain_height = self.nodes[1].getblockcount()
+ assert_equal(chain_height, 105)
+ assert_equal(self.nodes[1].getbalance(), 0)
+ assert_equal(self.nodes[2].getbalance(), 0)
+
+ node0utxos = self.nodes[0].listunspent(1)
+ tx1 = self.nodes[0].createrawtransaction([node0utxos.pop()], {self.nodes[1].getnewaddress(): 49.99})
+ txid1 = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransaction(tx1)["hex"])
+ tx2 = self.nodes[0].createrawtransaction([node0utxos.pop()], {self.nodes[1].getnewaddress(): 49.99})
+ txid2 = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransaction(tx2)["hex"])
+ assert_raises(JSONRPCException, self.nodes[0].gettxoutproof, [txid1])
+
+ self.nodes[0].generate(1)
+ blockhash = self.nodes[0].getblockhash(chain_height + 1)
+ self.sync_all()
+
+ txlist = []
+ blocktxn = self.nodes[0].getblock(blockhash, True)["tx"]
+ txlist.append(blocktxn[1])
+ txlist.append(blocktxn[2])
+
+ assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid1])), [txid1])
+ assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid1, txid2])), txlist)
+ assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid1, txid2], blockhash)), txlist)
+
+ txin_spent = self.nodes[1].listunspent(1).pop()
+ tx3 = self.nodes[1].createrawtransaction([txin_spent], {self.nodes[0].getnewaddress(): 49.98})
+ self.nodes[0].sendrawtransaction(self.nodes[1].signrawtransaction(tx3)["hex"])
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ txid_spent = txin_spent["txid"]
+ txid_unspent = txid1 if txin_spent["txid"] != txid1 else txid2
+
+ # We can't find the block from a fully-spent tx
+ assert_raises(JSONRPCException, self.nodes[2].gettxoutproof, [txid_spent])
+ # ...but we can if we specify the block
+ assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid_spent], blockhash)), [txid_spent])
+ # ...or if the first tx is not fully-spent
+ assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid_unspent])), [txid_unspent])
+ try:
+ assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid1, txid2])), txlist)
+ except JSONRPCException:
+ assert_equal(self.nodes[2].verifytxoutproof(self.nodes[2].gettxoutproof([txid2, txid1])), txlist)
+ # ...or if we have a -txindex
+ assert_equal(self.nodes[2].verifytxoutproof(self.nodes[3].gettxoutproof([txid_spent])), [txid_spent])
+
+if __name__ == '__main__':
+ MerkleBlockTest().main()
diff --git a/qa/rpc-tests/multi_rpc.py b/qa/rpc-tests/multi_rpc.py
new file mode 100755
index 0000000000..24373b257d
--- /dev/null
+++ b/qa/rpc-tests/multi_rpc.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test mulitple rpc user config option rpcauth
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import str_to_b64str, assert_equal
+
+import os
+import http.client
+import urllib.parse
+
+class HTTPBasicsTest (BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = False
+ self.num_nodes = 1
+
+ def setup_chain(self):
+ super().setup_chain()
+ #Append rpcauth to bitcoin.conf before initialization
+ rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144"
+ rpcauth2 = "rpcauth=rt2:f8607b1a88861fac29dfccf9b52ff9f$ff36a0c23c8c62b4846112e50fa888416e94c17bfd4c42f88fd8f55ec6a3137e"
+ with open(os.path.join(self.options.tmpdir+"/node0", "bitcoin.conf"), 'a') as f:
+ f.write(rpcauth+"\n")
+ f.write(rpcauth2+"\n")
+
+ def setup_network(self):
+ self.nodes = self.setup_nodes()
+
+ def run_test(self):
+
+ ##################################################
+ # Check correctness of the rpcauth config option #
+ ##################################################
+ url = urllib.parse.urlparse(self.nodes[0].url)
+
+ #Old authpair
+ authpair = url.username + ':' + url.password
+
+ #New authpair generated via share/rpcuser tool
+ rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144"
+ password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM="
+
+ #Second authpair with different username
+ rpcauth2 = "rpcauth=rt2:f8607b1a88861fac29dfccf9b52ff9f$ff36a0c23c8c62b4846112e50fa888416e94c17bfd4c42f88fd8f55ec6a3137e"
+ password2 = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI="
+ authpairnew = "rt:"+password
+
+ headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ resp = conn.getresponse()
+ assert_equal(resp.status==401, False)
+ conn.close()
+
+ #Use new authpair to confirm both work
+ headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ resp = conn.getresponse()
+ assert_equal(resp.status==401, False)
+ conn.close()
+
+ #Wrong login name with rt's password
+ authpairnew = "rtwrong:"+password
+ headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ resp = conn.getresponse()
+ assert_equal(resp.status==401, True)
+ conn.close()
+
+ #Wrong password for rt
+ authpairnew = "rt:"+password+"wrong"
+ headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ resp = conn.getresponse()
+ assert_equal(resp.status==401, True)
+ conn.close()
+
+ #Correct for rt2
+ authpairnew = "rt2:"+password2
+ headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ resp = conn.getresponse()
+ assert_equal(resp.status==401, False)
+ conn.close()
+
+ #Wrong password for rt2
+ authpairnew = "rt2:"+password2+"wrong"
+ headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ resp = conn.getresponse()
+ assert_equal(resp.status==401, True)
+ conn.close()
+
+
+if __name__ == '__main__':
+ HTTPBasicsTest ().main ()
diff --git a/qa/rpc-tests/nodehandling.py b/qa/rpc-tests/nodehandling.py
new file mode 100755
index 0000000000..e9682c4908
--- /dev/null
+++ b/qa/rpc-tests/nodehandling.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test node handling
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+import http.client
+import urllib.parse
+
+class NodeHandlingTest (BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.setup_clean_chain = False
+
+ def run_test(self):
+ ###########################
+ # setban/listbanned tests #
+ ###########################
+ assert_equal(len(self.nodes[2].getpeerinfo()), 4) #we should have 4 nodes at this point
+ self.nodes[2].setban("127.0.0.1", "add")
+ time.sleep(3) #wait till the nodes are disconected
+ assert_equal(len(self.nodes[2].getpeerinfo()), 0) #all nodes must be disconnected at this point
+ assert_equal(len(self.nodes[2].listbanned()), 1)
+ self.nodes[2].clearbanned()
+ assert_equal(len(self.nodes[2].listbanned()), 0)
+ self.nodes[2].setban("127.0.0.0/24", "add")
+ assert_equal(len(self.nodes[2].listbanned()), 1)
+ try:
+ self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24
+ except:
+ pass
+ assert_equal(len(self.nodes[2].listbanned()), 1) #still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24
+ try:
+ self.nodes[2].setban("127.0.0.1", "remove")
+ except:
+ pass
+ assert_equal(len(self.nodes[2].listbanned()), 1)
+ self.nodes[2].setban("127.0.0.0/24", "remove")
+ assert_equal(len(self.nodes[2].listbanned()), 0)
+ self.nodes[2].clearbanned()
+ assert_equal(len(self.nodes[2].listbanned()), 0)
+
+ ##test persisted banlist
+ self.nodes[2].setban("127.0.0.0/32", "add")
+ self.nodes[2].setban("127.0.0.0/24", "add")
+ self.nodes[2].setban("192.168.0.1", "add", 1) #ban for 1 seconds
+ self.nodes[2].setban("2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19", "add", 1000) #ban for 1000 seconds
+ listBeforeShutdown = self.nodes[2].listbanned()
+ assert_equal("192.168.0.1/32", listBeforeShutdown[2]['address']) #must be here
+ time.sleep(2) #make 100% sure we expired 192.168.0.1 node time
+
+ #stop node
+ stop_node(self.nodes[2], 2)
+
+ self.nodes[2] = start_node(2, self.options.tmpdir)
+ listAfterShutdown = self.nodes[2].listbanned()
+ assert_equal("127.0.0.0/24", listAfterShutdown[0]['address'])
+ assert_equal("127.0.0.0/32", listAfterShutdown[1]['address'])
+ assert_equal("/19" in listAfterShutdown[2]['address'], True)
+
+ ###########################
+ # RPC disconnectnode test #
+ ###########################
+ url = urllib.parse.urlparse(self.nodes[1].url)
+ self.nodes[0].disconnectnode(url.hostname+":"+str(p2p_port(1)))
+ time.sleep(2) #disconnecting a node needs a little bit of time
+ for node in self.nodes[0].getpeerinfo():
+ assert(node['addr'] != url.hostname+":"+str(p2p_port(1)))
+
+ connect_nodes_bi(self.nodes,0,1) #reconnect the node
+ found = False
+ for node in self.nodes[0].getpeerinfo():
+ if node['addr'] == url.hostname+":"+str(p2p_port(1)):
+ found = True
+ assert(found)
+
+if __name__ == '__main__':
+ NodeHandlingTest ().main ()
diff --git a/qa/rpc-tests/p2p-acceptblock.py b/qa/rpc-tests/p2p-acceptblock.py
new file mode 100755
index 0000000000..015ec34eff
--- /dev/null
+++ b/qa/rpc-tests/p2p-acceptblock.py
@@ -0,0 +1,291 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.mininode import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import time
+from test_framework.blocktools import create_block, create_coinbase
+
+'''
+AcceptBlockTest -- test processing of unrequested blocks.
+
+Since behavior differs when receiving unrequested blocks from whitelisted peers
+versus non-whitelisted peers, this tests the behavior of both (effectively two
+separate tests running in parallel).
+
+Setup: two nodes, node0 and node1, not connected to each other. Node0 does not
+whitelist localhost, but node1 does. They will each be on their own chain for
+this test.
+
+We have one NodeConn connection to each, test_node and white_node respectively.
+
+The test:
+1. Generate one block on each node, to leave IBD.
+
+2. Mine a new block on each tip, and deliver to each node from node's peer.
+ The tip should advance.
+
+3. Mine a block that forks the previous block, and deliver to each node from
+ corresponding peer.
+ Node0 should not process this block (just accept the header), because it is
+ unrequested and doesn't have more work than the tip.
+ Node1 should process because this is coming from a whitelisted peer.
+
+4. Send another block that builds on the forking block.
+ Node0 should process this block but be stuck on the shorter chain, because
+ it's missing an intermediate block.
+ Node1 should reorg to this longer chain.
+
+4b.Send 288 more blocks on the longer chain.
+ Node0 should process all but the last block (too far ahead in height).
+ Send all headers to Node1, and then send the last block in that chain.
+ Node1 should accept the block because it's coming from a whitelisted peer.
+
+5. Send a duplicate of the block in #3 to Node0.
+ Node0 should not process the block because it is unrequested, and stay on
+ the shorter chain.
+
+6. Send Node0 an inv for the height 3 block produced in #4 above.
+ Node0 should figure out that Node0 has the missing height 2 block and send a
+ getdata.
+
+7. Send Node0 the missing block again.
+ Node0 should process and the tip should advance.
+'''
+
+# TestNode: bare-bones "peer". Used mostly as a conduit for a test to sending
+# p2p messages to a node, generating the messages in the main testing logic.
+class TestNode(NodeConnCB):
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.connection = None
+ self.ping_counter = 1
+ self.last_pong = msg_pong()
+
+ def add_connection(self, conn):
+ self.connection = conn
+
+ # Track the last getdata message we receive (used in the test)
+ def on_getdata(self, conn, message):
+ self.last_getdata = message
+
+ # Spin until verack message is received from the node.
+ # We use this to signal that our test can begin. This
+ # is called from the testing thread, so it needs to acquire
+ # the global lock.
+ def wait_for_verack(self):
+ while True:
+ with mininode_lock:
+ if self.verack_received:
+ return
+ time.sleep(0.05)
+
+ # Wrapper for the NodeConn's send_message function
+ def send_message(self, message):
+ self.connection.send_message(message)
+
+ def on_pong(self, conn, message):
+ self.last_pong = message
+
+ # Sync up with the node after delivery of a block
+ def sync_with_ping(self, timeout=30):
+ self.connection.send_message(msg_ping(nonce=self.ping_counter))
+ received_pong = False
+ sleep_time = 0.05
+ while not received_pong and timeout > 0:
+ time.sleep(sleep_time)
+ timeout -= sleep_time
+ with mininode_lock:
+ if self.last_pong.nonce == self.ping_counter:
+ received_pong = True
+ self.ping_counter += 1
+ return received_pong
+
+
+class AcceptBlockTest(BitcoinTestFramework):
+ def add_options(self, parser):
+ parser.add_option("--testbinary", dest="testbinary",
+ default=os.getenv("BITCOIND", "bitcoind"),
+ help="bitcoind binary to test")
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+
+ def setup_network(self):
+ # Node0 will be used to test behavior of processing unrequested blocks
+ # from peers which are not whitelisted, while Node1 will be used for
+ # the whitelisted case.
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"],
+ binary=self.options.testbinary))
+ self.nodes.append(start_node(1, self.options.tmpdir,
+ ["-debug", "-whitelist=127.0.0.1"],
+ binary=self.options.testbinary))
+
+ def run_test(self):
+ # Setup the p2p connections and start up the network thread.
+ test_node = TestNode() # connects to node0 (not whitelisted)
+ white_node = TestNode() # connects to node1 (whitelisted)
+
+ connections = []
+ connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
+ connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_node))
+ test_node.add_connection(connections[0])
+ white_node.add_connection(connections[1])
+
+ NetworkThread().start() # Start up network handling in another thread
+
+ # Test logic begins here
+ test_node.wait_for_verack()
+ white_node.wait_for_verack()
+
+ # 1. Have both nodes mine a block (leave IBD)
+ [ n.generate(1) for n in self.nodes ]
+ tips = [ int("0x" + n.getbestblockhash(), 0) for n in self.nodes ]
+
+ # 2. Send one block that builds on each tip.
+ # This should be accepted.
+ blocks_h2 = [] # the height 2 blocks on each node's chain
+ block_time = int(time.time()) + 1
+ for i in range(2):
+ blocks_h2.append(create_block(tips[i], create_coinbase(2), block_time))
+ blocks_h2[i].solve()
+ block_time += 1
+ test_node.send_message(msg_block(blocks_h2[0]))
+ white_node.send_message(msg_block(blocks_h2[1]))
+
+ [ x.sync_with_ping() for x in [test_node, white_node] ]
+ assert_equal(self.nodes[0].getblockcount(), 2)
+ assert_equal(self.nodes[1].getblockcount(), 2)
+ print("First height 2 block accepted by both nodes")
+
+ # 3. Send another block that builds on the original tip.
+ blocks_h2f = [] # Blocks at height 2 that fork off the main chain
+ for i in range(2):
+ blocks_h2f.append(create_block(tips[i], create_coinbase(2), blocks_h2[i].nTime+1))
+ blocks_h2f[i].solve()
+ test_node.send_message(msg_block(blocks_h2f[0]))
+ white_node.send_message(msg_block(blocks_h2f[1]))
+
+ [ x.sync_with_ping() for x in [test_node, white_node] ]
+ for x in self.nodes[0].getchaintips():
+ if x['hash'] == blocks_h2f[0].hash:
+ assert_equal(x['status'], "headers-only")
+
+ for x in self.nodes[1].getchaintips():
+ if x['hash'] == blocks_h2f[1].hash:
+ assert_equal(x['status'], "valid-headers")
+
+ print("Second height 2 block accepted only from whitelisted peer")
+
+ # 4. Now send another block that builds on the forking chain.
+ blocks_h3 = []
+ for i in range(2):
+ blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(3), blocks_h2f[i].nTime+1))
+ blocks_h3[i].solve()
+ test_node.send_message(msg_block(blocks_h3[0]))
+ white_node.send_message(msg_block(blocks_h3[1]))
+
+ [ x.sync_with_ping() for x in [test_node, white_node] ]
+ # Since the earlier block was not processed by node0, the new block
+ # can't be fully validated.
+ for x in self.nodes[0].getchaintips():
+ if x['hash'] == blocks_h3[0].hash:
+ assert_equal(x['status'], "headers-only")
+
+ # But this block should be accepted by node0 since it has more work.
+ try:
+ self.nodes[0].getblock(blocks_h3[0].hash)
+ print("Unrequested more-work block accepted from non-whitelisted peer")
+ except:
+ raise AssertionError("Unrequested more work block was not processed")
+
+ # Node1 should have accepted and reorged.
+ assert_equal(self.nodes[1].getblockcount(), 3)
+ print("Successfully reorged to length 3 chain from whitelisted peer")
+
+ # 4b. Now mine 288 more blocks and deliver; all should be processed but
+ # the last (height-too-high) on node0. Node1 should process the tip if
+ # we give it the headers chain leading to the tip.
+ tips = blocks_h3
+ headers_message = msg_headers()
+ all_blocks = [] # node0's blocks
+ for j in range(2):
+ for i in range(288):
+ next_block = create_block(tips[j].sha256, create_coinbase(i + 4), tips[j].nTime+1)
+ next_block.solve()
+ if j==0:
+ test_node.send_message(msg_block(next_block))
+ all_blocks.append(next_block)
+ else:
+ headers_message.headers.append(CBlockHeader(next_block))
+ tips[j] = next_block
+
+ time.sleep(2)
+ for x in all_blocks:
+ try:
+ self.nodes[0].getblock(x.hash)
+ if x == all_blocks[287]:
+ raise AssertionError("Unrequested block too far-ahead should have been ignored")
+ except:
+ if x == all_blocks[287]:
+ print("Unrequested block too far-ahead not processed")
+ else:
+ raise AssertionError("Unrequested block with more work should have been accepted")
+
+ headers_message.headers.pop() # Ensure the last block is unrequested
+ white_node.send_message(headers_message) # Send headers leading to tip
+ white_node.send_message(msg_block(tips[1])) # Now deliver the tip
+ try:
+ white_node.sync_with_ping()
+ self.nodes[1].getblock(tips[1].hash)
+ print("Unrequested block far ahead of tip accepted from whitelisted peer")
+ except:
+ raise AssertionError("Unrequested block from whitelisted peer not accepted")
+
+ # 5. Test handling of unrequested block on the node that didn't process
+ # Should still not be processed (even though it has a child that has more
+ # work).
+ test_node.send_message(msg_block(blocks_h2f[0]))
+
+ # Here, if the sleep is too short, the test could falsely succeed (if the
+ # node hasn't processed the block by the time the sleep returns, and then
+ # the node processes it and incorrectly advances the tip).
+ # But this would be caught later on, when we verify that an inv triggers
+ # a getdata request for this block.
+ test_node.sync_with_ping()
+ assert_equal(self.nodes[0].getblockcount(), 2)
+ print("Unrequested block that would complete more-work chain was ignored")
+
+ # 6. Try to get node to request the missing block.
+ # Poke the node with an inv for block at height 3 and see if that
+ # triggers a getdata on block 2 (it should if block 2 is missing).
+ with mininode_lock:
+ # Clear state so we can check the getdata request
+ test_node.last_getdata = None
+ test_node.send_message(msg_inv([CInv(2, blocks_h3[0].sha256)]))
+
+ test_node.sync_with_ping()
+ with mininode_lock:
+ getdata = test_node.last_getdata
+
+ # Check that the getdata includes the right block
+ assert_equal(getdata.inv[0].hash, blocks_h2f[0].sha256)
+ print("Inv at tip triggered getdata for unprocessed block")
+
+ # 7. Send the missing block for the third time (now it is requested)
+ test_node.send_message(msg_block(blocks_h2f[0]))
+
+ test_node.sync_with_ping()
+ assert_equal(self.nodes[0].getblockcount(), 290)
+ print("Successfully reorged to longer chain from non-whitelisted peer")
+
+ [ c.disconnect_node() for c in connections ]
+
+if __name__ == '__main__':
+ AcceptBlockTest().main()
diff --git a/qa/rpc-tests/p2p-feefilter.py b/qa/rpc-tests/p2p-feefilter.py
new file mode 100755
index 0000000000..cd0501a314
--- /dev/null
+++ b/qa/rpc-tests/p2p-feefilter.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+
+from test_framework.mininode import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import time
+
+'''
+FeeFilterTest -- test processing of feefilter messages
+'''
+
+def hashToHex(hash):
+ return format(hash, '064x')
+
+# Wait up to 60 secs to see if the testnode has received all the expected invs
+def allInvsMatch(invsExpected, testnode):
+ for x in range(60):
+ with mininode_lock:
+ if (sorted(invsExpected) == sorted(testnode.txinvs)):
+ return True;
+ time.sleep(1)
+ return False;
+
+# TestNode: bare-bones "peer". Used to track which invs are received from a node
+# and to send the node feefilter messages.
+class TestNode(SingleNodeConnCB):
+ def __init__(self):
+ SingleNodeConnCB.__init__(self)
+ self.txinvs = []
+
+ def on_inv(self, conn, message):
+ for i in message.inv:
+ if (i.type == 1):
+ self.txinvs.append(hashToHex(i.hash))
+
+ def clear_invs(self):
+ with mininode_lock:
+ self.txinvs = []
+
+ def send_filter(self, feerate):
+ self.send_message(msg_feefilter(feerate))
+ self.sync_with_ping()
+
+class FeeFilterTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ # Node1 will be used to generate txs which should be relayed from Node0
+ # to our test node
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-logtimemicros"]))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-logtimemicros"]))
+ connect_nodes(self.nodes[0], 1)
+
+ def run_test(self):
+ node1 = self.nodes[1]
+ # Get out of IBD
+ node1.generate(1)
+ sync_blocks(self.nodes)
+
+ # Setup the p2p connections and start up the network thread.
+ test_node = TestNode()
+ connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)
+ test_node.add_connection(connection)
+ NetworkThread().start()
+ test_node.wait_for_verack()
+
+ # Test that invs are received for all txs at feerate of 20 sat/byte
+ node1.settxfee(Decimal("0.00020000"))
+ txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
+ assert(allInvsMatch(txids, test_node))
+ test_node.clear_invs()
+
+ # Set a filter of 15 sat/byte
+ test_node.send_filter(15000)
+
+ # Test that txs are still being received (paying 20 sat/byte)
+ txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
+ assert(allInvsMatch(txids, test_node))
+ test_node.clear_invs()
+
+ # Change tx fee rate to 10 sat/byte and test they are no longer received
+ node1.settxfee(Decimal("0.00010000"))
+ [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
+ sync_mempools(self.nodes) # must be sure node 0 has received all txs
+ time.sleep(10) # wait 10 secs to be sure its doesn't relay any
+ assert(allInvsMatch([], test_node))
+ test_node.clear_invs()
+
+ # Remove fee filter and check that txs are received again
+ test_node.send_filter(0)
+ txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)]
+ assert(allInvsMatch(txids, test_node))
+ test_node.clear_invs()
+
+if __name__ == '__main__':
+ FeeFilterTest().main()
diff --git a/qa/rpc-tests/p2p-fullblocktest.py b/qa/rpc-tests/p2p-fullblocktest.py
new file mode 100755
index 0000000000..17fd40ef1d
--- /dev/null
+++ b/qa/rpc-tests/p2p-fullblocktest.py
@@ -0,0 +1,1293 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.util import *
+from test_framework.comptool import TestManager, TestInstance, RejectResult
+from test_framework.blocktools import *
+import time
+from test_framework.key import CECKey
+from test_framework.script import *
+import struct
+
+class PreviousSpendableOutput(object):
+ def __init__(self, tx = CTransaction(), n = -1):
+ self.tx = tx
+ self.n = n # the output we're spending
+
+'''
+This reimplements tests from the bitcoinj/FullBlockTestGenerator used
+by the pull-tester.
+
+We use the testing framework in which we expect a particular answer from
+each test.
+'''
+
+def hash160(s):
+ return hashlib.new('ripemd160', sha256(s)).digest()
+
+# Use this class for tests that require behavior other than normal "mininode" behavior.
+# For now, it is used to serialize a bloated varint (b64).
+class CBrokenBlock(CBlock):
+ def __init__(self, header=None):
+ super(CBrokenBlock, self).__init__(header)
+
+ def initialize(self, base_block):
+ self.vtx = copy.deepcopy(base_block.vtx)
+ self.hashMerkleRoot = self.calc_merkle_root()
+
+ def serialize(self):
+ r = b""
+ r += super(CBlock, self).serialize()
+ r += struct.pack("<BQ", 255, len(self.vtx))
+ for tx in self.vtx:
+ r += tx.serialize()
+ return r
+
+ def normal_serialize(self):
+ r = b""
+ r += super(CBrokenBlock, self).serialize()
+ return r
+
+class FullBlockTest(ComparisonTestFramework):
+
+ # Can either run this test as 1 node with expected answers, or two and compare them.
+ # Change the "outcome" variable from each TestInstance object to only do the comparison.
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+ self.block_heights = {}
+ self.coinbase_key = CECKey()
+ self.coinbase_key.set_secretbytes(b"horsebattery")
+ self.coinbase_pubkey = self.coinbase_key.get_pubkey()
+ self.tip = None
+ self.blocks = {}
+
+ def add_options(self, parser):
+ super().add_options(parser)
+ parser.add_option("--runbarelyexpensive", dest="runbarelyexpensive", default=True)
+
+ def run_test(self):
+ self.test = TestManager(self, self.options.tmpdir)
+ self.test.add_all_connections(self.nodes)
+ NetworkThread().start() # Start up network handling in another thread
+ self.test.run()
+
+ def add_transactions_to_block(self, block, tx_list):
+ [ tx.rehash() for tx in tx_list ]
+ block.vtx.extend(tx_list)
+
+ # this is a little handier to use than the version in blocktools.py
+ def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])):
+ tx = create_transaction(spend_tx, n, b"", value, script)
+ return tx
+
+ # sign a transaction, using the key we know about
+ # this signs input 0 in tx, which is assumed to be spending output n in spend_tx
+ def sign_tx(self, tx, spend_tx, n):
+ scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey)
+ if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend
+ tx.vin[0].scriptSig = CScript()
+ return
+ (sighash, err) = SignatureHash(spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL)
+ tx.vin[0].scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))])
+
+ def create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])):
+ tx = self.create_tx(spend_tx, n, value, script)
+ self.sign_tx(tx, spend_tx, n)
+ tx.rehash()
+ return tx
+
+ def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True):
+ if self.tip == None:
+ base_block_hash = self.genesis_hash
+ block_time = int(time.time())+1
+ else:
+ base_block_hash = self.tip.sha256
+ block_time = self.tip.nTime + 1
+ # First create the coinbase
+ height = self.block_heights[base_block_hash] + 1
+ coinbase = create_coinbase(height, self.coinbase_pubkey)
+ coinbase.vout[0].nValue += additional_coinbase_value
+ coinbase.rehash()
+ if spend == None:
+ block = create_block(base_block_hash, coinbase, block_time)
+ else:
+ coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees
+ coinbase.rehash()
+ block = create_block(base_block_hash, coinbase, block_time)
+ tx = create_transaction(spend.tx, spend.n, b"", 1, script) # spend 1 satoshi
+ self.sign_tx(tx, spend.tx, spend.n)
+ self.add_transactions_to_block(block, [tx])
+ block.hashMerkleRoot = block.calc_merkle_root()
+ if solve:
+ block.solve()
+ self.tip = block
+ self.block_heights[block.sha256] = height
+ assert number not in self.blocks
+ self.blocks[number] = block
+ return block
+
+ def get_tests(self):
+ self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
+ self.block_heights[self.genesis_hash] = 0
+ spendable_outputs = []
+
+ # save the current tip so it can be spent by a later block
+ def save_spendable_output():
+ spendable_outputs.append(self.tip)
+
+ # get an output that we previously marked as spendable
+ def get_spendable_output():
+ return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)
+
+ # returns a test case that asserts that the current tip was accepted
+ def accepted():
+ return TestInstance([[self.tip, True]])
+
+ # returns a test case that asserts that the current tip was rejected
+ def rejected(reject = None):
+ if reject is None:
+ return TestInstance([[self.tip, False]])
+ else:
+ return TestInstance([[self.tip, reject]])
+
+ # move the tip back to a previous block
+ def tip(number):
+ self.tip = self.blocks[number]
+
+ # adds transactions to the block and updates state
+ def update_block(block_number, new_transactions):
+ block = self.blocks[block_number]
+ self.add_transactions_to_block(block, new_transactions)
+ old_sha256 = block.sha256
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.solve()
+ # Update the internal state just like in next_block
+ self.tip = block
+ if block.sha256 != old_sha256:
+ self.block_heights[block.sha256] = self.block_heights[old_sha256]
+ del self.block_heights[old_sha256]
+ self.blocks[block_number] = block
+ return block
+
+ # shorthand for functions
+ block = self.next_block
+ create_tx = self.create_tx
+ create_and_sign_tx = self.create_and_sign_transaction
+
+ # these must be updated if consensus changes
+ MAX_BLOCK_SIGOPS = 20000
+
+
+ # Create a new block
+ block(0)
+ save_spendable_output()
+ yield accepted()
+
+
+ # Now we need that block to mature so we can spend the coinbase.
+ test = TestInstance(sync_every_block=False)
+ for i in range(99):
+ block(5000 + i)
+ test.blocks_and_transactions.append([self.tip, True])
+ save_spendable_output()
+ yield test
+
+ # collect spendable outputs now to avoid cluttering the code later on
+ out = []
+ for i in range(33):
+ out.append(get_spendable_output())
+
+ # Start by building a couple of blocks on top (which output is spent is
+ # in parentheses):
+ # genesis -> b1 (0) -> b2 (1)
+ block(1, spend=out[0])
+ save_spendable_output()
+ yield accepted()
+
+ block(2, spend=out[1])
+ yield accepted()
+ save_spendable_output()
+
+ # so fork like this:
+ #
+ # genesis -> b1 (0) -> b2 (1)
+ # \-> b3 (1)
+ #
+ # Nothing should happen at this point. We saw b2 first so it takes priority.
+ tip(1)
+ b3 = block(3, spend=out[1])
+ txout_b3 = PreviousSpendableOutput(b3.vtx[1], 0)
+ yield rejected()
+
+
+ # Now we add another block to make the alternative chain longer.
+ #
+ # genesis -> b1 (0) -> b2 (1)
+ # \-> b3 (1) -> b4 (2)
+ block(4, spend=out[2])
+ yield accepted()
+
+
+ # ... and back to the first chain.
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b3 (1) -> b4 (2)
+ tip(2)
+ block(5, spend=out[2])
+ save_spendable_output()
+ yield rejected()
+
+ block(6, spend=out[3])
+ yield accepted()
+
+ # Try to create a fork that double-spends
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b7 (2) -> b8 (4)
+ # \-> b3 (1) -> b4 (2)
+ tip(5)
+ block(7, spend=out[2])
+ yield rejected()
+
+ block(8, spend=out[4])
+ yield rejected()
+
+ # Try to create a block that has too much fee
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b9 (4)
+ # \-> b3 (1) -> b4 (2)
+ tip(6)
+ block(9, spend=out[4], additional_coinbase_value=1)
+ yield rejected(RejectResult(16, b'bad-cb-amount'))
+
+ # Create a fork that ends in a block with too much fee (the one that causes the reorg)
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b10 (3) -> b11 (4)
+ # \-> b3 (1) -> b4 (2)
+ tip(5)
+ block(10, spend=out[3])
+ yield rejected()
+
+ block(11, spend=out[4], additional_coinbase_value=1)
+ yield rejected(RejectResult(16, b'bad-cb-amount'))
+
+
+ # Try again, but with a valid fork first
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b14 (5)
+ # (b12 added last)
+ # \-> b3 (1) -> b4 (2)
+ tip(5)
+ b12 = block(12, spend=out[3])
+ save_spendable_output()
+ b13 = block(13, spend=out[4])
+ # Deliver the block header for b12, and the block b13.
+ # b13 should be accepted but the tip won't advance until b12 is delivered.
+ yield TestInstance([[CBlockHeader(b12), None], [b13, False]])
+
+ save_spendable_output()
+ # b14 is invalid, but the node won't know that until it tries to connect
+ # Tip still can't advance because b12 is missing
+ block(14, spend=out[5], additional_coinbase_value=1)
+ yield rejected()
+
+ yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13.
+
+ # Add a block with MAX_BLOCK_SIGOPS and one with one more sigop
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6)
+ # \-> b3 (1) -> b4 (2)
+
+ # Test that a block with a lot of checksigs is okay
+ lots_of_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS - 1))
+ tip(13)
+ block(15, spend=out[5], script=lots_of_checksigs)
+ yield accepted()
+ save_spendable_output()
+
+
+ # Test that a block with too many checksigs is rejected
+ too_many_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS))
+ block(16, spend=out[6], script=too_many_checksigs)
+ yield rejected(RejectResult(16, b'bad-blk-sigops'))
+
+
+ # Attempt to spend a transaction created on a different fork
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1])
+ # \-> b3 (1) -> b4 (2)
+ tip(15)
+ block(17, spend=txout_b3)
+ yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent'))
+
+ # Attempt to spend a transaction created on a different fork (on a fork this time)
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b15 (5)
+ # \-> b18 (b3.vtx[1]) -> b19 (6)
+ # \-> b3 (1) -> b4 (2)
+ tip(13)
+ block(18, spend=txout_b3)
+ yield rejected()
+
+ block(19, spend=out[6])
+ yield rejected()
+
+ # Attempt to spend a coinbase at depth too low
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7)
+ # \-> b3 (1) -> b4 (2)
+ tip(15)
+ block(20, spend=out[7])
+ yield rejected(RejectResult(16, b'bad-txns-premature-spend-of-coinbase'))
+
+ # Attempt to spend a coinbase at depth too low (on a fork this time)
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b15 (5)
+ # \-> b21 (6) -> b22 (5)
+ # \-> b3 (1) -> b4 (2)
+ tip(13)
+ block(21, spend=out[6])
+ yield rejected()
+
+ block(22, spend=out[5])
+ yield rejected()
+
+ # Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6)
+ # \-> b24 (6) -> b25 (7)
+ # \-> b3 (1) -> b4 (2)
+ tip(15)
+ b23 = block(23, spend=out[6])
+ tx = CTransaction()
+ script_length = MAX_BLOCK_SIZE - len(b23.serialize()) - 69
+ script_output = CScript([b'\x00' * script_length])
+ tx.vout.append(CTxOut(0, script_output))
+ tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 0)))
+ b23 = update_block(23, [tx])
+ # Make sure the math above worked out to produce a max-sized block
+ assert_equal(len(b23.serialize()), MAX_BLOCK_SIZE)
+ yield accepted()
+ save_spendable_output()
+
+ # Make the next block one byte bigger and check that it fails
+ tip(15)
+ b24 = block(24, spend=out[6])
+ script_length = MAX_BLOCK_SIZE - len(b24.serialize()) - 69
+ script_output = CScript([b'\x00' * (script_length+1)])
+ tx.vout = [CTxOut(0, script_output)]
+ b24 = update_block(24, [tx])
+ assert_equal(len(b24.serialize()), MAX_BLOCK_SIZE+1)
+ yield rejected(RejectResult(16, b'bad-blk-length'))
+
+ block(25, spend=out[7])
+ yield rejected()
+
+ # Create blocks with a coinbase input script size out of range
+ # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
+ # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7)
+ # \-> ... (6) -> ... (7)
+ # \-> b3 (1) -> b4 (2)
+ tip(15)
+ b26 = block(26, spend=out[6])
+ b26.vtx[0].vin[0].scriptSig = b'\x00'
+ b26.vtx[0].rehash()
+ # update_block causes the merkle root to get updated, even with no new
+ # transactions, and updates the required state.
+ b26 = update_block(26, [])
+ yield rejected(RejectResult(16, b'bad-cb-length'))
+
+ # Extend the b26 chain to make sure bitcoind isn't accepting b26
+ b27 = block(27, spend=out[7])
+ yield rejected(RejectResult(16, b'bad-prevblk'))
+
+ # Now try a too-large-coinbase script
+ tip(15)
+ b28 = block(28, spend=out[6])
+ b28.vtx[0].vin[0].scriptSig = b'\x00' * 101
+ b28.vtx[0].rehash()
+ b28 = update_block(28, [])
+ yield rejected(RejectResult(16, b'bad-cb-length'))
+
+ # Extend the b28 chain to make sure bitcoind isn't accepting b28
+ b29 = block(29, spend=out[7])
+ yield rejected(RejectResult(16, b'bad-prevblk'))
+
+ # b30 has a max-sized coinbase scriptSig.
+ tip(23)
+ b30 = block(30)
+ b30.vtx[0].vin[0].scriptSig = b'\x00' * 100
+ b30.vtx[0].rehash()
+ b30 = update_block(30, [])
+ yield accepted()
+ save_spendable_output()
+
+ # b31 - b35 - check sigops of OP_CHECKMULTISIG / OP_CHECKMULTISIGVERIFY / OP_CHECKSIGVERIFY
+ #
+ # genesis -> ... -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10)
+ # \-> b36 (11)
+ # \-> b34 (10)
+ # \-> b32 (9)
+ #
+
+ # MULTISIG: each op code counts as 20 sigops. To create the edge case, pack another 19 sigops at the end.
+ lots_of_multisigs = CScript([OP_CHECKMULTISIG] * ((MAX_BLOCK_SIGOPS-1) // 20) + [OP_CHECKSIG] * 19)
+ b31 = block(31, spend=out[8], script=lots_of_multisigs)
+ assert_equal(get_legacy_sigopcount_block(b31), MAX_BLOCK_SIGOPS)
+ yield accepted()
+ save_spendable_output()
+
+ # this goes over the limit because the coinbase has one sigop
+ too_many_multisigs = CScript([OP_CHECKMULTISIG] * (MAX_BLOCK_SIGOPS // 20))
+ b32 = block(32, spend=out[9], script=too_many_multisigs)
+ assert_equal(get_legacy_sigopcount_block(b32), MAX_BLOCK_SIGOPS + 1)
+ yield rejected(RejectResult(16, b'bad-blk-sigops'))
+
+
+ # CHECKMULTISIGVERIFY
+ tip(31)
+ lots_of_multisigs = CScript([OP_CHECKMULTISIGVERIFY] * ((MAX_BLOCK_SIGOPS-1) // 20) + [OP_CHECKSIG] * 19)
+ block(33, spend=out[9], script=lots_of_multisigs)
+ yield accepted()
+ save_spendable_output()
+
+ too_many_multisigs = CScript([OP_CHECKMULTISIGVERIFY] * (MAX_BLOCK_SIGOPS // 20))
+ block(34, spend=out[10], script=too_many_multisigs)
+ yield rejected(RejectResult(16, b'bad-blk-sigops'))
+
+
+ # CHECKSIGVERIFY
+ tip(33)
+ lots_of_checksigs = CScript([OP_CHECKSIGVERIFY] * (MAX_BLOCK_SIGOPS - 1))
+ b35 = block(35, spend=out[10], script=lots_of_checksigs)
+ yield accepted()
+ save_spendable_output()
+
+ too_many_checksigs = CScript([OP_CHECKSIGVERIFY] * (MAX_BLOCK_SIGOPS))
+ block(36, spend=out[11], script=too_many_checksigs)
+ yield rejected(RejectResult(16, b'bad-blk-sigops'))
+
+
+ # Check spending of a transaction in a block which failed to connect
+ #
+ # b6 (3)
+ # b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10)
+ # \-> b37 (11)
+ # \-> b38 (11/37)
+ #
+
+ # save 37's spendable output, but then double-spend out11 to invalidate the block
+ tip(35)
+ b37 = block(37, spend=out[11])
+ txout_b37 = PreviousSpendableOutput(b37.vtx[1], 0)
+ tx = create_and_sign_tx(out[11].tx, out[11].n, 0)
+ b37 = update_block(37, [tx])
+ yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent'))
+
+ # attempt to spend b37's first non-coinbase tx, at which point b37 was still considered valid
+ tip(35)
+ block(38, spend=txout_b37)
+ yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent'))
+
+ # Check P2SH SigOp counting
+ #
+ #
+ # 13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b41 (12)
+ # \-> b40 (12)
+ #
+ # b39 - create some P2SH outputs that will require 6 sigops to spend:
+ #
+ # redeem_script = COINBASE_PUBKEY, (OP_2DUP+OP_CHECKSIGVERIFY) * 5, OP_CHECKSIG
+ # p2sh_script = OP_HASH160, ripemd160(sha256(script)), OP_EQUAL
+ #
+ tip(35)
+ b39 = block(39)
+ b39_outputs = 0
+ b39_sigops_per_output = 6
+
+ # Build the redeem script, hash it, use hash to create the p2sh script
+ redeem_script = CScript([self.coinbase_pubkey] + [OP_2DUP, OP_CHECKSIGVERIFY]*5 + [OP_CHECKSIG])
+ redeem_script_hash = hash160(redeem_script)
+ p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL])
+
+ # Create a transaction that spends one satoshi to the p2sh_script, the rest to OP_TRUE
+ # This must be signed because it is spending a coinbase
+ spend = out[11]
+ tx = create_tx(spend.tx, spend.n, 1, p2sh_script)
+ tx.vout.append(CTxOut(spend.tx.vout[spend.n].nValue - 1, CScript([OP_TRUE])))
+ self.sign_tx(tx, spend.tx, spend.n)
+ tx.rehash()
+ b39 = update_block(39, [tx])
+ b39_outputs += 1
+
+ # Until block is full, add tx's with 1 satoshi to p2sh_script, the rest to OP_TRUE
+ tx_new = None
+ tx_last = tx
+ total_size=len(b39.serialize())
+ while(total_size < MAX_BLOCK_SIZE):
+ tx_new = create_tx(tx_last, 1, 1, p2sh_script)
+ tx_new.vout.append(CTxOut(tx_last.vout[1].nValue - 1, CScript([OP_TRUE])))
+ tx_new.rehash()
+ total_size += len(tx_new.serialize())
+ if total_size >= MAX_BLOCK_SIZE:
+ break
+ b39.vtx.append(tx_new) # add tx to block
+ tx_last = tx_new
+ b39_outputs += 1
+
+ b39 = update_block(39, [])
+ yield accepted()
+ save_spendable_output()
+
+
+ # Test sigops in P2SH redeem scripts
+ #
+ # b40 creates 3333 tx's spending the 6-sigop P2SH outputs from b39 for a total of 19998 sigops.
+ # The first tx has one sigop and then at the end we add 2 more to put us just over the max.
+ #
+ # b41 does the same, less one, so it has the maximum sigops permitted.
+ #
+ tip(39)
+ b40 = block(40, spend=out[12])
+ sigops = get_legacy_sigopcount_block(b40)
+ numTxes = (MAX_BLOCK_SIGOPS - sigops) // b39_sigops_per_output
+ assert_equal(numTxes <= b39_outputs, True)
+
+ lastOutpoint = COutPoint(b40.vtx[1].sha256, 0)
+ new_txs = []
+ for i in range(1, numTxes+1):
+ tx = CTransaction()
+ tx.vout.append(CTxOut(1, CScript([OP_TRUE])))
+ tx.vin.append(CTxIn(lastOutpoint, b''))
+ # second input is corresponding P2SH output from b39
+ tx.vin.append(CTxIn(COutPoint(b39.vtx[i].sha256, 0), b''))
+ # Note: must pass the redeem_script (not p2sh_script) to the signature hash function
+ (sighash, err) = SignatureHash(redeem_script, tx, 1, SIGHASH_ALL)
+ sig = self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))
+ scriptSig = CScript([sig, redeem_script])
+
+ tx.vin[1].scriptSig = scriptSig
+ tx.rehash()
+ new_txs.append(tx)
+ lastOutpoint = COutPoint(tx.sha256, 0)
+
+ b40_sigops_to_fill = MAX_BLOCK_SIGOPS - (numTxes * b39_sigops_per_output + sigops) + 1
+ tx = CTransaction()
+ tx.vin.append(CTxIn(lastOutpoint, b''))
+ tx.vout.append(CTxOut(1, CScript([OP_CHECKSIG] * b40_sigops_to_fill)))
+ tx.rehash()
+ new_txs.append(tx)
+ update_block(40, new_txs)
+ yield rejected(RejectResult(16, b'bad-blk-sigops'))
+
+ # same as b40, but one less sigop
+ tip(39)
+ b41 = block(41, spend=None)
+ update_block(41, b40.vtx[1:-1])
+ b41_sigops_to_fill = b40_sigops_to_fill - 1
+ tx = CTransaction()
+ tx.vin.append(CTxIn(lastOutpoint, b''))
+ tx.vout.append(CTxOut(1, CScript([OP_CHECKSIG] * b41_sigops_to_fill)))
+ tx.rehash()
+ update_block(41, [tx])
+ yield accepted()
+
+ # Fork off of b39 to create a constant base again
+ #
+ # b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13)
+ # \-> b41 (12)
+ #
+ tip(39)
+ block(42, spend=out[12])
+ yield rejected()
+ save_spendable_output()
+
+ block(43, spend=out[13])
+ yield accepted()
+ save_spendable_output()
+
+
+ # Test a number of really invalid scenarios
+ #
+ # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b44 (14)
+ # \-> ??? (15)
+
+ # The next few blocks are going to be created "by hand" since they'll do funky things, such as having
+ # the first transaction be non-coinbase, etc. The purpose of b44 is to make sure this works.
+ height = self.block_heights[self.tip.sha256] + 1
+ coinbase = create_coinbase(height, self.coinbase_pubkey)
+ b44 = CBlock()
+ b44.nTime = self.tip.nTime + 1
+ b44.hashPrevBlock = self.tip.sha256
+ b44.nBits = 0x207fffff
+ b44.vtx.append(coinbase)
+ b44.hashMerkleRoot = b44.calc_merkle_root()
+ b44.solve()
+ self.tip = b44
+ self.block_heights[b44.sha256] = height
+ self.blocks[44] = b44
+ yield accepted()
+
+ # A block with a non-coinbase as the first tx
+ non_coinbase = create_tx(out[15].tx, out[15].n, 1)
+ b45 = CBlock()
+ b45.nTime = self.tip.nTime + 1
+ b45.hashPrevBlock = self.tip.sha256
+ b45.nBits = 0x207fffff
+ b45.vtx.append(non_coinbase)
+ b45.hashMerkleRoot = b45.calc_merkle_root()
+ b45.calc_sha256()
+ b45.solve()
+ self.block_heights[b45.sha256] = self.block_heights[self.tip.sha256]+1
+ self.tip = b45
+ self.blocks[45] = b45
+ yield rejected(RejectResult(16, b'bad-cb-missing'))
+
+ # A block with no txns
+ tip(44)
+ b46 = CBlock()
+ b46.nTime = b44.nTime+1
+ b46.hashPrevBlock = b44.sha256
+ b46.nBits = 0x207fffff
+ b46.vtx = []
+ b46.hashMerkleRoot = 0
+ b46.solve()
+ self.block_heights[b46.sha256] = self.block_heights[b44.sha256]+1
+ self.tip = b46
+ assert 46 not in self.blocks
+ self.blocks[46] = b46
+ s = ser_uint256(b46.hashMerkleRoot)
+ yield rejected(RejectResult(16, b'bad-blk-length'))
+
+ # A block with invalid work
+ tip(44)
+ b47 = block(47, solve=False)
+ target = uint256_from_compact(b47.nBits)
+ while b47.sha256 < target: #changed > to <
+ b47.nNonce += 1
+ b47.rehash()
+ yield rejected(RejectResult(16, b'high-hash'))
+
+ # A block with timestamp > 2 hrs in the future
+ tip(44)
+ b48 = block(48, solve=False)
+ b48.nTime = int(time.time()) + 60 * 60 * 3
+ b48.solve()
+ yield rejected(RejectResult(16, b'time-too-new'))
+
+ # A block with an invalid merkle hash
+ tip(44)
+ b49 = block(49)
+ b49.hashMerkleRoot += 1
+ b49.solve()
+ yield rejected(RejectResult(16, b'bad-txnmrklroot'))
+
+ # A block with an incorrect POW limit
+ tip(44)
+ b50 = block(50)
+ b50.nBits = b50.nBits - 1
+ b50.solve()
+ yield rejected(RejectResult(16, b'bad-diffbits'))
+
+ # A block with two coinbase txns
+ tip(44)
+ b51 = block(51)
+ cb2 = create_coinbase(51, self.coinbase_pubkey)
+ b51 = update_block(51, [cb2])
+ yield rejected(RejectResult(16, b'bad-cb-multiple'))
+
+ # A block w/ duplicate txns
+ # Note: txns have to be in the right position in the merkle tree to trigger this error
+ tip(44)
+ b52 = block(52, spend=out[15])
+ tx = create_tx(b52.vtx[1], 0, 1)
+ b52 = update_block(52, [tx, tx])
+ yield rejected(RejectResult(16, b'bad-txns-duplicate'))
+
+ # Test block timestamps
+ # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15)
+ # \-> b54 (15)
+ #
+ tip(43)
+ block(53, spend=out[14])
+ yield rejected() # rejected since b44 is at same height
+ save_spendable_output()
+
+ # invalid timestamp (b35 is 5 blocks back, so its time is MedianTimePast)
+ b54 = block(54, spend=out[15])
+ b54.nTime = b35.nTime - 1
+ b54.solve()
+ yield rejected(RejectResult(16, b'time-too-old'))
+
+ # valid timestamp
+ tip(53)
+ b55 = block(55, spend=out[15])
+ b55.nTime = b35.nTime
+ update_block(55, [])
+ yield accepted()
+ save_spendable_output()
+
+
+ # Test CVE-2012-2459
+ #
+ # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57p2 (16)
+ # \-> b57 (16)
+ # \-> b56p2 (16)
+ # \-> b56 (16)
+ #
+ # Merkle tree malleability (CVE-2012-2459): repeating sequences of transactions in a block without
+ # affecting the merkle root of a block, while still invalidating it.
+ # See: src/consensus/merkle.h
+ #
+ # b57 has three txns: coinbase, tx, tx1. The merkle root computation will duplicate tx.
+ # Result: OK
+ #
+ # b56 copies b57 but duplicates tx1 and does not recalculate the block hash. So it has a valid merkle
+ # root but duplicate transactions.
+ # Result: Fails
+ #
+ # b57p2 has six transactions in its merkle tree:
+ # - coinbase, tx, tx1, tx2, tx3, tx4
+ # Merkle root calculation will duplicate as necessary.
+ # Result: OK.
+ #
+ # b56p2 copies b57p2 but adds both tx3 and tx4. The purpose of the test is to make sure the code catches
+ # duplicate txns that are not next to one another with the "bad-txns-duplicate" error (which indicates
+ # that the error was caught early, avoiding a DOS vulnerability.)
+
+ # b57 - a good block with 2 txs, don't submit until end
+ tip(55)
+ b57 = block(57)
+ tx = create_and_sign_tx(out[16].tx, out[16].n, 1)
+ tx1 = create_tx(tx, 0, 1)
+ b57 = update_block(57, [tx, tx1])
+
+ # b56 - copy b57, add a duplicate tx
+ tip(55)
+ b56 = copy.deepcopy(b57)
+ self.blocks[56] = b56
+ assert_equal(len(b56.vtx),3)
+ b56 = update_block(56, [tx1])
+ assert_equal(b56.hash, b57.hash)
+ yield rejected(RejectResult(16, b'bad-txns-duplicate'))
+
+ # b57p2 - a good block with 6 tx'es, don't submit until end
+ tip(55)
+ b57p2 = block("57p2")
+ tx = create_and_sign_tx(out[16].tx, out[16].n, 1)
+ tx1 = create_tx(tx, 0, 1)
+ tx2 = create_tx(tx1, 0, 1)
+ tx3 = create_tx(tx2, 0, 1)
+ tx4 = create_tx(tx3, 0, 1)
+ b57p2 = update_block("57p2", [tx, tx1, tx2, tx3, tx4])
+
+ # b56p2 - copy b57p2, duplicate two non-consecutive tx's
+ tip(55)
+ b56p2 = copy.deepcopy(b57p2)
+ self.blocks["b56p2"] = b56p2
+ assert_equal(b56p2.hash, b57p2.hash)
+ assert_equal(len(b56p2.vtx),6)
+ b56p2 = update_block("b56p2", [tx3, tx4])
+ yield rejected(RejectResult(16, b'bad-txns-duplicate'))
+
+ tip("57p2")
+ yield accepted()
+
+ tip(57)
+ yield rejected() #rejected because 57p2 seen first
+ save_spendable_output()
+
+ # Test a few invalid tx types
+ #
+ # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
+ # \-> ??? (17)
+ #
+
+ # tx with prevout.n out of range
+ tip(57)
+ b58 = block(58, spend=out[17])
+ tx = CTransaction()
+ assert(len(out[17].tx.vout) < 42)
+ tx.vin.append(CTxIn(COutPoint(out[17].tx.sha256, 42), CScript([OP_TRUE]), 0xffffffff))
+ tx.vout.append(CTxOut(0, b""))
+ tx.calc_sha256()
+ b58 = update_block(58, [tx])
+ yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent'))
+
+ # tx with output value > input value out of range
+ tip(57)
+ b59 = block(59)
+ tx = create_and_sign_tx(out[17].tx, out[17].n, 51*COIN)
+ b59 = update_block(59, [tx])
+ yield rejected(RejectResult(16, b'bad-txns-in-belowout'))
+
+ # reset to good chain
+ tip(57)
+ b60 = block(60, spend=out[17])
+ yield accepted()
+ save_spendable_output()
+
+ # Test BIP30
+ #
+ # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
+ # \-> b61 (18)
+ #
+ # Blocks are not allowed to contain a transaction whose id matches that of an earlier,
+ # not-fully-spent transaction in the same chain. To test, make identical coinbases;
+ # the second one should be rejected.
+ #
+ tip(60)
+ b61 = block(61, spend=out[18])
+ b61.vtx[0].vin[0].scriptSig = b60.vtx[0].vin[0].scriptSig #equalize the coinbases
+ b61.vtx[0].rehash()
+ b61 = update_block(61, [])
+ assert_equal(b60.vtx[0].serialize(), b61.vtx[0].serialize())
+ yield rejected(RejectResult(16, b'bad-txns-BIP30'))
+
+
+ # Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests)
+ #
+ # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
+ # \-> b62 (18)
+ #
+ tip(60)
+ b62 = block(62)
+ tx = CTransaction()
+ tx.nLockTime = 0xffffffff #this locktime is non-final
+ assert(out[18].n < len(out[18].tx.vout))
+ tx.vin.append(CTxIn(COutPoint(out[18].tx.sha256, out[18].n))) # don't set nSequence
+ tx.vout.append(CTxOut(0, CScript([OP_TRUE])))
+ assert(tx.vin[0].nSequence < 0xffffffff)
+ tx.calc_sha256()
+ b62 = update_block(62, [tx])
+ yield rejected(RejectResult(16, b'bad-txns-nonfinal'))
+
+
+ # Test a non-final coinbase is also rejected
+ #
+ # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
+ # \-> b63 (-)
+ #
+ tip(60)
+ b63 = block(63)
+ b63.vtx[0].nLockTime = 0xffffffff
+ b63.vtx[0].vin[0].nSequence = 0xDEADBEEF
+ b63.vtx[0].rehash()
+ b63 = update_block(63, [])
+ yield rejected(RejectResult(16, b'bad-txns-nonfinal'))
+
+
+ # This checks that a block with a bloated VARINT between the block_header and the array of tx such that
+ # the block is > MAX_BLOCK_SIZE with the bloated varint, but <= MAX_BLOCK_SIZE without the bloated varint,
+ # does not cause a subsequent, identical block with canonical encoding to be rejected. The test does not
+ # care whether the bloated block is accepted or rejected; it only cares that the second block is accepted.
+ #
+ # What matters is that the receiving node should not reject the bloated block, and then reject the canonical
+ # block on the basis that it's the same as an already-rejected block (which would be a consensus failure.)
+ #
+ # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18)
+ # \
+ # b64a (18)
+ # b64a is a bloated block (non-canonical varint)
+ # b64 is a good block (same as b64 but w/ canonical varint)
+ #
+ tip(60)
+ regular_block = block("64a", spend=out[18])
+
+ # make it a "broken_block," with non-canonical serialization
+ b64a = CBrokenBlock(regular_block)
+ b64a.initialize(regular_block)
+ self.blocks["64a"] = b64a
+ self.tip = b64a
+ tx = CTransaction()
+
+ # use canonical serialization to calculate size
+ script_length = MAX_BLOCK_SIZE - len(b64a.normal_serialize()) - 69
+ script_output = CScript([b'\x00' * script_length])
+ tx.vout.append(CTxOut(0, script_output))
+ tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0)))
+ b64a = update_block("64a", [tx])
+ assert_equal(len(b64a.serialize()), MAX_BLOCK_SIZE + 8)
+ yield TestInstance([[self.tip, None]])
+
+ # comptool workaround: to make sure b64 is delivered, manually erase b64a from blockstore
+ self.test.block_store.erase(b64a.sha256)
+
+ tip(60)
+ b64 = CBlock(b64a)
+ b64.vtx = copy.deepcopy(b64a.vtx)
+ assert_equal(b64.hash, b64a.hash)
+ assert_equal(len(b64.serialize()), MAX_BLOCK_SIZE)
+ self.blocks[64] = b64
+ update_block(64, [])
+ yield accepted()
+ save_spendable_output()
+
+ # Spend an output created in the block itself
+ #
+ # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
+ #
+ tip(64)
+ b65 = block(65)
+ tx1 = create_and_sign_tx(out[19].tx, out[19].n, out[19].tx.vout[0].nValue)
+ tx2 = create_and_sign_tx(tx1, 0, 0)
+ update_block(65, [tx1, tx2])
+ yield accepted()
+ save_spendable_output()
+
+ # Attempt to spend an output created later in the same block
+ #
+ # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
+ # \-> b66 (20)
+ tip(65)
+ b66 = block(66)
+ tx1 = create_and_sign_tx(out[20].tx, out[20].n, out[20].tx.vout[0].nValue)
+ tx2 = create_and_sign_tx(tx1, 0, 1)
+ update_block(66, [tx2, tx1])
+ yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent'))
+
+ # Attempt to double-spend a transaction created in a block
+ #
+ # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
+ # \-> b67 (20)
+ #
+ #
+ tip(65)
+ b67 = block(67)
+ tx1 = create_and_sign_tx(out[20].tx, out[20].n, out[20].tx.vout[0].nValue)
+ tx2 = create_and_sign_tx(tx1, 0, 1)
+ tx3 = create_and_sign_tx(tx1, 0, 2)
+ update_block(67, [tx1, tx2, tx3])
+ yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent'))
+
+ # More tests of block subsidy
+ #
+ # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20)
+ # \-> b68 (20)
+ #
+ # b68 - coinbase with an extra 10 satoshis,
+ # creates a tx that has 9 satoshis from out[20] go to fees
+ # this fails because the coinbase is trying to claim 1 satoshi too much in fees
+ #
+ # b69 - coinbase with extra 10 satoshis, and a tx that gives a 10 satoshi fee
+ # this succeeds
+ #
+ tip(65)
+ b68 = block(68, additional_coinbase_value=10)
+ tx = create_and_sign_tx(out[20].tx, out[20].n, out[20].tx.vout[0].nValue-9)
+ update_block(68, [tx])
+ yield rejected(RejectResult(16, b'bad-cb-amount'))
+
+ tip(65)
+ b69 = block(69, additional_coinbase_value=10)
+ tx = create_and_sign_tx(out[20].tx, out[20].n, out[20].tx.vout[0].nValue-10)
+ update_block(69, [tx])
+ yield accepted()
+ save_spendable_output()
+
+ # Test spending the outpoint of a non-existent transaction
+ #
+ # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20)
+ # \-> b70 (21)
+ #
+ tip(69)
+ block(70, spend=out[21])
+ bogus_tx = CTransaction()
+ bogus_tx.sha256 = uint256_from_str(b"23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c")
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", 0xffffffff))
+ tx.vout.append(CTxOut(1, b""))
+ update_block(70, [tx])
+ yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent'))
+
+
+ # Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks)
+ #
+ # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21)
+ # \-> b71 (21)
+ #
+ # b72 is a good block.
+ # b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b71.
+ #
+ tip(69)
+ b72 = block(72)
+ tx1 = create_and_sign_tx(out[21].tx, out[21].n, 2)
+ tx2 = create_and_sign_tx(tx1, 0, 1)
+ b72 = update_block(72, [tx1, tx2]) # now tip is 72
+ b71 = copy.deepcopy(b72)
+ b71.vtx.append(tx2) # add duplicate tx2
+ self.block_heights[b71.sha256] = self.block_heights[b69.sha256] + 1 # b71 builds off b69
+ self.blocks[71] = b71
+
+ assert_equal(len(b71.vtx), 4)
+ assert_equal(len(b72.vtx), 3)
+ assert_equal(b72.sha256, b71.sha256)
+
+ tip(71)
+ yield rejected(RejectResult(16, b'bad-txns-duplicate'))
+ tip(72)
+ yield accepted()
+ save_spendable_output()
+
+
+ # Test some invalid scripts and MAX_BLOCK_SIGOPS
+ #
+ # -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21)
+ # \-> b** (22)
+ #
+
+ # b73 - tx with excessive sigops that are placed after an excessively large script element.
+ # The purpose of the test is to make sure those sigops are counted.
+ #
+ # script is a bytearray of size 20,526
+ #
+ # bytearray[0-19,998] : OP_CHECKSIG
+ # bytearray[19,999] : OP_PUSHDATA4
+ # bytearray[20,000-20,003]: 521 (max_script_element_size+1, in little-endian format)
+ # bytearray[20,004-20,525]: unread data (script_element)
+ # bytearray[20,526] : OP_CHECKSIG (this puts us over the limit)
+ #
+ tip(72)
+ b73 = block(73)
+ size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 + 1
+ a = bytearray([OP_CHECKSIG] * size)
+ a[MAX_BLOCK_SIGOPS - 1] = int("4e",16) # OP_PUSHDATA4
+
+ element_size = MAX_SCRIPT_ELEMENT_SIZE + 1
+ a[MAX_BLOCK_SIGOPS] = element_size % 256
+ a[MAX_BLOCK_SIGOPS+1] = element_size // 256
+ a[MAX_BLOCK_SIGOPS+2] = 0
+ a[MAX_BLOCK_SIGOPS+3] = 0
+
+ tx = create_and_sign_tx(out[22].tx, 0, 1, CScript(a))
+ b73 = update_block(73, [tx])
+ assert_equal(get_legacy_sigopcount_block(b73), MAX_BLOCK_SIGOPS+1)
+ yield rejected(RejectResult(16, b'bad-blk-sigops'))
+
+ # b74/75 - if we push an invalid script element, all prevous sigops are counted,
+ # but sigops after the element are not counted.
+ #
+ # The invalid script element is that the push_data indicates that
+ # there will be a large amount of data (0xffffff bytes), but we only
+ # provide a much smaller number. These bytes are CHECKSIGS so they would
+ # cause b75 to fail for excessive sigops, if those bytes were counted.
+ #
+ # b74 fails because we put MAX_BLOCK_SIGOPS+1 before the element
+ # b75 succeeds because we put MAX_BLOCK_SIGOPS before the element
+ #
+ #
+ tip(72)
+ b74 = block(74)
+ size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 42 # total = 20,561
+ a = bytearray([OP_CHECKSIG] * size)
+ a[MAX_BLOCK_SIGOPS] = 0x4e
+ a[MAX_BLOCK_SIGOPS+1] = 0xfe
+ a[MAX_BLOCK_SIGOPS+2] = 0xff
+ a[MAX_BLOCK_SIGOPS+3] = 0xff
+ a[MAX_BLOCK_SIGOPS+4] = 0xff
+ tx = create_and_sign_tx(out[22].tx, 0, 1, CScript(a))
+ b74 = update_block(74, [tx])
+ yield rejected(RejectResult(16, b'bad-blk-sigops'))
+
+ tip(72)
+ b75 = block(75)
+ size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 42
+ a = bytearray([OP_CHECKSIG] * size)
+ a[MAX_BLOCK_SIGOPS-1] = 0x4e
+ a[MAX_BLOCK_SIGOPS] = 0xff
+ a[MAX_BLOCK_SIGOPS+1] = 0xff
+ a[MAX_BLOCK_SIGOPS+2] = 0xff
+ a[MAX_BLOCK_SIGOPS+3] = 0xff
+ tx = create_and_sign_tx(out[22].tx, 0, 1, CScript(a))
+ b75 = update_block(75, [tx])
+ yield accepted()
+ save_spendable_output()
+
+ # Check that if we push an element filled with CHECKSIGs, they are not counted
+ tip(75)
+ b76 = block(76)
+ size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5
+ a = bytearray([OP_CHECKSIG] * size)
+ a[MAX_BLOCK_SIGOPS-1] = 0x4e # PUSHDATA4, but leave the following bytes as just checksigs
+ tx = create_and_sign_tx(out[23].tx, 0, 1, CScript(a))
+ b76 = update_block(76, [tx])
+ yield accepted()
+ save_spendable_output()
+
+ # Test transaction resurrection
+ #
+ # -> b77 (24) -> b78 (25) -> b79 (26)
+ # \-> b80 (25) -> b81 (26) -> b82 (27)
+ #
+ # b78 creates a tx, which is spent in b79. After b82, both should be in mempool
+ #
+ # The tx'es must be unsigned and pass the node's mempool policy. It is unsigned for the
+ # rather obscure reason that the Python signature code does not distinguish between
+ # Low-S and High-S values (whereas the bitcoin code has custom code which does so);
+ # as a result of which, the odds are 50% that the python code will use the right
+ # value and the transaction will be accepted into the mempool. Until we modify the
+ # test framework to support low-S signing, we are out of luck.
+ #
+ # To get around this issue, we construct transactions which are not signed and which
+ # spend to OP_TRUE. If the standard-ness rules change, this test would need to be
+ # updated. (Perhaps to spend to a P2SH OP_TRUE script)
+ #
+ tip(76)
+ block(77)
+ tx77 = create_and_sign_tx(out[24].tx, out[24].n, 10*COIN)
+ update_block(77, [tx77])
+ yield accepted()
+ save_spendable_output()
+
+ block(78)
+ tx78 = create_tx(tx77, 0, 9*COIN)
+ update_block(78, [tx78])
+ yield accepted()
+
+ block(79)
+ tx79 = create_tx(tx78, 0, 8*COIN)
+ update_block(79, [tx79])
+ yield accepted()
+
+ # mempool should be empty
+ assert_equal(len(self.nodes[0].getrawmempool()), 0)
+
+ tip(77)
+ block(80, spend=out[25])
+ yield rejected()
+ save_spendable_output()
+
+ block(81, spend=out[26])
+ yield rejected() # other chain is same length
+ save_spendable_output()
+
+ block(82, spend=out[27])
+ yield accepted() # now this chain is longer, triggers re-org
+ save_spendable_output()
+
+ # now check that tx78 and tx79 have been put back into the peer's mempool
+ mempool = self.nodes[0].getrawmempool()
+ assert_equal(len(mempool), 2)
+ assert(tx78.hash in mempool)
+ assert(tx79.hash in mempool)
+
+
+ # Test invalid opcodes in dead execution paths.
+ #
+ # -> b81 (26) -> b82 (27) -> b83 (28)
+ #
+ b83 = block(83)
+ op_codes = [OP_IF, OP_INVALIDOPCODE, OP_ELSE, OP_TRUE, OP_ENDIF]
+ script = CScript(op_codes)
+ tx1 = create_and_sign_tx(out[28].tx, out[28].n, out[28].tx.vout[0].nValue, script)
+
+ tx2 = create_and_sign_tx(tx1, 0, 0, CScript([OP_TRUE]))
+ tx2.vin[0].scriptSig = CScript([OP_FALSE])
+ tx2.rehash()
+
+ update_block(83, [tx1, tx2])
+ yield accepted()
+ save_spendable_output()
+
+
+ # Reorg on/off blocks that have OP_RETURN in them (and try to spend them)
+ #
+ # -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31)
+ # \-> b85 (29) -> b86 (30) \-> b89a (32)
+ #
+ #
+ b84 = block(84)
+ tx1 = create_tx(out[29].tx, out[29].n, 0, CScript([OP_RETURN]))
+ tx1.vout.append(CTxOut(0, CScript([OP_TRUE])))
+ tx1.vout.append(CTxOut(0, CScript([OP_TRUE])))
+ tx1.vout.append(CTxOut(0, CScript([OP_TRUE])))
+ tx1.vout.append(CTxOut(0, CScript([OP_TRUE])))
+ tx1.calc_sha256()
+ self.sign_tx(tx1, out[29].tx, out[29].n)
+ tx1.rehash()
+ tx2 = create_tx(tx1, 1, 0, CScript([OP_RETURN]))
+ tx2.vout.append(CTxOut(0, CScript([OP_RETURN])))
+ tx3 = create_tx(tx1, 2, 0, CScript([OP_RETURN]))
+ tx3.vout.append(CTxOut(0, CScript([OP_TRUE])))
+ tx4 = create_tx(tx1, 3, 0, CScript([OP_TRUE]))
+ tx4.vout.append(CTxOut(0, CScript([OP_RETURN])))
+ tx5 = create_tx(tx1, 4, 0, CScript([OP_RETURN]))
+
+ update_block(84, [tx1,tx2,tx3,tx4,tx5])
+ yield accepted()
+ save_spendable_output()
+
+ tip(83)
+ block(85, spend=out[29])
+ yield rejected()
+
+ block(86, spend=out[30])
+ yield accepted()
+
+ tip(84)
+ block(87, spend=out[30])
+ yield rejected()
+ save_spendable_output()
+
+ block(88, spend=out[31])
+ yield accepted()
+ save_spendable_output()
+
+ # trying to spend the OP_RETURN output is rejected
+ block("89a", spend=out[32])
+ tx = create_tx(tx1, 0, 0, CScript([OP_TRUE]))
+ update_block("89a", [tx])
+ yield rejected()
+
+
+ # Test re-org of a week's worth of blocks (1088 blocks)
+ # This test takes a minute or two and can be accomplished in memory
+ #
+ if self.options.runbarelyexpensive:
+ tip(88)
+ LARGE_REORG_SIZE = 1088
+ test1 = TestInstance(sync_every_block=False)
+ spend=out[32]
+ for i in range(89, LARGE_REORG_SIZE + 89):
+ b = block(i, spend)
+ tx = CTransaction()
+ script_length = MAX_BLOCK_SIZE - len(b.serialize()) - 69
+ script_output = CScript([b'\x00' * script_length])
+ tx.vout.append(CTxOut(0, script_output))
+ tx.vin.append(CTxIn(COutPoint(b.vtx[1].sha256, 0)))
+ b = update_block(i, [tx])
+ assert_equal(len(b.serialize()), MAX_BLOCK_SIZE)
+ test1.blocks_and_transactions.append([self.tip, True])
+ save_spendable_output()
+ spend = get_spendable_output()
+
+ yield test1
+ chain1_tip = i
+
+ # now create alt chain of same length
+ tip(88)
+ test2 = TestInstance(sync_every_block=False)
+ for i in range(89, LARGE_REORG_SIZE + 89):
+ block("alt"+str(i))
+ test2.blocks_and_transactions.append([self.tip, False])
+ yield test2
+
+ # extend alt chain to trigger re-org
+ block("alt" + str(chain1_tip + 1))
+ yield accepted()
+
+ # ... and re-org back to the first chain
+ tip(chain1_tip)
+ block(chain1_tip + 1)
+ yield rejected()
+ block(chain1_tip + 2)
+ yield accepted()
+
+ chain1_tip += 2
+
+
+
+if __name__ == '__main__':
+ FullBlockTest().main()
diff --git a/qa/rpc-tests/p2p-mempool.py b/qa/rpc-tests/p2p-mempool.py
new file mode 100755
index 0000000000..5d2daf39f8
--- /dev/null
+++ b/qa/rpc-tests/p2p-mempool.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.mininode import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import time
+
+class TestNode(NodeConnCB):
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.connection = None
+ self.ping_counter = 1
+ self.last_pong = msg_pong()
+ self.block_receive_map = {}
+
+ def add_connection(self, conn):
+ self.connection = conn
+ self.peer_disconnected = False
+
+ def on_inv(self, conn, message):
+ pass
+
+ # Track the last getdata message we receive (used in the test)
+ def on_getdata(self, conn, message):
+ self.last_getdata = message
+
+ def on_block(self, conn, message):
+ message.block.calc_sha256()
+ try:
+ self.block_receive_map[message.block.sha256] += 1
+ except KeyError as e:
+ self.block_receive_map[message.block.sha256] = 1
+
+ # Spin until verack message is received from the node.
+ # We use this to signal that our test can begin. This
+ # is called from the testing thread, so it needs to acquire
+ # the global lock.
+ def wait_for_verack(self):
+ def veracked():
+ return self.verack_received
+ return wait_until(veracked, timeout=10)
+
+ def wait_for_disconnect(self):
+ def disconnected():
+ return self.peer_disconnected
+ return wait_until(disconnected, timeout=10)
+
+ # Wrapper for the NodeConn's send_message function
+ def send_message(self, message):
+ self.connection.send_message(message)
+
+ def on_pong(self, conn, message):
+ self.last_pong = message
+
+ def on_close(self, conn):
+ self.peer_disconnected = True
+
+ # Sync up with the node after delivery of a block
+ def sync_with_ping(self, timeout=30):
+ def received_pong():
+ return (self.last_pong.nonce == self.ping_counter)
+ self.connection.send_message(msg_ping(nonce=self.ping_counter))
+ success = wait_until(received_pong, timeout)
+ self.ping_counter += 1
+ return success
+
+ def send_mempool(self):
+ self.lastInv = []
+ self.send_message(msg_mempool())
+
+class P2PMempoolTests(BitcoinTestFramework):
+ def setup_chain(self):
+ initialize_chain_clean(self.options.tmpdir, 2)
+
+ def setup_network(self):
+ # Start a node with maxuploadtarget of 200 MB (/24h)
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-peerbloomfilters=0"]))
+
+ def run_test(self):
+ #connect a mininode
+ aTestNode = TestNode()
+ node = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], aTestNode)
+ aTestNode.add_connection(node)
+ NetworkThread().start()
+ aTestNode.wait_for_verack()
+
+ #request mempool
+ aTestNode.send_mempool()
+ aTestNode.wait_for_disconnect()
+
+ #mininode must be disconnected at this point
+ assert_equal(len(self.nodes[0].getpeerinfo()), 0)
+
+if __name__ == '__main__':
+ P2PMempoolTests().main()
diff --git a/qa/rpc-tests/p2p-segwit.py b/qa/rpc-tests/p2p-segwit.py
new file mode 100755
index 0000000000..b30d41af92
--- /dev/null
+++ b/qa/rpc-tests/p2p-segwit.py
@@ -0,0 +1,1694 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.mininode import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.script import *
+from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment, WITNESS_COMMITMENT_HEADER
+from test_framework.key import CECKey, CPubKey
+import time
+import random
+from binascii import hexlify
+
+# The versionbit bit used to signal activation of SegWit
+VB_WITNESS_BIT = 1
+VB_PERIOD = 144
+VB_ACTIVATION_THRESHOLD = 108
+VB_TOP_BITS = 0x20000000
+
+MAX_SIGOP_COST = 80000
+
+'''
+SegWit p2p test.
+'''
+
+# Calculate the virtual size of a witness block:
+# (base + witness/4)
+def get_virtual_size(witness_block):
+ base_size = len(witness_block.serialize())
+ total_size = len(witness_block.serialize(with_witness=True))
+ # the "+3" is so we round up
+ vsize = int((3*base_size + total_size + 3)/4)
+ return vsize
+
+# Note: we can reduce code by using SingleNodeConnCB (in master, not 0.12)
+class TestNode(NodeConnCB):
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.connection = None
+ self.ping_counter = 1
+ self.last_pong = msg_pong(0)
+ self.sleep_time = 0.05
+ self.getdataset = set()
+ self.last_reject = None
+
+ def add_connection(self, conn):
+ self.connection = conn
+
+ # Wrapper for the NodeConn's send_message function
+ def send_message(self, message):
+ self.connection.send_message(message)
+
+ def on_inv(self, conn, message):
+ self.last_inv = message
+
+ def on_block(self, conn, message):
+ self.last_block = message.block
+ self.last_block.calc_sha256()
+
+ def on_getdata(self, conn, message):
+ for inv in message.inv:
+ self.getdataset.add(inv.hash)
+ self.last_getdata = message
+
+ def on_pong(self, conn, message):
+ self.last_pong = message
+
+ def on_reject(self, conn, message):
+ self.last_reject = message
+ #print (message)
+
+ # Syncing helpers
+ def sync(self, test_function, timeout=60):
+ while timeout > 0:
+ with mininode_lock:
+ if test_function():
+ return
+ time.sleep(self.sleep_time)
+ timeout -= self.sleep_time
+ raise AssertionError("Sync failed to complete")
+
+ def sync_with_ping(self, timeout=60):
+ self.send_message(msg_ping(nonce=self.ping_counter))
+ test_function = lambda: self.last_pong.nonce == self.ping_counter
+ self.sync(test_function, timeout)
+ self.ping_counter += 1
+ return
+
+ def wait_for_block(self, blockhash, timeout=60):
+ test_function = lambda: self.last_block != None and self.last_block.sha256 == blockhash
+ self.sync(test_function, timeout)
+ return
+
+ def wait_for_getdata(self, timeout=60):
+ test_function = lambda: self.last_getdata != None
+ self.sync(test_function, timeout)
+
+ def wait_for_inv(self, expected_inv, timeout=60):
+ test_function = lambda: self.last_inv != expected_inv
+ self.sync(test_function, timeout)
+
+ def announce_tx_and_wait_for_getdata(self, tx, timeout=60):
+ with mininode_lock:
+ self.last_getdata = None
+ self.send_message(msg_inv(inv=[CInv(1, tx.sha256)]))
+ self.wait_for_getdata(timeout)
+ return
+
+ def announce_block_and_wait_for_getdata(self, block, use_header, timeout=60):
+ with mininode_lock:
+ self.last_getdata = None
+ if use_header:
+ msg = msg_headers()
+ msg.headers = [ CBlockHeader(block) ]
+ self.send_message(msg)
+ else:
+ self.send_message(msg_inv(inv=[CInv(2, block.sha256)]))
+ self.wait_for_getdata()
+ return
+
+ def announce_block(self, block, use_header):
+ with mininode_lock:
+ self.last_getdata = None
+ if use_header:
+ msg = msg_headers()
+ msg.headers = [ CBlockHeader(block) ]
+ self.send_message(msg)
+ else:
+ self.send_message(msg_inv(inv=[CInv(2, block.sha256)]))
+
+ def request_block(self, blockhash, inv_type, timeout=60):
+ with mininode_lock:
+ self.last_block = None
+ self.send_message(msg_getdata(inv=[CInv(inv_type, blockhash)]))
+ self.wait_for_block(blockhash, timeout)
+ return self.last_block
+
+ def test_transaction_acceptance(self, tx, with_witness, accepted, reason=None):
+ tx_message = msg_tx(tx)
+ if with_witness:
+ tx_message = msg_witness_tx(tx)
+ self.send_message(tx_message)
+ self.sync_with_ping()
+ assert_equal(tx.hash in self.connection.rpc.getrawmempool(), accepted)
+ if (reason != None and not accepted):
+ # Check the rejection reason as well.
+ with mininode_lock:
+ assert_equal(self.last_reject.reason, reason)
+
+ # Test whether a witness block had the correct effect on the tip
+ def test_witness_block(self, block, accepted, with_witness=True):
+ if with_witness:
+ self.send_message(msg_witness_block(block))
+ else:
+ self.send_message(msg_block(block))
+ self.sync_with_ping()
+ assert_equal(self.connection.rpc.getbestblockhash() == block.hash, accepted)
+
+
+# Used to keep track of anyone-can-spend outputs that we can use in the tests
+class UTXO(object):
+ def __init__(self, sha256, n, nValue):
+ self.sha256 = sha256
+ self.n = n
+ self.nValue = nValue
+
+
+class SegWitTest(BitcoinTestFramework):
+ def setup_chain(self):
+ initialize_chain_clean(self.options.tmpdir, 3)
+
+ def add_options(self, parser):
+ parser.add_option("--oldbinary", dest="oldbinary",
+ default=None,
+ help="pre-segwit bitcoind binary for upgrade testing")
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-logtimemicros=1", "-whitelist=127.0.0.1"]))
+ # Start a node for testing IsStandard rules.
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-logtimemicros=1", "-whitelist=127.0.0.1", "-acceptnonstdtxn=0"]))
+ connect_nodes(self.nodes[0], 1)
+
+ # If an old bitcoind is given, do the upgrade-after-activation test.
+ self.test_upgrade = False
+ if (self.options.oldbinary != None):
+ self.nodes.append(start_node(2, self.options.tmpdir, ["-debug", "-whitelist=127.0.0.1"], binary=self.options.oldbinary))
+ connect_nodes(self.nodes[0], 2)
+ self.test_upgrade = True
+
+ ''' Helpers '''
+ # Build a block on top of node0's tip.
+ def build_next_block(self, nVersion=4):
+ tip = self.nodes[0].getbestblockhash()
+ height = self.nodes[0].getblockcount() + 1
+ block_time = self.nodes[0].getblockheader(tip)["mediantime"] + 1
+ block = create_block(int(tip, 16), create_coinbase(height), block_time)
+ block.nVersion = nVersion
+ block.rehash()
+ return block
+
+ # Adds list of transactions to block, adds witness commitment, then solves.
+ def update_witness_block_with_transactions(self, block, tx_list, nonce=0):
+ block.vtx.extend(tx_list)
+ add_witness_commitment(block, nonce)
+ block.solve()
+ return
+
+ ''' Individual tests '''
+ def test_witness_services(self):
+ print("\tVerifying NODE_WITNESS service bit")
+ assert((self.test_node.connection.nServices & NODE_WITNESS) != 0)
+
+
+ # See if sending a regular transaction works, and create a utxo
+ # to use in later tests.
+ def test_non_witness_transaction(self):
+ # Mine a block with an anyone-can-spend coinbase,
+ # let it mature, then try to spend it.
+ print("\tTesting non-witness transaction")
+ block = self.build_next_block(nVersion=1)
+ block.solve()
+ self.test_node.send_message(msg_block(block))
+ self.test_node.sync_with_ping() # make sure the block was processed
+ txid = block.vtx[0].sha256
+
+ self.nodes[0].generate(99) # let the block mature
+
+ # Create a transaction that spends the coinbase
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(txid, 0), b""))
+ tx.vout.append(CTxOut(49*100000000, CScript([OP_TRUE])))
+ tx.calc_sha256()
+
+ # Check that serializing it with or without witness is the same
+ # This is a sanity check of our testing framework.
+ assert_equal(msg_tx(tx).serialize(), msg_witness_tx(tx).serialize())
+
+ self.test_node.send_message(msg_witness_tx(tx))
+ self.test_node.sync_with_ping() # make sure the tx was processed
+ assert(tx.hash in self.nodes[0].getrawmempool())
+ # Save this transaction for later
+ self.utxo.append(UTXO(tx.sha256, 0, 49*100000000))
+ self.nodes[0].generate(1)
+
+
+ # Verify that blocks with witnesses are rejected before activation.
+ def test_unnecessary_witness_before_segwit_activation(self):
+ print("\tTesting behavior of unnecessary witnesses")
+ # For now, rely on earlier tests to have created at least one utxo for
+ # us to use
+ assert(len(self.utxo) > 0)
+ assert(get_bip9_status(self.nodes[0], 'segwit')['status'] != 'active')
+
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue-1000, CScript([OP_TRUE])))
+ tx.wit.vtxinwit.append(CTxInWitness())
+ tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([CScriptNum(1)])]
+
+ # Verify the hash with witness differs from the txid
+ # (otherwise our testing framework must be broken!)
+ tx.rehash()
+ assert(tx.sha256 != tx.calc_sha256(with_witness=True))
+
+ # Construct a segwit-signaling block that includes the transaction.
+ block = self.build_next_block(nVersion=(VB_TOP_BITS|(1 << VB_WITNESS_BIT)))
+ self.update_witness_block_with_transactions(block, [tx])
+ # Sending witness data before activation is not allowed (anti-spam
+ # rule).
+ self.test_node.test_witness_block(block, accepted=False)
+ # TODO: fix synchronization so we can test reject reason
+ # Right now, bitcoind delays sending reject messages for blocks
+ # until the future, making synchronization here difficult.
+ #assert_equal(self.test_node.last_reject.reason, "unexpected-witness")
+
+ # But it should not be permanently marked bad...
+ # Resend without witness information.
+ self.test_node.send_message(msg_block(block))
+ self.test_node.sync_with_ping()
+ assert_equal(self.nodes[0].getbestblockhash(), block.hash)
+
+ sync_blocks(self.nodes)
+
+ # Create a p2sh output -- this is so we can pass the standardness
+ # rules (an anyone-can-spend OP_TRUE would be rejected, if not wrapped
+ # in P2SH).
+ p2sh_program = CScript([OP_TRUE])
+ p2sh_pubkey = hash160(p2sh_program)
+ scriptPubKey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL])
+
+ # Now check that unnecessary witnesses can't be used to blind a node
+ # to a transaction, eg by violating standardness checks.
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
+ tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, scriptPubKey))
+ tx2.rehash()
+ self.test_node.test_transaction_acceptance(tx2, False, True)
+ self.nodes[0].generate(1)
+ sync_blocks(self.nodes)
+
+ # We'll add an unnecessary witness to this transaction that would cause
+ # it to be too large according to IsStandard.
+ tx3 = CTransaction()
+ tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), CScript([p2sh_program])))
+ tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, scriptPubKey))
+ tx3.wit.vtxinwit.append(CTxInWitness())
+ tx3.wit.vtxinwit[0].scriptWitness.stack = [b'a'*400000]
+ tx3.rehash()
+ self.std_node.test_transaction_acceptance(tx3, True, False, b'no-witness-yet')
+
+ # If we send without witness, it should be accepted.
+ self.std_node.test_transaction_acceptance(tx3, False, True)
+
+ # Now create a new anyone-can-spend utxo for the next test.
+ tx4 = CTransaction()
+ tx4.vin.append(CTxIn(COutPoint(tx3.sha256, 0), CScript([p2sh_program])))
+ tx4.vout.append(CTxOut(tx3.vout[0].nValue-1000, CScript([OP_TRUE])))
+ tx4.rehash()
+ self.test_node.test_transaction_acceptance(tx3, False, True)
+ self.test_node.test_transaction_acceptance(tx4, False, True)
+
+ self.nodes[0].generate(1)
+ sync_blocks(self.nodes)
+
+ # Update our utxo list; we spent the first entry.
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(tx4.sha256, 0, tx4.vout[0].nValue))
+
+
+ # Mine enough blocks for segwit's vb state to be 'started'.
+ def advance_to_segwit_started(self):
+ height = self.nodes[0].getblockcount()
+ # Will need to rewrite the tests here if we are past the first period
+ assert(height < VB_PERIOD - 1)
+ # Genesis block is 'defined'.
+ assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'defined')
+ # Advance to end of period, status should now be 'started'
+ self.nodes[0].generate(VB_PERIOD-height-1)
+ assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'started')
+
+ # Mine enough blocks to lock in segwit, but don't activate.
+ # TODO: we could verify that lockin only happens at the right threshold of
+ # signalling blocks, rather than just at the right period boundary.
+ def advance_to_segwit_lockin(self):
+ height = self.nodes[0].getblockcount()
+ assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'started')
+ # Advance to end of period, and verify lock-in happens at the end
+ self.nodes[0].generate(VB_PERIOD-1)
+ height = self.nodes[0].getblockcount()
+ assert((height % VB_PERIOD) == VB_PERIOD - 2)
+ assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'started')
+ self.nodes[0].generate(1)
+ assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'locked_in')
+
+
+ # Mine enough blocks to activate segwit.
+ # TODO: we could verify that activation only happens at the right threshold
+ # of signalling blocks, rather than just at the right period boundary.
+ def advance_to_segwit_active(self):
+ assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'locked_in')
+ height = self.nodes[0].getblockcount()
+ self.nodes[0].generate(VB_PERIOD - (height%VB_PERIOD) - 2)
+ assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'locked_in')
+ self.nodes[0].generate(1)
+ assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'active')
+
+
+ # This test can only be run after segwit has activated
+ def test_witness_commitments(self):
+ print("\tTesting witness commitments")
+
+ # First try a correct witness commitment.
+ block = self.build_next_block()
+ add_witness_commitment(block)
+ block.solve()
+
+ # Test the test -- witness serialization should be different
+ assert(msg_witness_block(block).serialize() != msg_block(block).serialize())
+
+ # This empty block should be valid.
+ self.test_node.test_witness_block(block, accepted=True)
+
+ # Try to tweak the nonce
+ block_2 = self.build_next_block()
+ add_witness_commitment(block_2, nonce=28)
+ block_2.solve()
+
+ # The commitment should have changed!
+ assert(block_2.vtx[0].vout[-1] != block.vtx[0].vout[-1])
+
+ # This should also be valid.
+ self.test_node.test_witness_block(block_2, accepted=True)
+
+ # Now test commitments with actual transactions
+ assert (len(self.utxo) > 0)
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+
+ # Let's construct a witness program
+ witness_program = CScript([OP_TRUE])
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+ tx.vout.append(CTxOut(self.utxo[0].nValue-1000, scriptPubKey))
+ tx.rehash()
+
+ # tx2 will spend tx1, and send back to a regular anyone-can-spend address
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
+ tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, witness_program))
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program]
+ tx2.rehash()
+
+ block_3 = self.build_next_block()
+ self.update_witness_block_with_transactions(block_3, [tx, tx2], nonce=1)
+ # Add an extra OP_RETURN output that matches the witness commitment template,
+ # even though it has extra data after the incorrect commitment.
+ # This block should fail.
+ block_3.vtx[0].vout.append(CTxOut(0, CScript([OP_RETURN, WITNESS_COMMITMENT_HEADER + ser_uint256(2), 10])))
+ block_3.vtx[0].rehash()
+ block_3.hashMerkleRoot = block_3.calc_merkle_root()
+ block_3.rehash()
+ block_3.solve()
+
+ self.test_node.test_witness_block(block_3, accepted=False)
+
+ # Add a different commitment with different nonce, but in the
+ # right location, and with some funds burned(!).
+ # This should succeed (nValue shouldn't affect finding the
+ # witness commitment).
+ add_witness_commitment(block_3, nonce=0)
+ block_3.vtx[0].vout[0].nValue -= 1
+ block_3.vtx[0].vout[-1].nValue += 1
+ block_3.vtx[0].rehash()
+ block_3.hashMerkleRoot = block_3.calc_merkle_root()
+ block_3.rehash()
+ assert(len(block_3.vtx[0].vout) == 4) # 3 OP_returns
+ block_3.solve()
+ self.test_node.test_witness_block(block_3, accepted=True)
+
+ # Finally test that a block with no witness transactions can
+ # omit the commitment.
+ block_4 = self.build_next_block()
+ tx3 = CTransaction()
+ tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), b""))
+ tx3.vout.append(CTxOut(tx.vout[0].nValue-1000, witness_program))
+ tx3.rehash()
+ block_4.vtx.append(tx3)
+ block_4.hashMerkleRoot = block_4.calc_merkle_root()
+ block_4.solve()
+ self.test_node.test_witness_block(block_4, with_witness=False, accepted=True)
+
+ # Update available utxo's for use in later test.
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue))
+
+
+ def test_block_malleability(self):
+ print("\tTesting witness block malleability")
+
+ # Make sure that a block that has too big a virtual size
+ # because of a too-large coinbase witness is not permanently
+ # marked bad.
+ block = self.build_next_block()
+ add_witness_commitment(block)
+ block.solve()
+
+ block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.append(b'a'*5000000)
+ assert(get_virtual_size(block) > MAX_BLOCK_SIZE)
+
+ # We can't send over the p2p network, because this is too big to relay
+ # TODO: repeat this test with a block that can be relayed
+ self.nodes[0].submitblock(bytes_to_hex_str(block.serialize(True)))
+
+ assert(self.nodes[0].getbestblockhash() != block.hash)
+
+ block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.pop()
+ assert(get_virtual_size(block) < MAX_BLOCK_SIZE)
+ self.nodes[0].submitblock(bytes_to_hex_str(block.serialize(True)))
+
+ assert(self.nodes[0].getbestblockhash() == block.hash)
+
+ # Now make sure that malleating the witness nonce doesn't
+ # result in a block permanently marked bad.
+ block = self.build_next_block()
+ add_witness_commitment(block)
+ block.solve()
+
+ # Change the nonce -- should not cause the block to be permanently
+ # failed
+ block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ ser_uint256(1) ]
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Changing the witness nonce doesn't change the block hash
+ block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ ser_uint256(0) ]
+ self.test_node.test_witness_block(block, accepted=True)
+
+
+ def test_witness_block_size(self):
+ print("\tTesting witness block size limit")
+ # TODO: Test that non-witness carrying blocks can't exceed 1MB
+ # Skipping this test for now; this is covered in p2p-fullblocktest.py
+
+ # Test that witness-bearing blocks are limited at ceil(base + wit/4) <= 1MB.
+ block = self.build_next_block()
+
+ assert(len(self.utxo) > 0)
+
+ # Create a P2WSH transaction.
+ # The witness program will be a bunch of OP_2DROP's, followed by OP_TRUE.
+ # This should give us plenty of room to tweak the spending tx's
+ # virtual size.
+ NUM_DROPS = 200 # 201 max ops per script!
+ NUM_OUTPUTS = 50
+
+ witness_program = CScript([OP_2DROP]*NUM_DROPS + [OP_TRUE])
+ witness_hash = uint256_from_str(sha256(witness_program))
+ scriptPubKey = CScript([OP_0, ser_uint256(witness_hash)])
+
+ prevout = COutPoint(self.utxo[0].sha256, self.utxo[0].n)
+ value = self.utxo[0].nValue
+
+ parent_tx = CTransaction()
+ parent_tx.vin.append(CTxIn(prevout, b""))
+ child_value = int(value/NUM_OUTPUTS)
+ for i in range(NUM_OUTPUTS):
+ parent_tx.vout.append(CTxOut(child_value, scriptPubKey))
+ parent_tx.vout[0].nValue -= 50000
+ assert(parent_tx.vout[0].nValue > 0)
+ parent_tx.rehash()
+
+ child_tx = CTransaction()
+ for i in range(NUM_OUTPUTS):
+ child_tx.vin.append(CTxIn(COutPoint(parent_tx.sha256, i), b""))
+ child_tx.vout = [CTxOut(value - 100000, CScript([OP_TRUE]))]
+ for i in range(NUM_OUTPUTS):
+ child_tx.wit.vtxinwit.append(CTxInWitness())
+ child_tx.wit.vtxinwit[-1].scriptWitness.stack = [b'a'*195]*(2*NUM_DROPS) + [witness_program]
+ child_tx.rehash()
+ self.update_witness_block_with_transactions(block, [parent_tx, child_tx])
+
+ vsize = get_virtual_size(block)
+ additional_bytes = (MAX_BLOCK_SIZE - vsize)*4
+ i = 0
+ while additional_bytes > 0:
+ # Add some more bytes to each input until we hit MAX_BLOCK_SIZE+1
+ extra_bytes = min(additional_bytes+1, 55)
+ block.vtx[-1].wit.vtxinwit[int(i/(2*NUM_DROPS))].scriptWitness.stack[i%(2*NUM_DROPS)] = b'a'*(195+extra_bytes)
+ additional_bytes -= extra_bytes
+ i += 1
+
+ block.vtx[0].vout.pop() # Remove old commitment
+ add_witness_commitment(block)
+ block.solve()
+ vsize = get_virtual_size(block)
+ assert_equal(vsize, MAX_BLOCK_SIZE + 1)
+ # Make sure that our test case would exceed the old max-network-message
+ # limit
+ assert(len(block.serialize(True)) > 2*1024*1024)
+
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Now resize the second transaction to make the block fit.
+ cur_length = len(block.vtx[-1].wit.vtxinwit[0].scriptWitness.stack[0])
+ block.vtx[-1].wit.vtxinwit[0].scriptWitness.stack[0] = b'a'*(cur_length-1)
+ block.vtx[0].vout.pop()
+ add_witness_commitment(block)
+ block.solve()
+ assert(get_virtual_size(block) == MAX_BLOCK_SIZE)
+
+ self.test_node.test_witness_block(block, accepted=True)
+
+ # Update available utxo's
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue))
+
+
+ # submitblock will try to add the nonce automatically, so that mining
+ # software doesn't need to worry about doing so itself.
+ def test_submit_block(self):
+ block = self.build_next_block()
+
+ # Try using a custom nonce and then don't supply it.
+ # This shouldn't possibly work.
+ add_witness_commitment(block, nonce=1)
+ block.vtx[0].wit = CTxWitness() # drop the nonce
+ block.solve()
+ self.nodes[0].submitblock(bytes_to_hex_str(block.serialize(True)))
+ assert(self.nodes[0].getbestblockhash() != block.hash)
+
+ # Now redo commitment with the standard nonce, but let bitcoind fill it in.
+ add_witness_commitment(block, nonce=0)
+ block.vtx[0].wit = CTxWitness()
+ block.solve()
+ self.nodes[0].submitblock(bytes_to_hex_str(block.serialize(True)))
+ assert_equal(self.nodes[0].getbestblockhash(), block.hash)
+
+ # This time, add a tx with non-empty witness, but don't supply
+ # the commitment.
+ block_2 = self.build_next_block()
+
+ add_witness_commitment(block_2)
+
+ block_2.solve()
+
+ # Drop commitment and nonce -- submitblock should not fill in.
+ block_2.vtx[0].vout.pop()
+ block_2.vtx[0].wit = CTxWitness()
+
+ self.nodes[0].submitblock(bytes_to_hex_str(block_2.serialize(True)))
+ # Tip should not advance!
+ assert(self.nodes[0].getbestblockhash() != block_2.hash)
+
+
+ # Consensus tests of extra witness data in a transaction.
+ def test_extra_witness_data(self):
+ print("\tTesting extra witness data in tx")
+
+ assert(len(self.utxo) > 0)
+
+ block = self.build_next_block()
+
+ witness_program = CScript([OP_DROP, OP_TRUE])
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+
+ # First try extra witness data on a tx that doesn't require a witness
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue-2000, scriptPubKey))
+ tx.vout.append(CTxOut(1000, CScript([OP_TRUE]))) # non-witness output
+ tx.wit.vtxinwit.append(CTxInWitness())
+ tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([])]
+ tx.rehash()
+ self.update_witness_block_with_transactions(block, [tx])
+
+ # Extra witness data should not be allowed.
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Try extra signature data. Ok if we're not spending a witness output.
+ block.vtx[1].wit.vtxinwit = []
+ block.vtx[1].vin[0].scriptSig = CScript([OP_0])
+ block.vtx[1].rehash()
+ add_witness_commitment(block)
+ block.solve()
+
+ self.test_node.test_witness_block(block, accepted=True)
+
+ # Now try extra witness/signature data on an input that DOES require a
+ # witness
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) # witness output
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 1), b"")) # non-witness
+ tx2.vout.append(CTxOut(tx.vout[0].nValue, CScript([OP_TRUE])))
+ tx2.wit.vtxinwit.extend([CTxInWitness(), CTxInWitness()])
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [ CScript([CScriptNum(1)]), CScript([CScriptNum(1)]), witness_program ]
+ tx2.wit.vtxinwit[1].scriptWitness.stack = [ CScript([OP_TRUE]) ]
+
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx2])
+
+ # This has extra witness data, so it should fail.
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Now get rid of the extra witness, but add extra scriptSig data
+ tx2.vin[0].scriptSig = CScript([OP_TRUE])
+ tx2.vin[1].scriptSig = CScript([OP_TRUE])
+ tx2.wit.vtxinwit[0].scriptWitness.stack.pop(0)
+ tx2.wit.vtxinwit[1].scriptWitness.stack = []
+ tx2.rehash()
+ add_witness_commitment(block)
+ block.solve()
+
+ # This has extra signature data for a witness input, so it should fail.
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Now get rid of the extra scriptsig on the witness input, and verify
+ # success (even with extra scriptsig data in the non-witness input)
+ tx2.vin[0].scriptSig = b""
+ tx2.rehash()
+ add_witness_commitment(block)
+ block.solve()
+
+ self.test_node.test_witness_block(block, accepted=True)
+
+ # Update utxo for later tests
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue))
+
+
+ def test_max_witness_push_length(self):
+ ''' Should only allow up to 520 byte pushes in witness stack '''
+ print("\tTesting maximum witness push size")
+ MAX_SCRIPT_ELEMENT_SIZE = 520
+ assert(len(self.utxo))
+
+ block = self.build_next_block()
+
+ witness_program = CScript([OP_DROP, OP_TRUE])
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue-1000, scriptPubKey))
+ tx.rehash()
+
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
+ tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE])))
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ # First try a 521-byte stack element
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [ b'a'*(MAX_SCRIPT_ELEMENT_SIZE+1), witness_program ]
+ tx2.rehash()
+
+ self.update_witness_block_with_transactions(block, [tx, tx2])
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Now reduce the length of the stack element
+ tx2.wit.vtxinwit[0].scriptWitness.stack[0] = b'a'*(MAX_SCRIPT_ELEMENT_SIZE)
+
+ add_witness_commitment(block)
+ block.solve()
+ self.test_node.test_witness_block(block, accepted=True)
+
+ # Update the utxo for later tests
+ self.utxo.pop()
+ self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue))
+
+ def test_max_witness_program_length(self):
+ # Can create witness outputs that are long, but can't be greater than
+ # 10k bytes to successfully spend
+ print("\tTesting maximum witness program length")
+ assert(len(self.utxo))
+ MAX_PROGRAM_LENGTH = 10000
+
+ # This program is 19 max pushes (9937 bytes), then 64 more opcode-bytes.
+ long_witness_program = CScript([b'a'*520]*19 + [OP_DROP]*63 + [OP_TRUE])
+ assert(len(long_witness_program) == MAX_PROGRAM_LENGTH+1)
+ long_witness_hash = sha256(long_witness_program)
+ long_scriptPubKey = CScript([OP_0, long_witness_hash])
+
+ block = self.build_next_block()
+
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue-1000, long_scriptPubKey))
+ tx.rehash()
+
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
+ tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE])))
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [b'a']*44 + [long_witness_program]
+ tx2.rehash()
+
+ self.update_witness_block_with_transactions(block, [tx, tx2])
+
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Try again with one less byte in the witness program
+ witness_program = CScript([b'a'*520]*19 + [OP_DROP]*62 + [OP_TRUE])
+ assert(len(witness_program) == MAX_PROGRAM_LENGTH)
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+
+ tx.vout[0] = CTxOut(tx.vout[0].nValue, scriptPubKey)
+ tx.rehash()
+ tx2.vin[0].prevout.hash = tx.sha256
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [b'a']*43 + [witness_program]
+ tx2.rehash()
+ block.vtx = [block.vtx[0]]
+ self.update_witness_block_with_transactions(block, [tx, tx2])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ self.utxo.pop()
+ self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue))
+
+
+ def test_witness_input_length(self):
+ ''' Ensure that vin length must match vtxinwit length '''
+ print("\tTesting witness input length")
+ assert(len(self.utxo))
+
+ witness_program = CScript([OP_DROP, OP_TRUE])
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+
+ # Create a transaction that splits our utxo into many outputs
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ nValue = self.utxo[0].nValue
+ for i in range(10):
+ tx.vout.append(CTxOut(int(nValue/10), scriptPubKey))
+ tx.vout[0].nValue -= 1000
+ assert(tx.vout[0].nValue >= 0)
+
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ # Try various ways to spend tx that should all break.
+ # This "broken" transaction serializer will not normalize
+ # the length of vtxinwit.
+ class BrokenCTransaction(CTransaction):
+ def serialize_with_witness(self):
+ flags = 0
+ if not self.wit.is_null():
+ flags |= 1
+ r = b""
+ r += struct.pack("<i", self.nVersion)
+ if flags:
+ dummy = []
+ r += ser_vector(dummy)
+ r += struct.pack("<B", flags)
+ r += ser_vector(self.vin)
+ r += ser_vector(self.vout)
+ if flags & 1:
+ r += self.wit.serialize()
+ r += struct.pack("<I", self.nLockTime)
+ return r
+
+ tx2 = BrokenCTransaction()
+ for i in range(10):
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, i), b""))
+ tx2.vout.append(CTxOut(nValue-3000, CScript([OP_TRUE])))
+
+ # First try using a too long vtxinwit
+ for i in range(11):
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[i].scriptWitness.stack = [b'a', witness_program]
+
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx2])
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Now try using a too short vtxinwit
+ tx2.wit.vtxinwit.pop()
+ tx2.wit.vtxinwit.pop()
+
+ block.vtx = [block.vtx[0]]
+ self.update_witness_block_with_transactions(block, [tx2])
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Now make one of the intermediate witnesses be incorrect
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[-1].scriptWitness.stack = [b'a', witness_program]
+ tx2.wit.vtxinwit[5].scriptWitness.stack = [ witness_program ]
+
+ block.vtx = [block.vtx[0]]
+ self.update_witness_block_with_transactions(block, [tx2])
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Fix the broken witness and the block should be accepted.
+ tx2.wit.vtxinwit[5].scriptWitness.stack = [b'a', witness_program]
+ block.vtx = [block.vtx[0]]
+ self.update_witness_block_with_transactions(block, [tx2])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ self.utxo.pop()
+ self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue))
+
+
+ def test_witness_tx_relay_before_segwit_activation(self):
+ print("\tTesting relay of witness transactions")
+ # Generate a transaction that doesn't require a witness, but send it
+ # with a witness. Should be rejected for premature-witness, but should
+ # not be added to recently rejected list.
+ assert(len(self.utxo))
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue-1000, CScript([OP_TRUE])))
+ tx.wit.vtxinwit.append(CTxInWitness())
+ tx.wit.vtxinwit[0].scriptWitness.stack = [ b'a' ]
+ tx.rehash()
+
+ tx_hash = tx.sha256
+ tx_value = tx.vout[0].nValue
+
+ # Verify that if a peer doesn't set nServices to include NODE_WITNESS,
+ # the getdata is just for the non-witness portion.
+ self.old_node.announce_tx_and_wait_for_getdata(tx)
+ assert(self.old_node.last_getdata.inv[0].type == 1)
+
+ # Since we haven't delivered the tx yet, inv'ing the same tx from
+ # a witness transaction ought not result in a getdata.
+ try:
+ self.test_node.announce_tx_and_wait_for_getdata(tx, timeout=2)
+ print("Error: duplicate tx getdata!")
+ assert(False)
+ except AssertionError as e:
+ pass
+
+ # Delivering this transaction with witness should fail (no matter who
+ # its from)
+ assert_equal(len(self.nodes[0].getrawmempool()), 0)
+ assert_equal(len(self.nodes[1].getrawmempool()), 0)
+ self.old_node.test_transaction_acceptance(tx, with_witness=True, accepted=False)
+ self.test_node.test_transaction_acceptance(tx, with_witness=True, accepted=False)
+
+ # But eliminating the witness should fix it
+ self.test_node.test_transaction_acceptance(tx, with_witness=False, accepted=True)
+
+ # Verify that inv's to test_node come with getdata's for non-witness tx's
+ # Just tweak the transaction, announce it, and verify we get a getdata
+ # for a normal tx
+ tx.vout[0].scriptPubKey = CScript([OP_TRUE, OP_TRUE])
+ tx.rehash()
+ self.test_node.announce_tx_and_wait_for_getdata(tx)
+ assert(self.test_node.last_getdata.inv[0].type == 1)
+
+ # Cleanup: mine the first transaction and update utxo
+ self.nodes[0].generate(1)
+ assert_equal(len(self.nodes[0].getrawmempool()), 0)
+
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(tx_hash, 0, tx_value))
+
+
+ # After segwit activates, verify that mempool:
+ # - rejects transactions with unnecessary/extra witnesses
+ # - accepts transactions with valid witnesses
+ # and that witness transactions are relayed to non-upgraded peers.
+ def test_tx_relay_after_segwit_activation(self):
+ print("\tTesting relay of witness transactions")
+ # Generate a transaction that doesn't require a witness, but send it
+ # with a witness. Should be rejected because we can't use a witness
+ # when spending a non-witness output.
+ assert(len(self.utxo))
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue-1000, CScript([OP_TRUE])))
+ tx.wit.vtxinwit.append(CTxInWitness())
+ tx.wit.vtxinwit[0].scriptWitness.stack = [ b'a' ]
+ tx.rehash()
+
+ tx_hash = tx.sha256
+ tx_value = tx.vout[0].nValue
+
+ # Verify that unnecessary witnesses are rejected.
+ self.test_node.announce_tx_and_wait_for_getdata(tx)
+ assert_equal(len(self.nodes[0].getrawmempool()), 0)
+ self.test_node.test_transaction_acceptance(tx, with_witness=True, accepted=False)
+
+ # Verify that removing the witness succeeds.
+ # Re-announcing won't result in a getdata for ~2.5 minutes, so just
+ # deliver the modified transaction.
+ self.test_node.test_transaction_acceptance(tx, with_witness=False, accepted=True)
+
+ # Now try to add extra witness data to a valid witness tx.
+ witness_program = CScript([OP_TRUE])
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx_hash, 0), b""))
+ tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, scriptPubKey))
+ tx2.rehash()
+
+ tx3 = CTransaction()
+ tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), b""))
+ tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, CScript([OP_TRUE])))
+ tx3.wit.vtxinwit.append(CTxInWitness())
+ tx3.wit.vtxinwit[0].scriptWitness.stack = [CScript([CScriptNum(1)]), witness_program ]
+ tx3.rehash()
+
+ self.test_node.test_transaction_acceptance(tx2, with_witness=True, accepted=True)
+ self.test_node.test_transaction_acceptance(tx3, with_witness=True, accepted=False)
+
+ # Get rid of the extra witness, and verify acceptance.
+ tx3.wit.vtxinwit[0].scriptWitness.stack = [ witness_program ]
+ # Also check that old_node gets a tx announcement, even though this is
+ # a witness transaction.
+ self.old_node.wait_for_inv(CInv(1, tx2.sha256)) # wait until tx2 was inv'ed
+ self.test_node.test_transaction_acceptance(tx3, with_witness=True, accepted=True)
+ self.old_node.wait_for_inv(CInv(1, tx3.sha256))
+
+ # Test that getrawtransaction returns correct witness information
+ # hash, size, vsize
+ raw_tx = self.nodes[0].getrawtransaction(tx3.hash, 1)
+ assert_equal(int(raw_tx["hash"], 16), tx3.calc_sha256(True))
+ assert_equal(raw_tx["size"], len(tx3.serialize_with_witness()))
+ vsize = (len(tx3.serialize_with_witness()) + 3*len(tx3.serialize_without_witness()) + 3) / 4
+ assert_equal(raw_tx["vsize"], vsize)
+ assert_equal(len(raw_tx["vin"][0]["txinwitness"]), 1)
+ assert_equal(raw_tx["vin"][0]["txinwitness"][0], hexlify(witness_program).decode('ascii'))
+ assert(vsize != raw_tx["size"])
+
+ # Cleanup: mine the transactions and update utxo for next test
+ self.nodes[0].generate(1)
+ assert_equal(len(self.nodes[0].getrawmempool()), 0)
+
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue))
+
+
+ # Test that block requests to NODE_WITNESS peer are with MSG_WITNESS_FLAG
+ # This is true regardless of segwit activation.
+ # Also test that we don't ask for blocks from unupgraded peers
+ def test_block_relay(self, segwit_activated):
+ print("\tTesting block relay")
+
+ blocktype = 2|MSG_WITNESS_FLAG if segwit_activated else 2
+
+ # test_node has set NODE_WITNESS, so all getdata requests should be for
+ # witness blocks.
+ # Test announcing a block via inv results in a getdata, and that
+ # announcing a version 4 or random VB block with a header results in a getdata
+ block1 = self.build_next_block()
+ block1.solve()
+
+ self.test_node.announce_block_and_wait_for_getdata(block1, use_header=False)
+ assert(self.test_node.last_getdata.inv[0].type == blocktype)
+ self.test_node.test_witness_block(block1, True)
+
+ block2 = self.build_next_block(nVersion=4)
+ block2.solve()
+
+ self.test_node.announce_block_and_wait_for_getdata(block2, use_header=True)
+ assert(self.test_node.last_getdata.inv[0].type == blocktype)
+ self.test_node.test_witness_block(block2, True)
+
+ block3 = self.build_next_block(nVersion=(VB_TOP_BITS | (1<<15)))
+ block3.solve()
+ self.test_node.announce_block_and_wait_for_getdata(block3, use_header=True)
+ assert(self.test_node.last_getdata.inv[0].type == blocktype)
+ self.test_node.test_witness_block(block3, True)
+
+ # Check that we can getdata for witness blocks or regular blocks,
+ # and the right thing happens.
+ if segwit_activated == False:
+ # Before activation, we should be able to request old blocks with
+ # or without witness, and they should be the same.
+ chain_height = self.nodes[0].getblockcount()
+ # Pick 10 random blocks on main chain, and verify that getdata's
+ # for MSG_BLOCK, MSG_WITNESS_BLOCK, and rpc getblock() are equal.
+ all_heights = list(range(chain_height+1))
+ random.shuffle(all_heights)
+ all_heights = all_heights[0:10]
+ for height in all_heights:
+ block_hash = self.nodes[0].getblockhash(height)
+ rpc_block = self.nodes[0].getblock(block_hash, False)
+ block_hash = int(block_hash, 16)
+ block = self.test_node.request_block(block_hash, 2)
+ wit_block = self.test_node.request_block(block_hash, 2|MSG_WITNESS_FLAG)
+ assert_equal(block.serialize(True), wit_block.serialize(True))
+ assert_equal(block.serialize(), hex_str_to_bytes(rpc_block))
+ else:
+ # After activation, witness blocks and non-witness blocks should
+ # be different. Verify rpc getblock() returns witness blocks, while
+ # getdata respects the requested type.
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [])
+ # This gives us a witness commitment.
+ assert(len(block.vtx[0].wit.vtxinwit) == 1)
+ assert(len(block.vtx[0].wit.vtxinwit[0].scriptWitness.stack) == 1)
+ self.test_node.test_witness_block(block, accepted=True)
+ # Now try to retrieve it...
+ rpc_block = self.nodes[0].getblock(block.hash, False)
+ non_wit_block = self.test_node.request_block(block.sha256, 2)
+ wit_block = self.test_node.request_block(block.sha256, 2|MSG_WITNESS_FLAG)
+ assert_equal(wit_block.serialize(True), hex_str_to_bytes(rpc_block))
+ assert_equal(wit_block.serialize(False), non_wit_block.serialize())
+ assert_equal(wit_block.serialize(True), block.serialize(True))
+
+ # Test size, vsize, cost
+ rpc_details = self.nodes[0].getblock(block.hash, True)
+ assert_equal(rpc_details["size"], len(block.serialize(True)))
+ assert_equal(rpc_details["strippedsize"], len(block.serialize(False)))
+ cost = 3*len(block.serialize(False)) + len(block.serialize(True))
+ assert_equal(rpc_details["cost"], cost)
+
+ # Upgraded node should not ask for blocks from unupgraded
+ block4 = self.build_next_block(nVersion=4)
+ block4.solve()
+ self.old_node.getdataset = set()
+ # Blocks can be requested via direct-fetch (immediately upon processing the announcement)
+ # or via parallel download (with an indeterminate delay from processing the announcement)
+ # so to test that a block is NOT requested, we could guess a time period to sleep for,
+ # and then check. We can avoid the sleep() by taking advantage of transaction getdata's
+ # being processed after block getdata's, and announce a transaction as well,
+ # and then check to see if that particular getdata has been received.
+ self.old_node.announce_block(block4, use_header=False)
+ self.old_node.announce_tx_and_wait_for_getdata(block4.vtx[0])
+ assert(block4.sha256 not in self.old_node.getdataset)
+
+ # Verify that future segwit upgraded transactions are non-standard,
+ # but valid in blocks. Can run this before and after segwit activation.
+ def test_segwit_versions(self):
+ print("\tTesting standardness/consensus for segwit versions (0-16)")
+ assert(len(self.utxo))
+ NUM_TESTS = 17 # will test OP_0, OP1, ..., OP_16
+ if (len(self.utxo) < NUM_TESTS):
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ split_value = (self.utxo[0].nValue - 4000) // NUM_TESTS
+ for i in range(NUM_TESTS):
+ tx.vout.append(CTxOut(split_value, CScript([OP_TRUE])))
+ tx.rehash()
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=True)
+ self.utxo.pop(0)
+ for i in range(NUM_TESTS):
+ self.utxo.append(UTXO(tx.sha256, i, split_value))
+
+ sync_blocks(self.nodes)
+ temp_utxo = []
+ tx = CTransaction()
+ count = 0
+ witness_program = CScript([OP_TRUE])
+ witness_hash = sha256(witness_program)
+ assert_equal(len(self.nodes[1].getrawmempool()), 0)
+ for version in list(range(OP_1, OP_16+1)) + [OP_0]:
+ count += 1
+ # First try to spend to a future version segwit scriptPubKey.
+ scriptPubKey = CScript([CScriptOp(version), witness_hash])
+ tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")]
+ tx.vout = [CTxOut(self.utxo[0].nValue-1000, scriptPubKey)]
+ tx.rehash()
+ self.std_node.test_transaction_acceptance(tx, with_witness=True, accepted=False)
+ self.test_node.test_transaction_acceptance(tx, with_witness=True, accepted=True)
+ self.utxo.pop(0)
+ temp_utxo.append(UTXO(tx.sha256, 0, tx.vout[0].nValue))
+
+ self.nodes[0].generate(1) # Mine all the transactions
+ sync_blocks(self.nodes)
+ assert(len(self.nodes[0].getrawmempool()) == 0)
+
+ # Finally, verify that version 0 -> version 1 transactions
+ # are non-standard
+ scriptPubKey = CScript([CScriptOp(OP_1), witness_hash])
+ tx2 = CTransaction()
+ tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")]
+ tx2.vout = [CTxOut(tx.vout[0].nValue-1000, scriptPubKey)]
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [ witness_program ]
+ tx2.rehash()
+ # Gets accepted to test_node, because standardness of outputs isn't
+ # checked with fRequireStandard
+ self.test_node.test_transaction_acceptance(tx2, with_witness=True, accepted=True)
+ self.std_node.test_transaction_acceptance(tx2, with_witness=True, accepted=False)
+ temp_utxo.pop() # last entry in temp_utxo was the output we just spent
+ temp_utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue))
+
+ # Spend everything in temp_utxo back to an OP_TRUE output.
+ tx3 = CTransaction()
+ total_value = 0
+ for i in temp_utxo:
+ tx3.vin.append(CTxIn(COutPoint(i.sha256, i.n), b""))
+ tx3.wit.vtxinwit.append(CTxInWitness())
+ total_value += i.nValue
+ tx3.wit.vtxinwit[-1].scriptWitness.stack = [witness_program]
+ tx3.vout.append(CTxOut(total_value - 1000, CScript([OP_TRUE])))
+ tx3.rehash()
+ # Spending a higher version witness output is not allowed by policy,
+ # even with fRequireStandard=false.
+ self.test_node.test_transaction_acceptance(tx3, with_witness=True, accepted=False)
+ self.test_node.sync_with_ping()
+ with mininode_lock:
+ assert(b"reserved for soft-fork upgrades" in self.test_node.last_reject.reason)
+
+ # Building a block with the transaction must be valid, however.
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx2, tx3])
+ self.test_node.test_witness_block(block, accepted=True)
+ sync_blocks(self.nodes)
+
+ # Add utxo to our list
+ self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue))
+
+
+ def test_premature_coinbase_witness_spend(self):
+ print("\tTesting premature coinbase witness spend")
+ block = self.build_next_block()
+ # Change the output of the block to be a witness output.
+ witness_program = CScript([OP_TRUE])
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+ block.vtx[0].vout[0].scriptPubKey = scriptPubKey
+ # This next line will rehash the coinbase and update the merkle
+ # root, and solve.
+ self.update_witness_block_with_transactions(block, [])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ spend_tx = CTransaction()
+ spend_tx.vin = [CTxIn(COutPoint(block.vtx[0].sha256, 0), b"")]
+ spend_tx.vout = [CTxOut(block.vtx[0].vout[0].nValue, witness_program)]
+ spend_tx.wit.vtxinwit.append(CTxInWitness())
+ spend_tx.wit.vtxinwit[0].scriptWitness.stack = [ witness_program ]
+ spend_tx.rehash()
+
+ # Now test a premature spend.
+ self.nodes[0].generate(98)
+ sync_blocks(self.nodes)
+ block2 = self.build_next_block()
+ self.update_witness_block_with_transactions(block2, [spend_tx])
+ self.test_node.test_witness_block(block2, accepted=False)
+
+ # Advancing one more block should allow the spend.
+ self.nodes[0].generate(1)
+ block2 = self.build_next_block()
+ self.update_witness_block_with_transactions(block2, [spend_tx])
+ self.test_node.test_witness_block(block2, accepted=True)
+ sync_blocks(self.nodes)
+
+
+ def test_signature_version_1(self):
+ print("\tTesting segwit signature hash version 1")
+ key = CECKey()
+ key.set_secretbytes(b"9")
+ pubkey = CPubKey(key.get_pubkey())
+
+ witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)])
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+
+ # First create a witness output for use in the tests.
+ assert(len(self.utxo))
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue-1000, scriptPubKey))
+ tx.rehash()
+
+ self.test_node.test_transaction_acceptance(tx, with_witness=True, accepted=True)
+ # Mine this transaction in preparation for following tests.
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=True)
+ sync_blocks(self.nodes)
+ self.utxo.pop(0)
+
+ # Add signature for a P2PK witness program.
+ def sign_P2PK_witness_input(script, txTo, inIdx, hashtype, value, key):
+ tx_hash = SegwitVersion1SignatureHash(script, txTo, inIdx, hashtype, value)
+ signature = key.sign(tx_hash) + chr(hashtype).encode('latin-1')
+ txTo.wit.vtxinwit[inIdx].scriptWitness.stack = [signature, script]
+ txTo.rehash()
+
+ # Test each hashtype
+ prev_utxo = UTXO(tx.sha256, 0, tx.vout[0].nValue)
+ for sigflag in [ 0, SIGHASH_ANYONECANPAY ]:
+ for hashtype in [SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE]:
+ hashtype |= sigflag
+ block = self.build_next_block()
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(prev_utxo.sha256, prev_utxo.n), b""))
+ tx.vout.append(CTxOut(prev_utxo.nValue - 1000, scriptPubKey))
+ tx.wit.vtxinwit.append(CTxInWitness())
+ # Too-large input value
+ sign_P2PK_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue+1, key)
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Too-small input value
+ sign_P2PK_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue-1, key)
+ block.vtx.pop() # remove last tx
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Now try correct value
+ sign_P2PK_witness_input(witness_program, tx, 0, hashtype, prev_utxo.nValue, key)
+ block.vtx.pop()
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ prev_utxo = UTXO(tx.sha256, 0, tx.vout[0].nValue)
+
+ # Test combinations of signature hashes.
+ # Split the utxo into a lot of outputs.
+ # Randomly choose up to 10 to spend, sign with different hashtypes, and
+ # output to a random number of outputs. Repeat NUM_TESTS times.
+ # Ensure that we've tested a situation where we use SIGHASH_SINGLE with
+ # an input index > number of outputs.
+ NUM_TESTS = 500
+ temp_utxos = []
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(prev_utxo.sha256, prev_utxo.n), b""))
+ split_value = prev_utxo.nValue // NUM_TESTS
+ for i in range(NUM_TESTS):
+ tx.vout.append(CTxOut(split_value, scriptPubKey))
+ tx.wit.vtxinwit.append(CTxInWitness())
+ sign_P2PK_witness_input(witness_program, tx, 0, SIGHASH_ALL, prev_utxo.nValue, key)
+ for i in range(NUM_TESTS):
+ temp_utxos.append(UTXO(tx.sha256, i, split_value))
+
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ block = self.build_next_block()
+ used_sighash_single_out_of_bounds = False
+ for i in range(NUM_TESTS):
+ # Choose random number of inputs to use.
+ num_inputs = random.randint(1, 10)
+ # Create a slight bias for producing more utxos
+ num_outputs = random.randint(1, 11)
+ random.shuffle(temp_utxos)
+ assert(len(temp_utxos) > num_inputs)
+ tx = CTransaction()
+ total_value = 0
+ for i in range(num_inputs):
+ tx.vin.append(CTxIn(COutPoint(temp_utxos[i].sha256, temp_utxos[i].n), b""))
+ tx.wit.vtxinwit.append(CTxInWitness())
+ total_value += temp_utxos[i].nValue
+ split_value = total_value // num_outputs
+ for i in range(num_outputs):
+ tx.vout.append(CTxOut(split_value, scriptPubKey))
+ for i in range(num_inputs):
+ # Now try to sign each input, using a random hashtype.
+ anyonecanpay = 0
+ if random.randint(0, 1):
+ anyonecanpay = SIGHASH_ANYONECANPAY
+ hashtype = random.randint(1, 3) | anyonecanpay
+ sign_P2PK_witness_input(witness_program, tx, i, hashtype, temp_utxos[i].nValue, key)
+ if (hashtype == SIGHASH_SINGLE and i >= num_outputs):
+ used_sighash_single_out_of_bounds = True
+ tx.rehash()
+ for i in range(num_outputs):
+ temp_utxos.append(UTXO(tx.sha256, i, split_value))
+ temp_utxos = temp_utxos[num_inputs:]
+
+ block.vtx.append(tx)
+
+ # Test the block periodically, if we're close to maxblocksize
+ if (get_virtual_size(block) > MAX_BLOCK_SIZE - 1000):
+ self.update_witness_block_with_transactions(block, [])
+ self.test_node.test_witness_block(block, accepted=True)
+ block = self.build_next_block()
+
+ if (not used_sighash_single_out_of_bounds):
+ print("WARNING: this test run didn't attempt SIGHASH_SINGLE with out-of-bounds index value")
+ # Test the transactions we've added to the block
+ if (len(block.vtx) > 1):
+ self.update_witness_block_with_transactions(block, [])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ # Now test witness version 0 P2PKH transactions
+ pubkeyhash = hash160(pubkey)
+ scriptPKH = CScript([OP_0, pubkeyhash])
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(temp_utxos[0].sha256, temp_utxos[0].n), b""))
+ tx.vout.append(CTxOut(temp_utxos[0].nValue, scriptPKH))
+ tx.wit.vtxinwit.append(CTxInWitness())
+ sign_P2PK_witness_input(witness_program, tx, 0, SIGHASH_ALL, temp_utxos[0].nValue, key)
+ tx2 = CTransaction()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
+ tx2.vout.append(CTxOut(tx.vout[0].nValue, CScript([OP_TRUE])))
+
+ script = CScript([CScriptOp(OP_DUP), CScriptOp(OP_HASH160), pubkeyhash, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_CHECKSIG)])
+ sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue)
+ signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
+
+ # Check that we can't have a scriptSig
+ tx2.vin[0].scriptSig = CScript([signature, pubkey])
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx, tx2])
+ self.test_node.test_witness_block(block, accepted=False)
+
+ # Move the signature to the witness.
+ block.vtx.pop()
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[0].scriptWitness.stack = [signature, pubkey]
+ tx2.vin[0].scriptSig = b""
+ tx2.rehash()
+
+ self.update_witness_block_with_transactions(block, [tx2])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ temp_utxos.pop(0)
+
+ # Update self.utxos for later tests. Just spend everything in
+ # temp_utxos to a corresponding entry in self.utxos
+ tx = CTransaction()
+ index = 0
+ for i in temp_utxos:
+ # Just spend to our usual anyone-can-spend output
+ # Use SIGHASH_SINGLE|SIGHASH_ANYONECANPAY so we can build up
+ # the signatures as we go.
+ tx.vin.append(CTxIn(COutPoint(i.sha256, i.n), b""))
+ tx.vout.append(CTxOut(i.nValue, CScript([OP_TRUE])))
+ tx.wit.vtxinwit.append(CTxInWitness())
+ sign_P2PK_witness_input(witness_program, tx, index, SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, i.nValue, key)
+ index += 1
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=True)
+
+ for i in range(len(tx.vout)):
+ self.utxo.append(UTXO(tx.sha256, i, tx.vout[i].nValue))
+
+
+ # Test P2SH wrapped witness programs.
+ def test_p2sh_witness(self, segwit_activated):
+ print("\tTesting P2SH witness transactions")
+
+ assert(len(self.utxo))
+
+ # Prepare the p2sh-wrapped witness output
+ witness_program = CScript([OP_DROP, OP_TRUE])
+ witness_hash = sha256(witness_program)
+ p2wsh_pubkey = CScript([OP_0, witness_hash])
+ p2sh_witness_hash = hash160(p2wsh_pubkey)
+ scriptPubKey = CScript([OP_HASH160, p2sh_witness_hash, OP_EQUAL])
+ scriptSig = CScript([p2wsh_pubkey]) # a push of the redeem script
+
+ # Fund the P2SH output
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ tx.vout.append(CTxOut(self.utxo[0].nValue-1000, scriptPubKey))
+ tx.rehash()
+
+ # Verify mempool acceptance and block validity
+ self.test_node.test_transaction_acceptance(tx, with_witness=False, accepted=True)
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [tx])
+ self.test_node.test_witness_block(block, accepted=True, with_witness=segwit_activated)
+ sync_blocks(self.nodes)
+
+ # Now test attempts to spend the output.
+ spend_tx = CTransaction()
+ spend_tx.vin.append(CTxIn(COutPoint(tx.sha256, 0), scriptSig))
+ spend_tx.vout.append(CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE])))
+ spend_tx.rehash()
+
+ # This transaction should not be accepted into the mempool pre- or
+ # post-segwit. Mempool acceptance will use SCRIPT_VERIFY_WITNESS which
+ # will require a witness to spend a witness program regardless of
+ # segwit activation. Note that older bitcoind's that are not
+ # segwit-aware would also reject this for failing CLEANSTACK.
+ self.test_node.test_transaction_acceptance(spend_tx, with_witness=False, accepted=False)
+
+ # Try to put the witness script in the scriptSig, should also fail.
+ spend_tx.vin[0].scriptSig = CScript([p2wsh_pubkey, b'a'])
+ spend_tx.rehash()
+ self.test_node.test_transaction_acceptance(spend_tx, with_witness=False, accepted=False)
+
+ # Now put the witness script in the witness, should succeed after
+ # segwit activates.
+ spend_tx.vin[0].scriptSig = scriptSig
+ spend_tx.rehash()
+ spend_tx.wit.vtxinwit.append(CTxInWitness())
+ spend_tx.wit.vtxinwit[0].scriptWitness.stack = [ b'a', witness_program ]
+
+ # Verify mempool acceptance
+ self.test_node.test_transaction_acceptance(spend_tx, with_witness=True, accepted=segwit_activated)
+ block = self.build_next_block()
+ self.update_witness_block_with_transactions(block, [spend_tx])
+
+ # If we're before activation, then sending this without witnesses
+ # should be valid. If we're after activation, then sending this with
+ # witnesses should be valid.
+ if segwit_activated:
+ self.test_node.test_witness_block(block, accepted=True)
+ else:
+ self.test_node.test_witness_block(block, accepted=True, with_witness=False)
+
+ # Update self.utxo
+ self.utxo.pop(0)
+ self.utxo.append(UTXO(spend_tx.sha256, 0, spend_tx.vout[0].nValue))
+
+ # Test the behavior of starting up a segwit-aware node after the softfork
+ # has activated. As segwit requires different block data than pre-segwit
+ # nodes would have stored, this requires special handling.
+ # To enable this test, pass --oldbinary=<path-to-pre-segwit-bitcoind> to
+ # the test.
+ def test_upgrade_after_activation(self, node, node_id):
+ print("\tTesting software upgrade after softfork activation")
+
+ assert(node_id != 0) # node0 is assumed to be a segwit-active bitcoind
+
+ # Make sure the nodes are all up
+ sync_blocks(self.nodes)
+
+ # Restart with the new binary
+ stop_node(node, node_id)
+ self.nodes[node_id] = start_node(node_id, self.options.tmpdir, ["-debug"])
+ connect_nodes(self.nodes[0], node_id)
+
+ sync_blocks(self.nodes)
+
+ # Make sure that this peer thinks segwit has activated.
+ assert(get_bip9_status(node, 'segwit')['status'] == "active")
+
+ # Make sure this peers blocks match those of node0.
+ height = node.getblockcount()
+ while height >= 0:
+ block_hash = node.getblockhash(height)
+ assert_equal(block_hash, self.nodes[0].getblockhash(height))
+ assert_equal(self.nodes[0].getblock(block_hash), node.getblock(block_hash))
+ height -= 1
+
+
+ def test_witness_sigops(self):
+ '''Ensure sigop counting is correct inside witnesses.'''
+ print("\tTesting sigops limit")
+
+ assert(len(self.utxo))
+
+ # Keep this under MAX_OPS_PER_SCRIPT (201)
+ witness_program = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKMULTISIG]*5 + [OP_CHECKSIG]*193 + [OP_ENDIF])
+ witness_hash = sha256(witness_program)
+ scriptPubKey = CScript([OP_0, witness_hash])
+
+ sigops_per_script = 20*5 + 193*1
+ # We'll produce 2 extra outputs, one with a program that would take us
+ # over max sig ops, and one with a program that would exactly reach max
+ # sig ops
+ outputs = (MAX_SIGOP_COST // sigops_per_script) + 2
+ extra_sigops_available = MAX_SIGOP_COST % sigops_per_script
+
+ # We chose the number of checkmultisigs/checksigs to make this work:
+ assert(extra_sigops_available < 100) # steer clear of MAX_OPS_PER_SCRIPT
+
+ # This script, when spent with the first
+ # N(=MAX_SIGOP_COST//sigops_per_script) outputs of our transaction,
+ # would push us just over the block sigop limit.
+ witness_program_toomany = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKSIG]*(extra_sigops_available + 1) + [OP_ENDIF])
+ witness_hash_toomany = sha256(witness_program_toomany)
+ scriptPubKey_toomany = CScript([OP_0, witness_hash_toomany])
+
+ # If we spend this script instead, we would exactly reach our sigop
+ # limit (for witness sigops).
+ witness_program_justright = CScript([OP_TRUE, OP_IF, OP_TRUE, OP_ELSE] + [OP_CHECKSIG]*(extra_sigops_available) + [OP_ENDIF])
+ witness_hash_justright = sha256(witness_program_justright)
+ scriptPubKey_justright = CScript([OP_0, witness_hash_justright])
+
+ # First split our available utxo into a bunch of outputs
+ split_value = self.utxo[0].nValue // outputs
+ tx = CTransaction()
+ tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b""))
+ for i in range(outputs):
+ tx.vout.append(CTxOut(split_value, scriptPubKey))
+ tx.vout[-2].scriptPubKey = scriptPubKey_toomany
+ tx.vout[-1].scriptPubKey = scriptPubKey_justright
+ tx.rehash()
+
+ block_1 = self.build_next_block()
+ self.update_witness_block_with_transactions(block_1, [tx])
+ self.test_node.test_witness_block(block_1, accepted=True)
+
+ tx2 = CTransaction()
+ # If we try to spend the first n-1 outputs from tx, that should be
+ # too many sigops.
+ total_value = 0
+ for i in range(outputs-1):
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, i), b""))
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[-1].scriptWitness.stack = [ witness_program ]
+ total_value += tx.vout[i].nValue
+ tx2.wit.vtxinwit[-1].scriptWitness.stack = [ witness_program_toomany ]
+ tx2.vout.append(CTxOut(total_value, CScript([OP_TRUE])))
+ tx2.rehash()
+
+ block_2 = self.build_next_block()
+ self.update_witness_block_with_transactions(block_2, [tx2])
+ self.test_node.test_witness_block(block_2, accepted=False)
+
+ # Try dropping the last input in tx2, and add an output that has
+ # too many sigops (contributing to legacy sigop count).
+ checksig_count = (extra_sigops_available // 4) + 1
+ scriptPubKey_checksigs = CScript([OP_CHECKSIG]*checksig_count)
+ tx2.vout.append(CTxOut(0, scriptPubKey_checksigs));
+ tx2.vin.pop()
+ tx2.wit.vtxinwit.pop()
+ tx2.vout[0].nValue -= tx.vout[-2].nValue
+ tx2.rehash()
+ block_3 = self.build_next_block()
+ self.update_witness_block_with_transactions(block_3, [tx2])
+ self.test_node.test_witness_block(block_3, accepted=False)
+
+ # If we drop the last checksig in this output, the tx should succeed.
+ block_4 = self.build_next_block()
+ tx2.vout[-1].scriptPubKey = CScript([OP_CHECKSIG]*(checksig_count-1))
+ tx2.rehash()
+ self.update_witness_block_with_transactions(block_4, [tx2])
+ self.test_node.test_witness_block(block_4, accepted=True)
+
+ # Reset the tip back down for the next test
+ sync_blocks(self.nodes)
+ for x in self.nodes:
+ x.invalidateblock(block_4.hash)
+
+ # Try replacing the last input of tx2 to be spending the last
+ # output of tx
+ block_5 = self.build_next_block()
+ tx2.vout.pop()
+ tx2.vin.append(CTxIn(COutPoint(tx.sha256, outputs-1), b""))
+ tx2.wit.vtxinwit.append(CTxInWitness())
+ tx2.wit.vtxinwit[-1].scriptWitness.stack = [ witness_program_justright ]
+ tx2.rehash()
+ self.update_witness_block_with_transactions(block_5, [tx2])
+ self.test_node.test_witness_block(block_5, accepted=True)
+
+ # TODO: test p2sh sigop counting
+
+ def test_getblocktemplate_before_lockin(self):
+ print("\tTesting getblocktemplate setting of segwit versionbit (before lockin)")
+ block_version = (self.nodes[0].getblocktemplate())['version']
+ assert_equal(block_version & (1 << VB_WITNESS_BIT), 0)
+
+ # Workaround:
+ # Can either change the tip, or change the mempool and wait 5 seconds
+ # to trigger a recomputation of getblocktemplate.
+ self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
+ # Using mocktime lets us avoid sleep()
+ self.nodes[0].setmocktime(int(time.time())+10)
+
+ block_version = self.nodes[0].getblocktemplate({"rules" : ["segwit"]})['version']
+ assert(block_version & (1 << VB_WITNESS_BIT) != 0)
+ self.nodes[0].setmocktime(0) # undo mocktime
+
+ def run_test(self):
+ # Setup the p2p connections and start up the network thread.
+ self.test_node = TestNode() # sets NODE_WITNESS|NODE_NETWORK
+ self.old_node = TestNode() # only NODE_NETWORK
+ self.std_node = TestNode() # for testing node1 (fRequireStandard=true)
+
+ self.p2p_connections = [self.test_node, self.old_node]
+
+ self.connections = []
+ self.connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.test_node, services=NODE_NETWORK|NODE_WITNESS))
+ self.connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.old_node, services=NODE_NETWORK))
+ self.connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], self.std_node, services=NODE_NETWORK|NODE_WITNESS))
+ self.test_node.add_connection(self.connections[0])
+ self.old_node.add_connection(self.connections[1])
+ self.std_node.add_connection(self.connections[2])
+
+ NetworkThread().start() # Start up network handling in another thread
+
+ # Keep a place to store utxo's that can be used in later tests
+ self.utxo = []
+
+ # Test logic begins here
+ self.test_node.wait_for_verack()
+
+ print("\nStarting tests before segwit lock in:")
+
+ self.test_witness_services() # Verifies NODE_WITNESS
+ self.test_non_witness_transaction() # non-witness tx's are accepted
+ self.test_unnecessary_witness_before_segwit_activation()
+ self.test_block_relay(segwit_activated=False)
+
+ # Advance to segwit being 'started'
+ self.advance_to_segwit_started()
+ self.test_getblocktemplate_before_lockin()
+
+ sync_blocks(self.nodes)
+
+ # At lockin, nothing should change.
+ print("\nTesting behavior post lockin, pre-activation")
+ self.advance_to_segwit_lockin()
+
+ # Retest unnecessary witnesses
+ self.test_unnecessary_witness_before_segwit_activation()
+ self.test_witness_tx_relay_before_segwit_activation()
+ self.test_block_relay(segwit_activated=False)
+ self.test_p2sh_witness(segwit_activated=False)
+
+ sync_blocks(self.nodes)
+
+ # Now activate segwit
+ print("\nTesting behavior after segwit activation")
+ self.advance_to_segwit_active()
+
+ sync_blocks(self.nodes)
+
+ # Test P2SH witness handling again
+ self.test_p2sh_witness(segwit_activated=True)
+ self.test_witness_commitments()
+ self.test_block_malleability()
+ self.test_witness_block_size()
+ self.test_submit_block()
+ self.test_extra_witness_data()
+ self.test_max_witness_push_length()
+ self.test_max_witness_program_length()
+ self.test_witness_input_length()
+ self.test_block_relay(segwit_activated=True)
+ self.test_tx_relay_after_segwit_activation()
+ self.test_segwit_versions()
+ self.test_premature_coinbase_witness_spend()
+ self.test_signature_version_1()
+ sync_blocks(self.nodes)
+ if self.test_upgrade:
+ self.test_upgrade_after_activation(self.nodes[2], 2)
+ else:
+ print("\tSkipping upgrade-after-activation test (use --oldbinary to enable)")
+ self.test_witness_sigops()
+
+
+if __name__ == '__main__':
+ SegWitTest().main()
diff --git a/qa/rpc-tests/p2p-versionbits-warning.py b/qa/rpc-tests/p2p-versionbits-warning.py
new file mode 100755
index 0000000000..962cafef0b
--- /dev/null
+++ b/qa/rpc-tests/p2p-versionbits-warning.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.mininode import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import time
+from test_framework.blocktools import create_block, create_coinbase
+
+'''
+Test version bits' warning system.
+
+Generate chains with block versions that appear to be signalling unknown
+soft-forks, and test that warning alerts are generated.
+'''
+
+VB_PERIOD = 144 # versionbits period length for regtest
+VB_THRESHOLD = 108 # versionbits activation threshold for regtest
+VB_TOP_BITS = 0x20000000
+VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment
+
+# TestNode: bare-bones "peer". Used mostly as a conduit for a test to sending
+# p2p messages to a node, generating the messages in the main testing logic.
+class TestNode(NodeConnCB):
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.connection = None
+ self.ping_counter = 1
+ self.last_pong = msg_pong()
+
+ def add_connection(self, conn):
+ self.connection = conn
+
+ def on_inv(self, conn, message):
+ pass
+
+ # Wrapper for the NodeConn's send_message function
+ def send_message(self, message):
+ self.connection.send_message(message)
+
+ def on_pong(self, conn, message):
+ self.last_pong = message
+
+ # Sync up with the node after delivery of a block
+ def sync_with_ping(self, timeout=30):
+ self.connection.send_message(msg_ping(nonce=self.ping_counter))
+ received_pong = False
+ sleep_time = 0.05
+ while not received_pong and timeout > 0:
+ time.sleep(sleep_time)
+ timeout -= sleep_time
+ with mininode_lock:
+ if self.last_pong.nonce == self.ping_counter:
+ received_pong = True
+ self.ping_counter += 1
+ return received_pong
+
+
+class VersionBitsWarningTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def setup_network(self):
+ self.nodes = []
+ self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt")
+ # Open and close to create zero-length file
+ with open(self.alert_filename, 'w') as f:
+ pass
+ self.node_options = ["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""]
+ self.nodes.append(start_node(0, self.options.tmpdir, self.node_options))
+
+ import re
+ self.vb_pattern = re.compile("^Warning.*versionbit")
+
+ # Send numblocks blocks via peer with nVersionToUse set.
+ def send_blocks_with_version(self, peer, numblocks, nVersionToUse):
+ tip = self.nodes[0].getbestblockhash()
+ height = self.nodes[0].getblockcount()
+ block_time = self.nodes[0].getblockheader(tip)["time"]+1
+ tip = int(tip, 16)
+
+ for i in range(numblocks):
+ block = create_block(tip, create_coinbase(height+1), block_time)
+ block.nVersion = nVersionToUse
+ block.solve()
+ peer.send_message(msg_block(block))
+ block_time += 1
+ height += 1
+ tip = block.sha256
+ peer.sync_with_ping()
+
+ def test_versionbits_in_alert_file(self):
+ with open(self.alert_filename, 'r') as f:
+ alert_text = f.read()
+ assert(self.vb_pattern.match(alert_text))
+
+ def run_test(self):
+ # Setup the p2p connection and start up the network thread.
+ test_node = TestNode()
+
+ connections = []
+ connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
+ test_node.add_connection(connections[0])
+
+ NetworkThread().start() # Start up network handling in another thread
+
+ # Test logic begins here
+ test_node.wait_for_verack()
+
+ # 1. Have the node mine one period worth of blocks
+ self.nodes[0].generate(VB_PERIOD)
+
+ # 2. Now build one period of blocks on the tip, with < VB_THRESHOLD
+ # blocks signaling some unknown bit.
+ nVersion = VB_TOP_BITS | (1<<VB_UNKNOWN_BIT)
+ self.send_blocks_with_version(test_node, VB_THRESHOLD-1, nVersion)
+
+ # Fill rest of period with regular version blocks
+ self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD + 1)
+ # Check that we're not getting any versionbit-related errors in
+ # getinfo()
+ assert(not self.vb_pattern.match(self.nodes[0].getinfo()["errors"]))
+
+ # 3. Now build one period of blocks with >= VB_THRESHOLD blocks signaling
+ # some unknown bit
+ self.send_blocks_with_version(test_node, VB_THRESHOLD, nVersion)
+ self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD)
+ # Might not get a versionbits-related alert yet, as we should
+ # have gotten a different alert due to more than 51/100 blocks
+ # being of unexpected version.
+ # Check that getinfo() shows some kind of error.
+ assert(len(self.nodes[0].getinfo()["errors"]) != 0)
+
+ # Mine a period worth of expected blocks so the generic block-version warning
+ # is cleared, and restart the node. This should move the versionbit state
+ # to ACTIVE.
+ self.nodes[0].generate(VB_PERIOD)
+ stop_node(self.nodes[0], 0)
+ wait_bitcoinds()
+ # Empty out the alert file
+ with open(self.alert_filename, 'w') as f:
+ pass
+ self.nodes[0] = start_node(0, self.options.tmpdir, ["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""])
+
+ # Connecting one block should be enough to generate an error.
+ self.nodes[0].generate(1)
+ assert(len(self.nodes[0].getinfo()["errors"]) != 0)
+ stop_node(self.nodes[0], 0)
+ wait_bitcoinds()
+ self.test_versionbits_in_alert_file()
+
+ # Test framework expects the node to still be running...
+ self.nodes[0] = start_node(0, self.options.tmpdir, ["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""])
+
+
+if __name__ == '__main__':
+ VersionBitsWarningTest().main()
diff --git a/qa/rpc-tests/prioritise_transaction.py b/qa/rpc-tests/prioritise_transaction.py
new file mode 100755
index 0000000000..e1771231c0
--- /dev/null
+++ b/qa/rpc-tests/prioritise_transaction.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test PrioritiseTransaction code
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.mininode import COIN, MAX_BLOCK_SIZE
+
+class PrioritiseTransactionTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ self.txouts = gen_return_txouts()
+
+ def setup_network(self):
+ self.nodes = []
+ self.is_network_split = False
+
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-printpriority=1"]))
+ self.relayfee = self.nodes[0].getnetworkinfo()['relayfee']
+
+ def run_test(self):
+ utxo_count = 90
+ utxos = create_confirmed_utxos(self.relayfee, self.nodes[0], utxo_count)
+ base_fee = self.relayfee*100 # our transactions are smaller than 100kb
+ txids = []
+
+ # Create 3 batches of transactions at 3 different fee rate levels
+ range_size = utxo_count // 3
+ for i in range(3):
+ txids.append([])
+ start_range = i * range_size
+ end_range = start_range + range_size
+ txids[i] = create_lots_of_big_transactions(self.nodes[0], self.txouts, utxos[start_range:end_range], (i+1)*base_fee)
+
+ # Make sure that the size of each group of transactions exceeds
+ # MAX_BLOCK_SIZE -- otherwise the test needs to be revised to create
+ # more transactions.
+ mempool = self.nodes[0].getrawmempool(True)
+ sizes = [0, 0, 0]
+ for i in range(3):
+ for j in txids[i]:
+ assert(j in mempool)
+ sizes[i] += mempool[j]['size']
+ assert(sizes[i] > MAX_BLOCK_SIZE) # Fail => raise utxo_count
+
+ # add a fee delta to something in the cheapest bucket and make sure it gets mined
+ # also check that a different entry in the cheapest bucket is NOT mined (lower
+ # the priority to ensure its not mined due to priority)
+ self.nodes[0].prioritisetransaction(txids[0][0], 0, int(3*base_fee*COIN))
+ self.nodes[0].prioritisetransaction(txids[0][1], -1e15, 0)
+
+ self.nodes[0].generate(1)
+
+ mempool = self.nodes[0].getrawmempool()
+ print("Assert that prioritised transaction was mined")
+ assert(txids[0][0] not in mempool)
+ assert(txids[0][1] in mempool)
+
+ high_fee_tx = None
+ for x in txids[2]:
+ if x not in mempool:
+ high_fee_tx = x
+
+ # Something high-fee should have been mined!
+ assert(high_fee_tx != None)
+
+ # Add a prioritisation before a tx is in the mempool (de-prioritising a
+ # high-fee transaction so that it's now low fee).
+ self.nodes[0].prioritisetransaction(high_fee_tx, -1e15, -int(2*base_fee*COIN))
+
+ # Add everything back to mempool
+ self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
+
+ # Check to make sure our high fee rate tx is back in the mempool
+ mempool = self.nodes[0].getrawmempool()
+ assert(high_fee_tx in mempool)
+
+ # Now verify the modified-high feerate transaction isn't mined before
+ # the other high fee transactions. Keep mining until our mempool has
+ # decreased by all the high fee size that we calculated above.
+ while (self.nodes[0].getmempoolinfo()['bytes'] > sizes[0] + sizes[1]):
+ self.nodes[0].generate(1)
+
+ # High fee transaction should not have been mined, but other high fee rate
+ # transactions should have been.
+ mempool = self.nodes[0].getrawmempool()
+ print("Assert that de-prioritised transaction is still in mempool")
+ assert(high_fee_tx in mempool)
+ for x in txids[2]:
+ if (x != high_fee_tx):
+ assert(x not in mempool)
+
+ # Create a free, low priority transaction. Should be rejected.
+ utxo_list = self.nodes[0].listunspent()
+ assert(len(utxo_list) > 0)
+ utxo = utxo_list[0]
+
+ inputs = []
+ outputs = {}
+ inputs.append({"txid" : utxo["txid"], "vout" : utxo["vout"]})
+ outputs[self.nodes[0].getnewaddress()] = utxo["amount"] - self.relayfee
+ raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
+ tx_hex = self.nodes[0].signrawtransaction(raw_tx)["hex"]
+ txid = self.nodes[0].sendrawtransaction(tx_hex)
+
+ # A tx that spends an in-mempool tx has 0 priority, so we can use it to
+ # test the effect of using prioritise transaction for mempool acceptance
+ inputs = []
+ inputs.append({"txid": txid, "vout": 0})
+ outputs = {}
+ outputs[self.nodes[0].getnewaddress()] = utxo["amount"] - self.relayfee
+ raw_tx2 = self.nodes[0].createrawtransaction(inputs, outputs)
+ tx2_hex = self.nodes[0].signrawtransaction(raw_tx2)["hex"]
+ tx2_id = self.nodes[0].decoderawtransaction(tx2_hex)["txid"]
+
+ try:
+ self.nodes[0].sendrawtransaction(tx2_hex)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26) # insufficient fee
+ assert(tx2_id not in self.nodes[0].getrawmempool())
+ else:
+ assert(False)
+
+ # This is a less than 1000-byte transaction, so just set the fee
+ # to be the minimum for a 1000 byte transaction and check that it is
+ # accepted.
+ self.nodes[0].prioritisetransaction(tx2_id, 0, int(self.relayfee*COIN))
+
+ print("Assert that prioritised free transaction is accepted to mempool")
+ assert_equal(self.nodes[0].sendrawtransaction(tx2_hex), tx2_id)
+ assert(tx2_id in self.nodes[0].getrawmempool())
+
+if __name__ == '__main__':
+ PrioritiseTransactionTest().main()
diff --git a/qa/rpc-tests/proxy_test.py b/qa/rpc-tests/proxy_test.py
new file mode 100755
index 0000000000..27160cae07
--- /dev/null
+++ b/qa/rpc-tests/proxy_test.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import socket
+
+from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.netutil import test_ipv6_local
+'''
+Test plan:
+- Start bitcoind's with different proxy configurations
+- Use addnode to initiate connections
+- Verify that proxies are connected to, and the right connection command is given
+- Proxy configurations to test on bitcoind side:
+ - `-proxy` (proxy everything)
+ - `-onion` (proxy just onions)
+ - `-proxyrandomize` Circuit randomization
+- Proxy configurations to test on proxy side,
+ - support no authentication (other proxy)
+ - support no authentication + user/pass authentication (Tor)
+ - proxy on IPv6
+
+- Create various proxies (as threads)
+- Create bitcoinds that connect to them
+- Manipulate the bitcoinds using addnode (onetry) an observe effects
+
+addnode connect to IPv4
+addnode connect to IPv6
+addnode connect to onion
+addnode connect to generic DNS name
+'''
+
+
+class ProxyTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.setup_clean_chain = False
+
+ self.have_ipv6 = test_ipv6_local()
+ # Create two proxies on different ports
+ # ... one unauthenticated
+ self.conf1 = Socks5Configuration()
+ self.conf1.addr = ('127.0.0.1', 13000 + (os.getpid() % 1000))
+ self.conf1.unauth = True
+ self.conf1.auth = False
+ # ... one supporting authenticated and unauthenticated (Tor)
+ self.conf2 = Socks5Configuration()
+ self.conf2.addr = ('127.0.0.1', 14000 + (os.getpid() % 1000))
+ self.conf2.unauth = True
+ self.conf2.auth = True
+ if self.have_ipv6:
+ # ... one on IPv6 with similar configuration
+ self.conf3 = Socks5Configuration()
+ self.conf3.af = socket.AF_INET6
+ self.conf3.addr = ('::1', 15000 + (os.getpid() % 1000))
+ self.conf3.unauth = True
+ self.conf3.auth = True
+ else:
+ print("Warning: testing without local IPv6 support")
+
+ self.serv1 = Socks5Server(self.conf1)
+ self.serv1.start()
+ self.serv2 = Socks5Server(self.conf2)
+ self.serv2.start()
+ if self.have_ipv6:
+ self.serv3 = Socks5Server(self.conf3)
+ self.serv3.start()
+
+ def setup_nodes(self):
+ # Note: proxies are not used to connect to local nodes
+ # this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost
+ args = [
+ ['-listen', '-debug=net', '-debug=proxy', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
+ ['-listen', '-debug=net', '-debug=proxy', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'],
+ ['-listen', '-debug=net', '-debug=proxy', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
+ []
+ ]
+ if self.have_ipv6:
+ args[3] = ['-listen', '-debug=net', '-debug=proxy', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion']
+ return start_nodes(self.num_nodes, self.options.tmpdir, extra_args=args)
+
+ def node_test(self, node, proxies, auth, test_onion=True):
+ rv = []
+ # Test: outgoing IPv4 connection through node
+ node.addnode("15.61.23.23:1234", "onetry")
+ cmd = proxies[0].queue.get()
+ assert(isinstance(cmd, Socks5Command))
+ # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
+ assert_equal(cmd.atyp, AddressType.DOMAINNAME)
+ assert_equal(cmd.addr, b"15.61.23.23")
+ assert_equal(cmd.port, 1234)
+ if not auth:
+ assert_equal(cmd.username, None)
+ assert_equal(cmd.password, None)
+ rv.append(cmd)
+
+ if self.have_ipv6:
+ # Test: outgoing IPv6 connection through node
+ node.addnode("[1233:3432:2434:2343:3234:2345:6546:4534]:5443", "onetry")
+ cmd = proxies[1].queue.get()
+ assert(isinstance(cmd, Socks5Command))
+ # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
+ assert_equal(cmd.atyp, AddressType.DOMAINNAME)
+ assert_equal(cmd.addr, b"1233:3432:2434:2343:3234:2345:6546:4534")
+ assert_equal(cmd.port, 5443)
+ if not auth:
+ assert_equal(cmd.username, None)
+ assert_equal(cmd.password, None)
+ rv.append(cmd)
+
+ if test_onion:
+ # Test: outgoing onion connection through node
+ node.addnode("bitcoinostk4e4re.onion:8333", "onetry")
+ cmd = proxies[2].queue.get()
+ assert(isinstance(cmd, Socks5Command))
+ assert_equal(cmd.atyp, AddressType.DOMAINNAME)
+ assert_equal(cmd.addr, b"bitcoinostk4e4re.onion")
+ assert_equal(cmd.port, 8333)
+ if not auth:
+ assert_equal(cmd.username, None)
+ assert_equal(cmd.password, None)
+ rv.append(cmd)
+
+ # Test: outgoing DNS name connection through node
+ node.addnode("node.noumenon:8333", "onetry")
+ cmd = proxies[3].queue.get()
+ assert(isinstance(cmd, Socks5Command))
+ assert_equal(cmd.atyp, AddressType.DOMAINNAME)
+ assert_equal(cmd.addr, b"node.noumenon")
+ assert_equal(cmd.port, 8333)
+ if not auth:
+ assert_equal(cmd.username, None)
+ assert_equal(cmd.password, None)
+ rv.append(cmd)
+
+ return rv
+
+ def run_test(self):
+ # basic -proxy
+ self.node_test(self.nodes[0], [self.serv1, self.serv1, self.serv1, self.serv1], False)
+
+ # -proxy plus -onion
+ self.node_test(self.nodes[1], [self.serv1, self.serv1, self.serv2, self.serv1], False)
+
+ # -proxy plus -onion, -proxyrandomize
+ rv = self.node_test(self.nodes[2], [self.serv2, self.serv2, self.serv2, self.serv2], True)
+ # Check that credentials as used for -proxyrandomize connections are unique
+ credentials = set((x.username,x.password) for x in rv)
+ assert_equal(len(credentials), len(rv))
+
+ if self.have_ipv6:
+ # proxy on IPv6 localhost
+ self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False, False)
+
+ def networks_dict(d):
+ r = {}
+ for x in d['networks']:
+ r[x['name']] = x
+ return r
+
+ # test RPC getnetworkinfo
+ n0 = networks_dict(self.nodes[0].getnetworkinfo())
+ for net in ['ipv4','ipv6','onion']:
+ assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr))
+ assert_equal(n0[net]['proxy_randomize_credentials'], True)
+ assert_equal(n0['onion']['reachable'], True)
+
+ n1 = networks_dict(self.nodes[1].getnetworkinfo())
+ for net in ['ipv4','ipv6']:
+ assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr))
+ assert_equal(n1[net]['proxy_randomize_credentials'], False)
+ assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
+ assert_equal(n1['onion']['proxy_randomize_credentials'], False)
+ assert_equal(n1['onion']['reachable'], True)
+
+ n2 = networks_dict(self.nodes[2].getnetworkinfo())
+ for net in ['ipv4','ipv6','onion']:
+ assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr))
+ assert_equal(n2[net]['proxy_randomize_credentials'], True)
+ assert_equal(n2['onion']['reachable'], True)
+
+ if self.have_ipv6:
+ n3 = networks_dict(self.nodes[3].getnetworkinfo())
+ for net in ['ipv4','ipv6']:
+ assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr))
+ assert_equal(n3[net]['proxy_randomize_credentials'], False)
+ assert_equal(n3['onion']['reachable'], False)
+
+if __name__ == '__main__':
+ ProxyTest().main()
+
diff --git a/qa/rpc-tests/pruning.py b/qa/rpc-tests/pruning.py
new file mode 100755
index 0000000000..7cbe69c29b
--- /dev/null
+++ b/qa/rpc-tests/pruning.py
@@ -0,0 +1,341 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test pruning code
+# ********
+# WARNING:
+# This test uses 4GB of disk space.
+# This test takes 30 mins or more (up to 2 hours)
+# ********
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+def calc_usage(blockdir):
+ return sum(os.path.getsize(blockdir+f) for f in os.listdir(blockdir) if os.path.isfile(blockdir+f)) / (1024. * 1024.)
+
+class PruneTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 3
+
+ self.utxo = []
+ self.address = ["",""]
+ self.txouts = gen_return_txouts()
+
+ def setup_network(self):
+ self.nodes = []
+ self.is_network_split = False
+
+ # Create nodes 0 and 1 to mine
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=999000", "-checkblocks=5"], timewait=900))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=999000", "-checkblocks=5"], timewait=900))
+
+ # Create node 2 to test pruning
+ self.nodes.append(start_node(2, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-prune=550"], timewait=900))
+ self.prunedir = self.options.tmpdir+"/node2/regtest/blocks/"
+
+ self.address[0] = self.nodes[0].getnewaddress()
+ self.address[1] = self.nodes[1].getnewaddress()
+
+ # Determine default relay fee
+ self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"]
+
+ connect_nodes(self.nodes[0], 1)
+ connect_nodes(self.nodes[1], 2)
+ connect_nodes(self.nodes[2], 0)
+ sync_blocks(self.nodes[0:3])
+
+ def create_big_chain(self):
+ # Start by creating some coinbases we can spend later
+ self.nodes[1].generate(200)
+ sync_blocks(self.nodes[0:2])
+ self.nodes[0].generate(150)
+ # Then mine enough full blocks to create more than 550MiB of data
+ for i in range(645):
+ self.mine_full_block(self.nodes[0], self.address[0])
+
+ sync_blocks(self.nodes[0:3])
+
+ def test_height_min(self):
+ if not os.path.isfile(self.prunedir+"blk00000.dat"):
+ raise AssertionError("blk00000.dat is missing, pruning too early")
+ print("Success")
+ print("Though we're already using more than 550MiB, current usage:", calc_usage(self.prunedir))
+ print("Mining 25 more blocks should cause the first block file to be pruned")
+ # Pruning doesn't run until we're allocating another chunk, 20 full blocks past the height cutoff will ensure this
+ for i in range(25):
+ self.mine_full_block(self.nodes[0],self.address[0])
+
+ waitstart = time.time()
+ while os.path.isfile(self.prunedir+"blk00000.dat"):
+ time.sleep(0.1)
+ if time.time() - waitstart > 30:
+ raise AssertionError("blk00000.dat not pruned when it should be")
+
+ print("Success")
+ usage = calc_usage(self.prunedir)
+ print("Usage should be below target:", usage)
+ if (usage > 550):
+ raise AssertionError("Pruning target not being met")
+
+ def create_chain_with_staleblocks(self):
+ # Create stale blocks in manageable sized chunks
+ print("Mine 24 (stale) blocks on Node 1, followed by 25 (main chain) block reorg from Node 0, for 12 rounds")
+
+ for j in range(12):
+ # Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain
+ # Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects
+ # Stopping node 0 also clears its mempool, so it doesn't have node1's transactions to accidentally mine
+ stop_node(self.nodes[0],0)
+ self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=999000", "-checkblocks=5"], timewait=900)
+ # Mine 24 blocks in node 1
+ self.utxo = self.nodes[1].listunspent()
+ for i in range(24):
+ if j == 0:
+ self.mine_full_block(self.nodes[1],self.address[1])
+ else:
+ self.nodes[1].generate(1) #tx's already in mempool from previous disconnects
+
+ # Reorg back with 25 block chain from node 0
+ self.utxo = self.nodes[0].listunspent()
+ for i in range(25):
+ self.mine_full_block(self.nodes[0],self.address[0])
+
+ # Create connections in the order so both nodes can see the reorg at the same time
+ connect_nodes(self.nodes[1], 0)
+ connect_nodes(self.nodes[2], 0)
+ sync_blocks(self.nodes[0:3])
+
+ print("Usage can be over target because of high stale rate:", calc_usage(self.prunedir))
+
+ def reorg_test(self):
+ # Node 1 will mine a 300 block chain starting 287 blocks back from Node 0 and Node 2's tip
+ # This will cause Node 2 to do a reorg requiring 288 blocks of undo data to the reorg_test chain
+ # Reboot node 1 to clear its mempool (hopefully make the invalidate faster)
+ # Lower the block max size so we don't keep mining all our big mempool transactions (from disconnected blocks)
+ stop_node(self.nodes[1],1)
+ self.nodes[1]=start_node(1, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"], timewait=900)
+
+ height = self.nodes[1].getblockcount()
+ print("Current block height:", height)
+
+ invalidheight = height-287
+ badhash = self.nodes[1].getblockhash(invalidheight)
+ print("Invalidating block at height:",invalidheight,badhash)
+ self.nodes[1].invalidateblock(badhash)
+
+ # We've now switched to our previously mined-24 block fork on node 1, but thats not what we want
+ # So invalidate that fork as well, until we're on the same chain as node 0/2 (but at an ancestor 288 blocks ago)
+ mainchainhash = self.nodes[0].getblockhash(invalidheight - 1)
+ curhash = self.nodes[1].getblockhash(invalidheight - 1)
+ while curhash != mainchainhash:
+ self.nodes[1].invalidateblock(curhash)
+ curhash = self.nodes[1].getblockhash(invalidheight - 1)
+
+ assert(self.nodes[1].getblockcount() == invalidheight - 1)
+ print("New best height", self.nodes[1].getblockcount())
+
+ # Reboot node1 to clear those giant tx's from mempool
+ stop_node(self.nodes[1],1)
+ self.nodes[1]=start_node(1, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"], timewait=900)
+
+ print("Generating new longer chain of 300 more blocks")
+ self.nodes[1].generate(300)
+
+ print("Reconnect nodes")
+ connect_nodes(self.nodes[0], 1)
+ connect_nodes(self.nodes[2], 1)
+ sync_blocks(self.nodes[0:3], timeout=120)
+
+ print("Verify height on node 2:",self.nodes[2].getblockcount())
+ print("Usage possibly still high bc of stale blocks in block files:", calc_usage(self.prunedir))
+
+ print("Mine 220 more blocks so we have requisite history (some blocks will be big and cause pruning of previous chain)")
+ self.nodes[0].generate(220) #node 0 has many large tx's in its mempool from the disconnects
+ sync_blocks(self.nodes[0:3], timeout=300)
+
+ usage = calc_usage(self.prunedir)
+ print("Usage should be below target:", usage)
+ if (usage > 550):
+ raise AssertionError("Pruning target not being met")
+
+ return invalidheight,badhash
+
+ def reorg_back(self):
+ # Verify that a block on the old main chain fork has been pruned away
+ try:
+ self.nodes[2].getblock(self.forkhash)
+ raise AssertionError("Old block wasn't pruned so can't test redownload")
+ except JSONRPCException as e:
+ print("Will need to redownload block",self.forkheight)
+
+ # Verify that we have enough history to reorg back to the fork point
+ # Although this is more than 288 blocks, because this chain was written more recently
+ # and only its other 299 small and 220 large block are in the block files after it,
+ # its expected to still be retained
+ self.nodes[2].getblock(self.nodes[2].getblockhash(self.forkheight))
+
+ first_reorg_height = self.nodes[2].getblockcount()
+ curchainhash = self.nodes[2].getblockhash(self.mainchainheight)
+ self.nodes[2].invalidateblock(curchainhash)
+ goalbestheight = self.mainchainheight
+ goalbesthash = self.mainchainhash2
+
+ # As of 0.10 the current block download logic is not able to reorg to the original chain created in
+ # create_chain_with_stale_blocks because it doesn't know of any peer thats on that chain from which to
+ # redownload its missing blocks.
+ # Invalidate the reorg_test chain in node 0 as well, it can successfully switch to the original chain
+ # because it has all the block data.
+ # However it must mine enough blocks to have a more work chain than the reorg_test chain in order
+ # to trigger node 2's block download logic.
+ # At this point node 2 is within 288 blocks of the fork point so it will preserve its ability to reorg
+ if self.nodes[2].getblockcount() < self.mainchainheight:
+ blocks_to_mine = first_reorg_height + 1 - self.mainchainheight
+ print("Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed:", blocks_to_mine)
+ self.nodes[0].invalidateblock(curchainhash)
+ assert(self.nodes[0].getblockcount() == self.mainchainheight)
+ assert(self.nodes[0].getbestblockhash() == self.mainchainhash2)
+ goalbesthash = self.nodes[0].generate(blocks_to_mine)[-1]
+ goalbestheight = first_reorg_height + 1
+
+ print("Verify node 2 reorged back to the main chain, some blocks of which it had to redownload")
+ waitstart = time.time()
+ while self.nodes[2].getblockcount() < goalbestheight:
+ time.sleep(0.1)
+ if time.time() - waitstart > 900:
+ raise AssertionError("Node 2 didn't reorg to proper height")
+ assert(self.nodes[2].getbestblockhash() == goalbesthash)
+ # Verify we can now have the data for a block previously pruned
+ assert(self.nodes[2].getblock(self.forkhash)["height"] == self.forkheight)
+
+ def mine_full_block(self, node, address):
+ # Want to create a full block
+ # We'll generate a 66k transaction below, and 14 of them is close to the 1MB block limit
+ for j in range(14):
+ if len(self.utxo) < 14:
+ self.utxo = node.listunspent()
+ inputs=[]
+ outputs = {}
+ t = self.utxo.pop()
+ inputs.append({ "txid" : t["txid"], "vout" : t["vout"]})
+ remchange = t["amount"] - 100*self.relayfee # Fee must be above min relay rate for 66kb tx
+ outputs[address]=remchange
+ # Create a basic transaction that will send change back to ourself after account for a fee
+ # And then insert the 128 generated transaction outs in the middle rawtx[92] is where the #
+ # of txouts is stored and is the only thing we overwrite from the original transaction
+ rawtx = node.createrawtransaction(inputs, outputs)
+ newtx = rawtx[0:92]
+ newtx = newtx + self.txouts
+ newtx = newtx + rawtx[94:]
+ # Appears to be ever so slightly faster to sign with SIGHASH_NONE
+ signresult = node.signrawtransaction(newtx,None,None,"NONE")
+ txid = node.sendrawtransaction(signresult["hex"], True)
+ # Mine a full sized block which will be these transactions we just created
+ node.generate(1)
+
+
+ def run_test(self):
+ print("Warning! This test requires 4GB of disk space and takes over 30 mins (up to 2 hours)")
+ print("Mining a big blockchain of 995 blocks")
+ self.create_big_chain()
+ # Chain diagram key:
+ # * blocks on main chain
+ # +,&,$,@ blocks on other forks
+ # X invalidated block
+ # N1 Node 1
+ #
+ # Start by mining a simple chain that all nodes have
+ # N0=N1=N2 **...*(995)
+
+ print("Check that we haven't started pruning yet because we're below PruneAfterHeight")
+ self.test_height_min()
+ # Extend this chain past the PruneAfterHeight
+ # N0=N1=N2 **...*(1020)
+
+ print("Check that we'll exceed disk space target if we have a very high stale block rate")
+ self.create_chain_with_staleblocks()
+ # Disconnect N0
+ # And mine a 24 block chain on N1 and a separate 25 block chain on N0
+ # N1=N2 **...*+...+(1044)
+ # N0 **...**...**(1045)
+ #
+ # reconnect nodes causing reorg on N1 and N2
+ # N1=N2 **...*(1020) *...**(1045)
+ # \
+ # +...+(1044)
+ #
+ # repeat this process until you have 12 stale forks hanging off the
+ # main chain on N1 and N2
+ # N0 *************************...***************************(1320)
+ #
+ # N1=N2 **...*(1020) *...**(1045) *.. ..**(1295) *...**(1320)
+ # \ \ \
+ # +...+(1044) &.. $...$(1319)
+
+ # Save some current chain state for later use
+ self.mainchainheight = self.nodes[2].getblockcount() #1320
+ self.mainchainhash2 = self.nodes[2].getblockhash(self.mainchainheight)
+
+ print("Check that we can survive a 288 block reorg still")
+ (self.forkheight,self.forkhash) = self.reorg_test() #(1033, )
+ # Now create a 288 block reorg by mining a longer chain on N1
+ # First disconnect N1
+ # Then invalidate 1033 on main chain and 1032 on fork so height is 1032 on main chain
+ # N1 **...*(1020) **...**(1032)X..
+ # \
+ # ++...+(1031)X..
+ #
+ # Now mine 300 more blocks on N1
+ # N1 **...*(1020) **...**(1032) @@...@(1332)
+ # \ \
+ # \ X...
+ # \ \
+ # ++...+(1031)X.. ..
+ #
+ # Reconnect nodes and mine 220 more blocks on N1
+ # N1 **...*(1020) **...**(1032) @@...@@@(1552)
+ # \ \
+ # \ X...
+ # \ \
+ # ++...+(1031)X.. ..
+ #
+ # N2 **...*(1020) **...**(1032) @@...@@@(1552)
+ # \ \
+ # \ *...**(1320)
+ # \ \
+ # ++...++(1044) ..
+ #
+ # N0 ********************(1032) @@...@@@(1552)
+ # \
+ # *...**(1320)
+
+ print("Test that we can rerequest a block we previously pruned if needed for a reorg")
+ self.reorg_back()
+ # Verify that N2 still has block 1033 on current chain (@), but not on main chain (*)
+ # Invalidate 1033 on current chain (@) on N2 and we should be able to reorg to
+ # original main chain (*), but will require redownload of some blocks
+ # In order to have a peer we think we can download from, must also perform this invalidation
+ # on N0 and mine a new longest chain to trigger.
+ # Final result:
+ # N0 ********************(1032) **...****(1553)
+ # \
+ # X@...@@@(1552)
+ #
+ # N2 **...*(1020) **...**(1032) **...****(1553)
+ # \ \
+ # \ X@...@@@(1552)
+ # \
+ # +..
+ #
+ # N1 doesn't change because 1033 on main chain (*) is invalid
+
+ print("Done")
+
+if __name__ == '__main__':
+ PruneTest().main()
diff --git a/qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/.gitignore b/qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/.gitignore
deleted file mode 100644
index 0d20b6487c..0000000000
--- a/qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.pyc
diff --git a/qa/rpc-tests/python-bitcoinrpc/setup.py b/qa/rpc-tests/python-bitcoinrpc/setup.py
deleted file mode 100644
index 43cdb1c038..0000000000
--- a/qa/rpc-tests/python-bitcoinrpc/setup.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env python2
-
-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/qa/rpc-tests/rawtransactions.py b/qa/rpc-tests/rawtransactions.py
new file mode 100755
index 0000000000..ab6d2e8def
--- /dev/null
+++ b/qa/rpc-tests/rawtransactions.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test re-org scenarios with a mempool that contains transactions
+# that spend (directly or indirectly) coinbase transactions.
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+# Create one-input, one-output, no-fee transaction:
+class RawTransactionsTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 3
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+
+ #connect to a local machine for debugging
+ #url = "http://bitcoinrpc:DP6DvqZtqXarpeNWyN3LZTFchCCyCUuHwNF7E8pX99x1@%s:%d" % ('127.0.0.1', 18332)
+ #proxy = AuthServiceProxy(url)
+ #proxy.url = url # store URL on proxy for info
+ #self.nodes.append(proxy)
+
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+
+ self.is_network_split=False
+ self.sync_all()
+
+ def run_test(self):
+
+ #prepare some coins for multiple *rawtransaction commands
+ self.nodes[2].generate(1)
+ self.sync_all()
+ self.nodes[0].generate(101)
+ self.sync_all()
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5)
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0)
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0)
+ self.sync_all()
+ self.nodes[0].generate(5)
+ self.sync_all()
+
+ #########################################
+ # sendrawtransaction with missing input #
+ #########################################
+ inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1}] #won't exists
+ outputs = { self.nodes[0].getnewaddress() : 4.998 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ rawtx = self.nodes[2].signrawtransaction(rawtx)
+
+ try:
+ rawtx = self.nodes[2].sendrawtransaction(rawtx['hex'])
+ except JSONRPCException as e:
+ assert("Missing inputs" in e.error['message'])
+ else:
+ assert(False)
+
+
+ #########################
+ # RAW TX MULTISIG TESTS #
+ #########################
+ # 2of2 test
+ addr1 = self.nodes[2].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[2].validateaddress(addr1)
+ addr2Obj = self.nodes[2].validateaddress(addr2)
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+ mSigObjValid = self.nodes[2].validateaddress(mSigObj)
+
+ #use balance deltas instead of absolute values
+ bal = self.nodes[2].getbalance()
+
+ # send 1.2 BTC to msig adr
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.2)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(self.nodes[2].getbalance(), bal+Decimal('1.20000000')) #node2 has both keys of the 2of2 ms addr., tx should affect the balance
+
+
+ # 2of3 test from different nodes
+ bal = self.nodes[2].getbalance()
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+ addr3 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[1].validateaddress(addr1)
+ addr2Obj = self.nodes[2].validateaddress(addr2)
+ addr3Obj = self.nodes[2].validateaddress(addr3)
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])
+ mSigObjValid = self.nodes[2].validateaddress(mSigObj)
+
+ txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
+ decTx = self.nodes[0].gettransaction(txId)
+ rawTx = self.nodes[0].decoderawtransaction(decTx['hex'])
+ sPK = rawTx['vout'][0]['scriptPubKey']['hex']
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ #THIS IS A INCOMPLETE FEATURE
+ #NODE2 HAS TWO OF THREE KEY AND THE FUNDS SHOULD BE SPENDABLE AND COUNT AT BALANCE CALCULATION
+ assert_equal(self.nodes[2].getbalance(), bal) #for now, assume the funds of a 2of3 multisig tx are not marked as spendable
+
+ txDetails = self.nodes[0].gettransaction(txId, True)
+ rawTx = self.nodes[0].decoderawtransaction(txDetails['hex'])
+ vout = False
+ for outpoint in rawTx['vout']:
+ if outpoint['value'] == Decimal('2.20000000'):
+ vout = outpoint
+ break
+
+ bal = self.nodes[0].getbalance()
+ inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}]
+ outputs = { self.nodes[0].getnewaddress() : 2.19 }
+ rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
+ rawTxPartialSigned = self.nodes[1].signrawtransaction(rawTx, inputs)
+ assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx
+
+ rawTxSigned = self.nodes[2].signrawtransaction(rawTx, inputs)
+ assert_equal(rawTxSigned['complete'], True) #node2 can sign the tx compl., own two of three keys
+ self.nodes[2].sendrawtransaction(rawTxSigned['hex'])
+ rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
+
+ inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : 1000}]
+ outputs = { self.nodes[0].getnewaddress() : 1 }
+ rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
+ decrawtx= self.nodes[0].decoderawtransaction(rawtx)
+ assert_equal(decrawtx['vin'][0]['sequence'], 1000)
+
+ inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : -1}]
+ outputs = { self.nodes[0].getnewaddress() : 1 }
+ assert_raises(JSONRPCException, self.nodes[0].createrawtransaction, inputs, outputs)
+
+ inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : 4294967296}]
+ outputs = { self.nodes[0].getnewaddress() : 1 }
+ assert_raises(JSONRPCException, self.nodes[0].createrawtransaction, inputs, outputs)
+
+ inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : 4294967294}]
+ outputs = { self.nodes[0].getnewaddress() : 1 }
+ rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
+ decrawtx= self.nodes[0].decoderawtransaction(rawtx)
+ assert_equal(decrawtx['vin'][0]['sequence'], 4294967294)
+
+if __name__ == '__main__':
+ RawTransactionsTest().main()
diff --git a/qa/rpc-tests/receivedby.py b/qa/rpc-tests/receivedby.py
index d3504e0920..4f17b661cb 100755
--- a/qa/rpc-tests/receivedby.py
+++ b/qa/rpc-tests/receivedby.py
@@ -1,13 +1,12 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Exercise the listreceivedbyaddress API
-from test_framework import BitcoinTestFramework
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
def get_sub_array_from_array(object_array, to_match):
@@ -26,34 +25,18 @@ def get_sub_array_from_array(object_array, to_match):
return item
return []
-def check_array_result(object_array, to_match, expected, should_not_find = False):
- """
- Pass in array of JSON objects, a dictionary with key/value pairs
- to match against, and another dictionary with expected key/value
- pairs.
- If the should_not_find flag is true, to_match should not be found in object_array
- """
- if should_not_find == True:
- expected = { }
- 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 and should_not_find != True:
- raise AssertionError("No objects matched %s"%(str(to_match)))
- if num_matched > 0 and should_not_find == True:
- raise AssertionError("Objects was matched %s"%(str(to_match)))
-
class ReceivedByTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.setup_clean_chain = False
+
+ def setup_nodes(self):
+ #This test requires mocktime
+ enable_mocktime()
+ return start_nodes(self.num_nodes, self.options.tmpdir)
+
def run_test(self):
'''
listreceivedbyaddress Test
@@ -64,26 +47,26 @@ class ReceivedByTest(BitcoinTestFramework):
self.sync_all()
#Check not listed in listreceivedbyaddress because has 0 confirmations
- check_array_result(self.nodes[1].listreceivedbyaddress(),
+ assert_array_result(self.nodes[1].listreceivedbyaddress(),
{"address":addr},
{ },
True)
#Bury Tx under 10 block so it will be returned by listreceivedbyaddress
- self.nodes[1].setgenerate(True, 10)
+ self.nodes[1].generate(10)
self.sync_all()
- check_array_result(self.nodes[1].listreceivedbyaddress(),
+ assert_array_result(self.nodes[1].listreceivedbyaddress(),
{"address":addr},
{"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]})
#With min confidence < 10
- check_array_result(self.nodes[1].listreceivedbyaddress(5),
+ assert_array_result(self.nodes[1].listreceivedbyaddress(5),
{"address":addr},
{"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]})
#With min confidence > 10, should not find Tx
- check_array_result(self.nodes[1].listreceivedbyaddress(11),{"address":addr},{ },True)
+ assert_array_result(self.nodes[1].listreceivedbyaddress(11),{"address":addr},{ },True)
#Empty Tx
addr = self.nodes[1].getnewaddress()
- check_array_result(self.nodes[1].listreceivedbyaddress(0,True),
+ assert_array_result(self.nodes[1].listreceivedbyaddress(0,True),
{"address":addr},
{"address":addr, "account":"", "amount":0, "confirmations":0, "txids":[]})
@@ -106,7 +89,7 @@ class ReceivedByTest(BitcoinTestFramework):
raise AssertionError("Wrong balance returned by getreceivedbyaddress, %0.2f"%(balance))
#Bury Tx under 10 block so it will be returned by the default getreceivedbyaddress
- self.nodes[1].setgenerate(True, 10)
+ self.nodes[1].generate(10)
self.sync_all()
balance = self.nodes[1].getreceivedbyaddress(addr)
if balance != Decimal("0.1"):
@@ -127,7 +110,7 @@ class ReceivedByTest(BitcoinTestFramework):
self.sync_all()
# listreceivedbyaccount should return received_by_account_json because of 0 confirmations
- check_array_result(self.nodes[1].listreceivedbyaccount(),
+ assert_array_result(self.nodes[1].listreceivedbyaccount(),
{"account":account},
received_by_account_json)
@@ -136,10 +119,10 @@ class ReceivedByTest(BitcoinTestFramework):
if balance != balance_by_account:
raise AssertionError("Wrong balance returned by getreceivedbyaccount, %0.2f"%(balance))
- self.nodes[1].setgenerate(True, 10)
+ self.nodes[1].generate(10)
self.sync_all()
# listreceivedbyaccount should return updated account balance
- check_array_result(self.nodes[1].listreceivedbyaccount(),
+ assert_array_result(self.nodes[1].listreceivedbyaccount(),
{"account":account},
{"account":received_by_account_json["account"], "amount":(received_by_account_json["amount"] + Decimal("0.1"))})
diff --git a/qa/rpc-tests/reindex.py b/qa/rpc-tests/reindex.py
new file mode 100755
index 0000000000..abbbb10336
--- /dev/null
+++ b/qa/rpc-tests/reindex.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test -reindex and -reindex-chainstate with CheckBlockIndex
+#
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import time
+
+class ReindexTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def setup_network(self):
+ self.nodes = []
+ self.is_network_split = False
+ self.nodes.append(start_node(0, self.options.tmpdir))
+
+ def reindex(self, justchainstate=False):
+ self.nodes[0].generate(3)
+ blockcount = self.nodes[0].getblockcount()
+ stop_node(self.nodes[0], 0)
+ wait_bitcoinds()
+ self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug", "-reindex-chainstate" if justchainstate else "-reindex", "-checkblockindex=1"])
+ while self.nodes[0].getblockcount() < blockcount:
+ time.sleep(0.1)
+ assert_equal(self.nodes[0].getblockcount(), blockcount)
+ print("Success")
+
+ def run_test(self):
+ self.reindex(False)
+ self.reindex(True)
+ self.reindex(False)
+ self.reindex(True)
+
+if __name__ == '__main__':
+ ReindexTest().main()
diff --git a/qa/rpc-tests/replace-by-fee.py b/qa/rpc-tests/replace-by-fee.py
new file mode 100755
index 0000000000..34c0f9d795
--- /dev/null
+++ b/qa/rpc-tests/replace-by-fee.py
@@ -0,0 +1,590 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test replace by fee code
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.script import *
+from test_framework.mininode import *
+
+MAX_REPLACEMENT_LIMIT = 100
+
+def txToHex(tx):
+ return bytes_to_hex_str(tx.serialize())
+
+def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])):
+ """Create a txout with a given amount and scriptPubKey
+
+ Mines coins as needed.
+
+ confirmed - txouts created will be confirmed in the blockchain;
+ unconfirmed otherwise.
+ """
+ fee = 1*COIN
+ while node.getbalance() < satoshi_round((amount + fee)/COIN):
+ node.generate(100)
+ #print (node.getbalance(), amount, fee)
+
+ new_addr = node.getnewaddress()
+ #print new_addr
+ txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN))
+ tx1 = node.getrawtransaction(txid, 1)
+ txid = int(txid, 16)
+ i = None
+
+ for i, txout in enumerate(tx1['vout']):
+ #print i, txout['scriptPubKey']['addresses']
+ if txout['scriptPubKey']['addresses'] == [new_addr]:
+ #print i
+ break
+ assert i is not None
+
+ tx2 = CTransaction()
+ tx2.vin = [CTxIn(COutPoint(txid, i))]
+ tx2.vout = [CTxOut(amount, scriptPubKey)]
+ tx2.rehash()
+
+ signed_tx = node.signrawtransaction(txToHex(tx2))
+
+ txid = node.sendrawtransaction(signed_tx['hex'], True)
+
+ # If requested, ensure txouts are confirmed.
+ if confirmed:
+ mempool_size = len(node.getrawmempool())
+ while mempool_size > 0:
+ node.generate(1)
+ new_size = len(node.getrawmempool())
+ # Error out if we have something stuck in the mempool, as this
+ # would likely be a bug.
+ assert(new_size < mempool_size)
+ mempool_size = new_size
+
+ return COutPoint(int(txid, 16), 0)
+
+class ReplaceByFeeTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 1
+ self.setup_clean_chain = False
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000", "-debug",
+ "-relaypriority=0", "-whitelist=127.0.0.1",
+ "-limitancestorcount=50",
+ "-limitancestorsize=101",
+ "-limitdescendantcount=200",
+ "-limitdescendantsize=101"
+ ]))
+ self.is_network_split = False
+
+ def run_test(self):
+ make_utxo(self.nodes[0], 1*COIN)
+
+ print("Running test simple doublespend...")
+ self.test_simple_doublespend()
+
+ print("Running test doublespend chain...")
+ self.test_doublespend_chain()
+
+ print("Running test doublespend tree...")
+ self.test_doublespend_tree()
+
+ print("Running test replacement feeperkb...")
+ self.test_replacement_feeperkb()
+
+ print("Running test spends of conflicting outputs...")
+ self.test_spends_of_conflicting_outputs()
+
+ print("Running test new unconfirmed inputs...")
+ self.test_new_unconfirmed_inputs()
+
+ print("Running test too many replacements...")
+ self.test_too_many_replacements()
+
+ print("Running test opt-in...")
+ self.test_opt_in()
+
+ print("Running test prioritised transactions...")
+ self.test_prioritised_transactions()
+
+ print("Passed\n")
+
+ def test_simple_doublespend(self):
+ """Simple doublespend"""
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+
+ tx1a = CTransaction()
+ tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
+ tx1a_hex = txToHex(tx1a)
+ tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
+
+ # Should fail because we haven't changed the fee
+ tx1b = CTransaction()
+ tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ tx1b.vout = [CTxOut(1*COIN, CScript([b'b']))]
+ tx1b_hex = txToHex(tx1b)
+
+ try:
+ tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26) # insufficient fee
+ else:
+ assert(False)
+
+ # Extra 0.1 BTC fee
+ tx1b = CTransaction()
+ tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ tx1b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))]
+ tx1b_hex = txToHex(tx1b)
+ tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
+
+ mempool = self.nodes[0].getrawmempool()
+
+ assert (tx1a_txid not in mempool)
+ assert (tx1b_txid in mempool)
+
+ assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid))
+
+ def test_doublespend_chain(self):
+ """Doublespend of a long chain"""
+
+ initial_nValue = 50*COIN
+ tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
+
+ prevout = tx0_outpoint
+ remaining_value = initial_nValue
+ chain_txids = []
+ while remaining_value > 10*COIN:
+ remaining_value -= 1*COIN
+ tx = CTransaction()
+ tx.vin = [CTxIn(prevout, nSequence=0)]
+ tx.vout = [CTxOut(remaining_value, CScript([1]))]
+ tx_hex = txToHex(tx)
+ txid = self.nodes[0].sendrawtransaction(tx_hex, True)
+ chain_txids.append(txid)
+ prevout = COutPoint(int(txid, 16), 0)
+
+ # Whether the double-spend is allowed is evaluated by including all
+ # child fees - 40 BTC - so this attempt is rejected.
+ dbl_tx = CTransaction()
+ dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ dbl_tx.vout = [CTxOut(initial_nValue - 30*COIN, CScript([1]))]
+ dbl_tx_hex = txToHex(dbl_tx)
+
+ try:
+ self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26) # insufficient fee
+ else:
+ assert(False) # transaction mistakenly accepted!
+
+ # Accepted with sufficient fee
+ dbl_tx = CTransaction()
+ dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ dbl_tx.vout = [CTxOut(1*COIN, CScript([1]))]
+ dbl_tx_hex = txToHex(dbl_tx)
+ self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
+
+ mempool = self.nodes[0].getrawmempool()
+ for doublespent_txid in chain_txids:
+ assert(doublespent_txid not in mempool)
+
+ def test_doublespend_tree(self):
+ """Doublespend of a big tree of transactions"""
+
+ initial_nValue = 50*COIN
+ tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
+
+ def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _total_txs=None):
+ if _total_txs is None:
+ _total_txs = [0]
+ if _total_txs[0] >= max_txs:
+ return
+
+ txout_value = (initial_value - fee) // tree_width
+ if txout_value < fee:
+ return
+
+ vout = [CTxOut(txout_value, CScript([i+1]))
+ for i in range(tree_width)]
+ tx = CTransaction()
+ tx.vin = [CTxIn(prevout, nSequence=0)]
+ tx.vout = vout
+ tx_hex = txToHex(tx)
+
+ assert(len(tx.serialize()) < 100000)
+ txid = self.nodes[0].sendrawtransaction(tx_hex, True)
+ yield tx
+ _total_txs[0] += 1
+
+ txid = int(txid, 16)
+
+ for i, txout in enumerate(tx.vout):
+ for x in branch(COutPoint(txid, i), txout_value,
+ max_txs,
+ tree_width=tree_width, fee=fee,
+ _total_txs=_total_txs):
+ yield x
+
+ fee = int(0.0001*COIN)
+ n = MAX_REPLACEMENT_LIMIT
+ tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
+ assert_equal(len(tree_txs), n)
+
+ # Attempt double-spend, will fail because too little fee paid
+ dbl_tx = CTransaction()
+ dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ dbl_tx.vout = [CTxOut(initial_nValue - fee*n, CScript([1]))]
+ dbl_tx_hex = txToHex(dbl_tx)
+ try:
+ self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26) # insufficient fee
+ else:
+ assert(False)
+
+ # 1 BTC fee is enough
+ dbl_tx = CTransaction()
+ dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ dbl_tx.vout = [CTxOut(initial_nValue - fee*n - 1*COIN, CScript([1]))]
+ dbl_tx_hex = txToHex(dbl_tx)
+ self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
+
+ mempool = self.nodes[0].getrawmempool()
+
+ for tx in tree_txs:
+ tx.rehash()
+ assert (tx.hash not in mempool)
+
+ # Try again, but with more total transactions than the "max txs
+ # double-spent at once" anti-DoS limit.
+ for n in (MAX_REPLACEMENT_LIMIT+1, MAX_REPLACEMENT_LIMIT*2):
+ fee = int(0.0001*COIN)
+ tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
+ tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
+ assert_equal(len(tree_txs), n)
+
+ dbl_tx = CTransaction()
+ dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ dbl_tx.vout = [CTxOut(initial_nValue - 2*fee*n, CScript([1]))]
+ dbl_tx_hex = txToHex(dbl_tx)
+ try:
+ self.nodes[0].sendrawtransaction(dbl_tx_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ assert_equal("too many potential replacements" in exp.error['message'], True)
+ else:
+ assert(False)
+
+ for tx in tree_txs:
+ tx.rehash()
+ self.nodes[0].getrawtransaction(tx.hash)
+
+ def test_replacement_feeperkb(self):
+ """Replacement requires fee-per-KB to be higher"""
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+
+ tx1a = CTransaction()
+ tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
+ tx1a_hex = txToHex(tx1a)
+ tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
+
+ # Higher fee, but the fee per KB is much lower, so the replacement is
+ # rejected.
+ tx1b = CTransaction()
+ tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*999000]))]
+ tx1b_hex = txToHex(tx1b)
+
+ try:
+ tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26) # insufficient fee
+ else:
+ assert(False)
+
+ def test_spends_of_conflicting_outputs(self):
+ """Replacements that spend conflicting tx outputs are rejected"""
+ utxo1 = make_utxo(self.nodes[0], int(1.2*COIN))
+ utxo2 = make_utxo(self.nodes[0], 3*COIN)
+
+ tx1a = CTransaction()
+ tx1a.vin = [CTxIn(utxo1, nSequence=0)]
+ tx1a.vout = [CTxOut(int(1.1*COIN), CScript([b'a']))]
+ tx1a_hex = txToHex(tx1a)
+ tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
+
+ tx1a_txid = int(tx1a_txid, 16)
+
+ # Direct spend an output of the transaction we're replacing.
+ tx2 = CTransaction()
+ tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0)]
+ tx2.vin.append(CTxIn(COutPoint(tx1a_txid, 0), nSequence=0))
+ tx2.vout = tx1a.vout
+ tx2_hex = txToHex(tx2)
+
+ try:
+ tx2_txid = self.nodes[0].sendrawtransaction(tx2_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ else:
+ assert(False)
+
+ # Spend tx1a's output to test the indirect case.
+ tx1b = CTransaction()
+ tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
+ tx1b.vout = [CTxOut(1*COIN, CScript([b'a']))]
+ tx1b_hex = txToHex(tx1b)
+ tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
+ tx1b_txid = int(tx1b_txid, 16)
+
+ tx2 = CTransaction()
+ tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0),
+ CTxIn(COutPoint(tx1b_txid, 0))]
+ tx2.vout = tx1a.vout
+ tx2_hex = txToHex(tx2)
+
+ try:
+ tx2_txid = self.nodes[0].sendrawtransaction(tx2_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ else:
+ assert(False)
+
+ def test_new_unconfirmed_inputs(self):
+ """Replacements that add new unconfirmed inputs are rejected"""
+ confirmed_utxo = make_utxo(self.nodes[0], int(1.1*COIN))
+ unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1*COIN), False)
+
+ tx1 = CTransaction()
+ tx1.vin = [CTxIn(confirmed_utxo)]
+ tx1.vout = [CTxOut(1*COIN, CScript([b'a']))]
+ tx1_hex = txToHex(tx1)
+ tx1_txid = self.nodes[0].sendrawtransaction(tx1_hex, True)
+
+ tx2 = CTransaction()
+ tx2.vin = [CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)]
+ tx2.vout = tx1.vout
+ tx2_hex = txToHex(tx2)
+
+ try:
+ tx2_txid = self.nodes[0].sendrawtransaction(tx2_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ else:
+ assert(False)
+
+ def test_too_many_replacements(self):
+ """Replacements that evict too many transactions are rejected"""
+ # Try directly replacing more than MAX_REPLACEMENT_LIMIT
+ # transactions
+
+ # Start by creating a single transaction with many outputs
+ initial_nValue = 10*COIN
+ utxo = make_utxo(self.nodes[0], initial_nValue)
+ fee = int(0.0001*COIN)
+ split_value = int((initial_nValue-fee)/(MAX_REPLACEMENT_LIMIT+1))
+ actual_fee = initial_nValue - split_value*(MAX_REPLACEMENT_LIMIT+1)
+
+ outputs = []
+ for i in range(MAX_REPLACEMENT_LIMIT+1):
+ outputs.append(CTxOut(split_value, CScript([1])))
+
+ splitting_tx = CTransaction()
+ splitting_tx.vin = [CTxIn(utxo, nSequence=0)]
+ splitting_tx.vout = outputs
+ splitting_tx_hex = txToHex(splitting_tx)
+
+ txid = self.nodes[0].sendrawtransaction(splitting_tx_hex, True)
+ txid = int(txid, 16)
+
+ # Now spend each of those outputs individually
+ for i in range(MAX_REPLACEMENT_LIMIT+1):
+ tx_i = CTransaction()
+ tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)]
+ tx_i.vout = [CTxOut(split_value-fee, CScript([b'a']))]
+ tx_i_hex = txToHex(tx_i)
+ self.nodes[0].sendrawtransaction(tx_i_hex, True)
+
+ # Now create doublespend of the whole lot; should fail.
+ # Need a big enough fee to cover all spending transactions and have
+ # a higher fee rate
+ double_spend_value = (split_value-100*fee)*(MAX_REPLACEMENT_LIMIT+1)
+ inputs = []
+ for i in range(MAX_REPLACEMENT_LIMIT+1):
+ inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
+ double_tx = CTransaction()
+ double_tx.vin = inputs
+ double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
+ double_tx_hex = txToHex(double_tx)
+
+ try:
+ self.nodes[0].sendrawtransaction(double_tx_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ assert_equal("too many potential replacements" in exp.error['message'], True)
+ else:
+ assert(False)
+
+ # If we remove an input, it should pass
+ double_tx = CTransaction()
+ double_tx.vin = inputs[0:-1]
+ double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
+ double_tx_hex = txToHex(double_tx)
+ self.nodes[0].sendrawtransaction(double_tx_hex, True)
+
+ def test_opt_in(self):
+ """ Replacing should only work if orig tx opted in """
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+
+ # Create a non-opting in transaction
+ tx1a = CTransaction()
+ tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0xffffffff)]
+ tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
+ tx1a_hex = txToHex(tx1a)
+ tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
+
+ # Shouldn't be able to double-spend
+ tx1b = CTransaction()
+ tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ tx1b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))]
+ tx1b_hex = txToHex(tx1b)
+
+ try:
+ tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ else:
+ print(tx1b_txid)
+ assert(False)
+
+ tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+
+ # Create a different non-opting in transaction
+ tx2a = CTransaction()
+ tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)]
+ tx2a.vout = [CTxOut(1*COIN, CScript([b'a']))]
+ tx2a_hex = txToHex(tx2a)
+ tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, True)
+
+ # Still shouldn't be able to double-spend
+ tx2b = CTransaction()
+ tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
+ tx2b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))]
+ tx2b_hex = txToHex(tx2b)
+
+ try:
+ tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ else:
+ assert(False)
+
+ # Now create a new transaction that spends from tx1a and tx2a
+ # opt-in on one of the inputs
+ # Transaction should be replaceable on either input
+
+ tx1a_txid = int(tx1a_txid, 16)
+ tx2a_txid = int(tx2a_txid, 16)
+
+ tx3a = CTransaction()
+ tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff),
+ CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)]
+ tx3a.vout = [CTxOut(int(0.9*COIN), CScript([b'c'])), CTxOut(int(0.9*COIN), CScript([b'd']))]
+ tx3a_hex = txToHex(tx3a)
+
+ self.nodes[0].sendrawtransaction(tx3a_hex, True)
+
+ tx3b = CTransaction()
+ tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
+ tx3b.vout = [CTxOut(int(0.5*COIN), CScript([b'e']))]
+ tx3b_hex = txToHex(tx3b)
+
+ tx3c = CTransaction()
+ tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)]
+ tx3c.vout = [CTxOut(int(0.5*COIN), CScript([b'f']))]
+ tx3c_hex = txToHex(tx3c)
+
+ self.nodes[0].sendrawtransaction(tx3b_hex, True)
+ # If tx3b was accepted, tx3c won't look like a replacement,
+ # but make sure it is accepted anyway
+ self.nodes[0].sendrawtransaction(tx3c_hex, True)
+
+ def test_prioritised_transactions(self):
+ # Ensure that fee deltas used via prioritisetransaction are
+ # correctly used by replacement logic
+
+ # 1. Check that feeperkb uses modified fees
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+
+ tx1a = CTransaction()
+ tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ tx1a.vout = [CTxOut(1*COIN, CScript([b'a']))]
+ tx1a_hex = txToHex(tx1a)
+ tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True)
+
+ # Higher fee, but the actual fee per KB is much lower.
+ tx1b = CTransaction()
+ tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
+ tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*740000]))]
+ tx1b_hex = txToHex(tx1b)
+
+ # Verify tx1b cannot replace tx1a.
+ try:
+ tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ else:
+ assert(False)
+
+ # Use prioritisetransaction to set tx1a's fee to 0.
+ self.nodes[0].prioritisetransaction(tx1a_txid, 0, int(-0.1*COIN))
+
+ # Now tx1b should be able to replace tx1a
+ tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True)
+
+ assert(tx1b_txid in self.nodes[0].getrawmempool())
+
+ # 2. Check that absolute fee checks use modified fee.
+ tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+
+ tx2a = CTransaction()
+ tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
+ tx2a.vout = [CTxOut(1*COIN, CScript([b'a']))]
+ tx2a_hex = txToHex(tx2a)
+ tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, True)
+
+ # Lower fee, but we'll prioritise it
+ tx2b = CTransaction()
+ tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
+ tx2b.vout = [CTxOut(int(1.01*COIN), CScript([b'a']))]
+ tx2b.rehash()
+ tx2b_hex = txToHex(tx2b)
+
+ # Verify tx2b cannot replace tx2a.
+ try:
+ tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True)
+ except JSONRPCException as exp:
+ assert_equal(exp.error['code'], -26)
+ else:
+ assert(False)
+
+ # Now prioritise tx2b to have a higher modified fee
+ self.nodes[0].prioritisetransaction(tx2b.hash, 0, int(0.1*COIN))
+
+ # tx2b should now be accepted
+ tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True)
+
+ assert(tx2b_txid in self.nodes[0].getrawmempool())
+
+if __name__ == '__main__':
+ ReplaceByFeeTest().main()
diff --git a/qa/rpc-tests/rest.py b/qa/rpc-tests/rest.py
index cb1868de36..c9c2eaf7f3 100755
--- a/qa/rpc-tests/rest.py
+++ b/qa/rpc-tests/rest.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -7,57 +7,285 @@
# Test REST interface
#
-from test_framework import BitcoinTestFramework
-from util import *
-import json
-try:
- import http.client as httplib
-except ImportError:
- import httplib
-try:
- import urllib.parse as urlparse
-except ImportError:
- import urlparse
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from struct import *
+from io import BytesIO
+from codecs import encode
+import http.client
+import urllib.parse
+
+def deser_uint256(f):
+ r = 0
+ for i in range(8):
+ t = unpack(b"<I", f.read(4))[0]
+ r += t << (i * 32)
+ return r
+
+#allows simple http get calls
def http_get_call(host, port, path, response_object = 0):
- conn = httplib.HTTPConnection(host, port)
+ conn = http.client.HTTPConnection(host, port)
conn.request('GET', path)
-
+
if response_object:
return conn.getresponse()
-
- return conn.getresponse().read()
+ return conn.getresponse().read().decode('utf-8')
+
+#allows simple http post calls with a request body
+def http_post_call(host, port, path, requestdata = '', response_object = 0):
+ conn = http.client.HTTPConnection(host, port)
+ conn.request('POST', path, requestdata)
+
+ if response_object:
+ return conn.getresponse()
+
+ return conn.getresponse().read()
class RESTTest (BitcoinTestFramework):
FORMAT_SEPARATOR = "."
-
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 3
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+ self.is_network_split=False
+ self.sync_all()
+
def run_test(self):
- url = urlparse.urlparse(self.nodes[0].url)
+ url = urllib.parse.urlparse(self.nodes[0].url)
+ print("Mining blocks...")
+
+ self.nodes[0].generate(1)
+ self.sync_all()
+ self.nodes[2].generate(100)
+ self.sync_all()
+
+ assert_equal(self.nodes[0].getbalance(), 50)
+
+ txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
+ self.sync_all()
+ self.nodes[2].generate(1)
+ self.sync_all()
+ bb_hash = self.nodes[0].getbestblockhash()
+
+ assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) #balance now should be 0.1 on node 1
+
+ # load the latest 0.1 tx over the REST API
+ json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+txid+self.FORMAT_SEPARATOR+"json")
+ json_obj = json.loads(json_string)
+ vintx = json_obj['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then)
+ # get n of 0.1 outpoint
+ n = 0
+ for vout in json_obj['vout']:
+ if vout['value'] == 0.1:
+ n = vout['n']
+
+
+ ######################################
+ # GETUTXOS: query a unspent outpoint #
+ ######################################
+ json_request = '/checkmempool/'+txid+'-'+str(n)
+ json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+
+ #check chainTip response
+ assert_equal(json_obj['chaintipHash'], bb_hash)
+
+ #make sure there is one utxo
+ assert_equal(len(json_obj['utxos']), 1)
+ assert_equal(json_obj['utxos'][0]['value'], 0.1)
+
+
+ ################################################
+ # GETUTXOS: now query a already spent outpoint #
+ ################################################
+ json_request = '/checkmempool/'+vintx+'-0'
+ json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+
+ #check chainTip response
+ assert_equal(json_obj['chaintipHash'], bb_hash)
+
+ #make sure there is no utox in the response because this oupoint has been spent
+ assert_equal(len(json_obj['utxos']), 0)
+
+ #check bitmap
+ assert_equal(json_obj['bitmap'], "0")
+
+
+ ##################################################
+ # GETUTXOS: now check both with the same request #
+ ##################################################
+ json_request = '/checkmempool/'+txid+'-'+str(n)+'/'+vintx+'-0'
+ json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+ assert_equal(len(json_obj['utxos']), 1)
+ assert_equal(json_obj['bitmap'], "10")
+
+ #test binary response
bb_hash = self.nodes[0].getbestblockhash()
-
+
+ binaryRequest = b'\x01\x02'
+ binaryRequest += hex_str_to_bytes(txid)
+ binaryRequest += pack("i", n)
+ binaryRequest += hex_str_to_bytes(vintx)
+ binaryRequest += pack("i", 0)
+
+ bin_response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest)
+ output = BytesIO()
+ output.write(bin_response)
+ output.seek(0)
+ chainHeight = unpack("i", output.read(4))[0]
+ hashFromBinResponse = hex(deser_uint256(output))[2:].zfill(64)
+
+ assert_equal(bb_hash, hashFromBinResponse) #check if getutxo's chaintip during calculation was fine
+ assert_equal(chainHeight, 102) #chain height must be 102
+
+
+ ############################
+ # GETUTXOS: mempool checks #
+ ############################
+
+ # do a tx and don't sync
+ txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
+ json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+txid+self.FORMAT_SEPARATOR+"json")
+ json_obj = json.loads(json_string)
+ vintx = json_obj['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then)
+ # get n of 0.1 outpoint
+ n = 0
+ for vout in json_obj['vout']:
+ if vout['value'] == 0.1:
+ n = vout['n']
+
+ json_request = '/'+txid+'-'+str(n)
+ json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+ assert_equal(len(json_obj['utxos']), 0) #there should be a outpoint because it has just added to the mempool
+
+ json_request = '/checkmempool/'+txid+'-'+str(n)
+ json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+ assert_equal(len(json_obj['utxos']), 1) #there should be a outpoint because it has just added to the mempool
+
+ #do some invalid requests
+ json_request = '{"checkmempool'
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True)
+ assert_equal(response.status, 500) #must be a 500 because we send a invalid json request
+
+ json_request = '{"checkmempool'
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True)
+ assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
+
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos/checkmempool'+self.FORMAT_SEPARATOR+'bin', '', True)
+ assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
+
+ #test limits
+ json_request = '/checkmempool/'
+ for x in range(0, 20):
+ json_request += txid+'-'+str(n)+'/'
+ json_request = json_request.rstrip("/")
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
+ assert_equal(response.status, 500) #must be a 500 because we exceeding the limits
+
+ json_request = '/checkmempool/'
+ for x in range(0, 15):
+ json_request += txid+'-'+str(n)+'/'
+ json_request = json_request.rstrip("/")
+ response = http_post_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
+ assert_equal(response.status, 200) #must be a 500 because we exceeding the limits
+
+ self.nodes[0].generate(1) #generate block to not affect upcoming tests
+ self.sync_all()
+
+ ################
+ # /rest/block/ #
+ ################
+
# check binary format
response = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"bin", True)
assert_equal(response.status, 200)
- assert_greater_than(int(response.getheader('content-length')), 10)
-
+ assert_greater_than(int(response.getheader('content-length')), 80)
+ response_str = response.read()
+
+ # compare with block header
+ response_header = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"bin", True)
+ assert_equal(response_header.status, 200)
+ assert_equal(int(response_header.getheader('content-length')), 80)
+ response_header_str = response_header.read()
+ assert_equal(response_str[0:80], response_header_str)
+
+ # check block hex format
+ response_hex = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"hex", True)
+ assert_equal(response_hex.status, 200)
+ assert_greater_than(int(response_hex.getheader('content-length')), 160)
+ response_hex_str = response_hex.read()
+ assert_equal(encode(response_str, "hex_codec")[0:160], response_hex_str[0:160])
+
+ # compare with hex block header
+ response_header_hex = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"hex", True)
+ assert_equal(response_header_hex.status, 200)
+ assert_greater_than(int(response_header_hex.getheader('content-length')), 160)
+ response_header_hex_str = response_header_hex.read()
+ assert_equal(response_hex_str[0:160], response_header_hex_str[0:160])
+ assert_equal(encode(response_header_str, "hex_codec")[0:160], response_header_hex_str[0:160])
+
# check json format
- json_string = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+'json')
- json_obj = json.loads(json_string)
- assert_equal(json_obj['hash'], bb_hash)
-
+ block_json_string = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+'json')
+ block_json_obj = json.loads(block_json_string)
+ assert_equal(block_json_obj['hash'], bb_hash)
+
+ # compare with json block header
+ response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"json", True)
+ assert_equal(response_header_json.status, 200)
+ response_header_json_str = response_header_json.read().decode('utf-8')
+ json_obj = json.loads(response_header_json_str, parse_float=Decimal)
+ assert_equal(len(json_obj), 1) #ensure that there is one header in the json response
+ assert_equal(json_obj[0]['hash'], bb_hash) #request/response hash should be the same
+
+ #compare with normal RPC block response
+ rpc_block_json = self.nodes[0].getblock(bb_hash)
+ assert_equal(json_obj[0]['hash'], rpc_block_json['hash'])
+ assert_equal(json_obj[0]['confirmations'], rpc_block_json['confirmations'])
+ assert_equal(json_obj[0]['height'], rpc_block_json['height'])
+ assert_equal(json_obj[0]['version'], rpc_block_json['version'])
+ assert_equal(json_obj[0]['merkleroot'], rpc_block_json['merkleroot'])
+ assert_equal(json_obj[0]['time'], rpc_block_json['time'])
+ assert_equal(json_obj[0]['nonce'], rpc_block_json['nonce'])
+ assert_equal(json_obj[0]['bits'], rpc_block_json['bits'])
+ assert_equal(json_obj[0]['difficulty'], rpc_block_json['difficulty'])
+ assert_equal(json_obj[0]['chainwork'], rpc_block_json['chainwork'])
+ assert_equal(json_obj[0]['previousblockhash'], rpc_block_json['previousblockhash'])
+
+ #see if we can get 5 headers in one response
+ self.nodes[1].generate(5)
+ self.sync_all()
+ response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/5/'+bb_hash+self.FORMAT_SEPARATOR+"json", True)
+ assert_equal(response_header_json.status, 200)
+ response_header_json_str = response_header_json.read().decode('utf-8')
+ json_obj = json.loads(response_header_json_str)
+ assert_equal(len(json_obj), 5) #now we should have 5 header objects
+
# do tx test
- tx_hash = json_obj['tx'][0]['txid'];
+ tx_hash = block_json_obj['tx'][0]['txid']
json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"json")
json_obj = json.loads(json_string)
assert_equal(json_obj['txid'], tx_hash)
-
+
# check hex format response
hex_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"hex", True)
- assert_equal(response.status, 200)
+ assert_equal(hex_string.status, 200)
assert_greater_than(int(response.getheader('content-length')), 10)
-
+
+
# check block tx details
# let's make 3 tx and mine them on node 1
txs = []
@@ -65,25 +293,43 @@ class RESTTest (BitcoinTestFramework):
txs.append(self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11))
txs.append(self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11))
self.sync_all()
-
+
+ # check that there are exactly 3 transactions in the TX memory pool before generating the block
+ json_string = http_get_call(url.hostname, url.port, '/rest/mempool/info'+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+ assert_equal(json_obj['size'], 3)
+ # the size of the memory pool should be greater than 3x ~100 bytes
+ assert_greater_than(json_obj['bytes'], 300)
+
+ # check that there are our submitted transactions in the TX memory pool
+ json_string = http_get_call(url.hostname, url.port, '/rest/mempool/contents'+self.FORMAT_SEPARATOR+'json')
+ json_obj = json.loads(json_string)
+ for tx in txs:
+ assert_equal(tx in json_obj, True)
+
# now mine the transactions
- newblockhash = self.nodes[1].setgenerate(True, 1)
+ newblockhash = self.nodes[1].generate(1)
self.sync_all()
-
+
#check if the 3 tx show up in the new block
json_string = http_get_call(url.hostname, url.port, '/rest/block/'+newblockhash[0]+self.FORMAT_SEPARATOR+'json')
json_obj = json.loads(json_string)
for tx in json_obj['tx']:
if not 'coinbase' in tx['vin'][0]: #exclude coinbase
assert_equal(tx['txid'] in txs, True)
-
+
#check the same but without tx details
json_string = http_get_call(url.hostname, url.port, '/rest/block/notxdetails/'+newblockhash[0]+self.FORMAT_SEPARATOR+'json')
json_obj = json.loads(json_string)
for tx in txs:
assert_equal(tx in json_obj['tx'], True)
-
-
+
+ #test rest bestblock
+ bb_hash = self.nodes[0].getbestblockhash()
+
+ json_string = http_get_call(url.hostname, url.port, '/rest/chaininfo.json')
+ json_obj = json.loads(json_string)
+ assert_equal(json_obj['bestblockhash'], bb_hash)
if __name__ == '__main__':
RESTTest ().main ()
diff --git a/qa/rpc-tests/rpcbind_test.py b/qa/rpc-tests/rpcbind_test.py
index 655e00b6e7..572273566b 100755
--- a/qa/rpc-tests/rpcbind_test.py
+++ b/qa/rpc-tests/rpcbind_test.py
@@ -1,24 +1,17 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Test for -rpcbind, as well as -rpcallowip and -rpcconnect
-# 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"))
+# TODO extend this test from the test framework (like all other tests)
-import json
-import shutil
-import subprocess
import tempfile
import traceback
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
-from netutil import *
+from test_framework.util import *
+from test_framework.netutil import *
def run_bind_test(tmpdir, allow_ips, connect_to, addresses, expected):
'''
@@ -31,7 +24,7 @@ def run_bind_test(tmpdir, allow_ips, connect_to, addresses, expected):
if allow_ips:
base_args += ['-rpcallowip=' + x for x in allow_ips]
binds = ['-rpcbind='+addr for addr in addresses]
- nodes = start_nodes(1, tmpdir, [base_args + binds], connect_to)
+ nodes = start_nodes(self.num_nodes, tmpdir, [base_args + binds], connect_to)
try:
pid = bitcoind_processes[0].pid
assert_equal(set(get_bind_addrs(pid)), set(expected))
@@ -45,11 +38,11 @@ def run_allowip_test(tmpdir, allow_ips, rpchost, rpcport):
at a non-localhost IP.
'''
base_args = ['-disablewallet', '-nolisten'] + ['-rpcallowip='+x for x in allow_ips]
- nodes = start_nodes(1, tmpdir, [base_args])
+ nodes = start_nodes(self.num_nodes, tmpdir, [base_args])
try:
# connect to node through non-loopback interface
url = "http://rt:rt@%s:%d" % (rpchost, rpcport,)
- node = AuthServiceProxy(url)
+ node = get_rpc_proxy(url, 1)
node.getinfo()
finally:
node = None # make sure connection will be garbage collected and closed
@@ -58,7 +51,7 @@ def run_allowip_test(tmpdir, allow_ips, rpchost, rpcport):
def run_test(tmpdir):
- assert(sys.platform == 'linux2') # due to OS-specific network stats queries, this test works only on Linux
+ assert(sys.platform.startswith('linux')) # due to OS-specific network stats queries, this test works only on Linux
# find the first non-loopback interface for testing
non_loopback_ip = None
for name,ip in all_interfaces():
diff --git a/qa/rpc-tests/segwit.py b/qa/rpc-tests/segwit.py
new file mode 100755
index 0000000000..d4c9a8afed
--- /dev/null
+++ b/qa/rpc-tests/segwit.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test the SegWit changeover logic
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.mininode import sha256, ripemd160
+import os
+import shutil
+
+NODE_0 = 0
+NODE_1 = 1
+NODE_2 = 2
+WIT_V0 = 0
+WIT_V1 = 1
+
+def witness_script(version, pubkey):
+ if (version == 0):
+ pubkeyhash = bytes_to_hex_str(ripemd160(sha256(hex_str_to_bytes(pubkey))))
+ pkscript = "0014" + pubkeyhash
+ elif (version == 1):
+ # 1-of-1 multisig
+ scripthash = bytes_to_hex_str(sha256(hex_str_to_bytes("5121" + pubkey + "51ae")))
+ pkscript = "0020" + scripthash
+ else:
+ assert("Wrong version" == "0 or 1")
+ return pkscript
+
+def addlength(script):
+ scriptlen = format(len(script)//2, 'x')
+ assert(len(scriptlen) == 2)
+ return scriptlen + script
+
+def create_witnessprogram(version, node, utxo, pubkey, encode_p2sh, amount):
+ pkscript = witness_script(version, pubkey);
+ if (encode_p2sh):
+ p2sh_hash = bytes_to_hex_str(ripemd160(sha256(hex_str_to_bytes(pkscript))))
+ pkscript = "a914"+p2sh_hash+"87"
+ inputs = []
+ outputs = {}
+ inputs.append({ "txid" : utxo["txid"], "vout" : utxo["vout"]} )
+ DUMMY_P2SH = "2MySexEGVzZpRgNQ1JdjdP5bRETznm3roQ2" # P2SH of "OP_1 OP_DROP"
+ outputs[DUMMY_P2SH] = amount
+ tx_to_witness = node.createrawtransaction(inputs,outputs)
+ #replace dummy output with our own
+ tx_to_witness = tx_to_witness[0:110] + addlength(pkscript) + tx_to_witness[-8:]
+ return tx_to_witness
+
+def send_to_witness(version, node, utxo, pubkey, encode_p2sh, amount, sign=True, insert_redeem_script=""):
+ tx_to_witness = create_witnessprogram(version, node, utxo, pubkey, encode_p2sh, amount)
+ if (sign):
+ signed = node.signrawtransaction(tx_to_witness)
+ assert("errors" not in signed or len(["errors"]) == 0)
+ return node.sendrawtransaction(signed["hex"])
+ else:
+ if (insert_redeem_script):
+ tx_to_witness = tx_to_witness[0:82] + addlength(insert_redeem_script) + tx_to_witness[84:]
+
+ return node.sendrawtransaction(tx_to_witness)
+
+def getutxo(txid):
+ utxo = {}
+ utxo["vout"] = 0
+ utxo["txid"] = txid
+ return utxo
+
+class SegWitTest(BitcoinTestFramework):
+
+ def setup_chain(self):
+ print("Initializing test directory "+self.options.tmpdir)
+ initialize_chain_clean(self.options.tmpdir, 3)
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-logtimemicros", "-debug", "-walletprematurewitness"]))
+ self.nodes.append(start_node(1, self.options.tmpdir, ["-logtimemicros", "-debug", "-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness"]))
+ self.nodes.append(start_node(2, self.options.tmpdir, ["-logtimemicros", "-debug", "-blockversion=536870915", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness"]))
+ connect_nodes(self.nodes[1], 0)
+ connect_nodes(self.nodes[2], 1)
+ connect_nodes(self.nodes[0], 2)
+ self.is_network_split = False
+ self.sync_all()
+
+ def success_mine(self, node, txid, sign, redeem_script=""):
+ send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
+ block = node.generate(1)
+ assert_equal(len(node.getblock(block[0])["tx"]), 2)
+ sync_blocks(self.nodes)
+
+ def skip_mine(self, node, txid, sign, redeem_script=""):
+ send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
+ block = node.generate(1)
+ assert_equal(len(node.getblock(block[0])["tx"]), 1)
+ sync_blocks(self.nodes)
+
+ def fail_accept(self, node, txid, sign, redeem_script=""):
+ try:
+ send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
+ except JSONRPCException as exp:
+ assert(exp.error["code"] == -26)
+ else:
+ raise AssertionError("Tx should not have been accepted")
+
+ def fail_mine(self, node, txid, sign, redeem_script=""):
+ send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
+ try:
+ node.generate(1)
+ except JSONRPCException as exp:
+ assert(exp.error["code"] == -1)
+ else:
+ raise AssertionError("Created valid block when TestBlockValidity should have failed")
+ sync_blocks(self.nodes)
+
+ def run_test(self):
+ self.nodes[0].generate(160) #block 160
+
+ self.pubkey = []
+ p2sh_ids = [] # p2sh_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE embedded in p2sh
+ wit_ids = [] # wit_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE via bare witness
+ for i in range(3):
+ newaddress = self.nodes[i].getnewaddress()
+ self.pubkey.append(self.nodes[i].validateaddress(newaddress)["pubkey"])
+ multiaddress = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]])
+ self.nodes[i].addwitnessaddress(newaddress)
+ self.nodes[i].addwitnessaddress(multiaddress)
+ p2sh_ids.append([])
+ wit_ids.append([])
+ for v in range(2):
+ p2sh_ids[i].append([])
+ wit_ids[i].append([])
+
+ for i in range(5):
+ for n in range(3):
+ for v in range(2):
+ wit_ids[n][v].append(send_to_witness(v, self.nodes[0], self.nodes[0].listunspent()[0], self.pubkey[n], False, Decimal("49.999")))
+ p2sh_ids[n][v].append(send_to_witness(v, self.nodes[0], self.nodes[0].listunspent()[0], self.pubkey[n], True, Decimal("49.999")))
+
+ self.nodes[0].generate(1) #block 161
+ sync_blocks(self.nodes)
+
+ # Make sure all nodes recognize the transactions as theirs
+ assert_equal(self.nodes[0].getbalance(), 60*50 - 60*50 + 20*Decimal("49.999") + 50)
+ assert_equal(self.nodes[1].getbalance(), 20*Decimal("49.999"))
+ assert_equal(self.nodes[2].getbalance(), 20*Decimal("49.999"))
+
+ self.nodes[0].generate(262) #block 423
+ sync_blocks(self.nodes)
+
+ print("Verify default node can't accept any witness format txs before fork")
+ # unsigned, no scriptsig
+ self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], False)
+ self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], False)
+ self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], False)
+ self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], False)
+ # unsigned with redeem script
+ self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], False, addlength(witness_script(0, self.pubkey[0])))
+ self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], False, addlength(witness_script(1, self.pubkey[0])))
+ # signed
+ self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True)
+ self.fail_accept(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True)
+ self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True)
+ self.fail_accept(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True)
+
+ print("Verify witness txs are skipped for mining before the fork")
+ self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][0], True) #block 424
+ self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][0], True) #block 425
+ self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][0], True) #block 426
+ self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][0], True) #block 427
+
+ # TODO: An old node would see these txs without witnesses and be able to mine them
+
+ print("Verify unsigned bare witness txs in versionbits-setting blocks are valid before the fork")
+ self.success_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][1], False) #block 428
+ self.success_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][1], False) #block 429
+
+ print("Verify unsigned p2sh witness txs without a redeem script are invalid")
+ self.fail_accept(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][1], False)
+ self.fail_accept(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][1], False)
+
+ print("Verify unsigned p2sh witness txs with a redeem script in versionbits-settings blocks are valid before the fork")
+ self.success_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][1], False, addlength(witness_script(0, self.pubkey[2]))) #block 430
+ self.success_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][1], False, addlength(witness_script(1, self.pubkey[2]))) #block 431
+
+ print("Verify previous witness txs skipped for mining can now be mined")
+ assert_equal(len(self.nodes[2].getrawmempool()), 4)
+ block = self.nodes[2].generate(1) #block 432 (first block with new rules; 432 = 144 * 3)
+ sync_blocks(self.nodes)
+ assert_equal(len(self.nodes[2].getrawmempool()), 0)
+ assert_equal(len(self.nodes[2].getblock(block[0])["tx"]), 5)
+
+ print("Verify witness txs without witness data are invalid after the fork")
+ self.fail_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][2], False)
+ self.fail_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][2], False)
+ self.fail_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][2], False, addlength(witness_script(0, self.pubkey[2])))
+ self.fail_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][2], False, addlength(witness_script(1, self.pubkey[2])))
+
+ print("Verify default node can now use witness txs")
+ self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True) #block 432
+ self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True) #block 433
+ self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True) #block 434
+ self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True) #block 435
+
+if __name__ == '__main__':
+ SegWitTest().main()
diff --git a/qa/rpc-tests/send.sh b/qa/rpc-tests/send.sh
deleted file mode 100755
index 2d54cc6ded..0000000000
--- a/qa/rpc-tests/send.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-# 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.
-TIMEOUT=10
-SIGNAL=HUP
-PIDFILE=.send.pid
-if [ $# -eq 0 ]; then
- echo -e "Usage:\t$0 <cmd>"
- echo -e "\tRuns <cmd> and wait ${TIMEOUT} seconds or until SIG${SIGNAL} is received."
- echo -e "\tReturns: 0 if SIG${SIGNAL} is received, 1 otherwise."
- echo -e "Or:\t$0 -STOP"
- echo -e "\tsends SIG${SIGNAL} to running send.sh"
- exit 0
-fi
-
-if [ $1 = "-STOP" ]; then
- if [ -s ${PIDFILE} ]; then
- kill -s ${SIGNAL} $(<$PIDFILE 2>/dev/null) 2>/dev/null
- fi
- exit 0
-fi
-
-trap '[[ ${PID} ]] && kill ${PID}' ${SIGNAL}
-trap 'rm -f ${PIDFILE}' EXIT
-echo $$ > ${PIDFILE}
-"$@"
-sleep ${TIMEOUT} & PID=$!
-wait ${PID} && exit 1
-
-exit 0
diff --git a/qa/rpc-tests/sendheaders.py b/qa/rpc-tests/sendheaders.py
new file mode 100755
index 0000000000..c3f3180b6b
--- /dev/null
+++ b/qa/rpc-tests/sendheaders.py
@@ -0,0 +1,623 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.mininode import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from test_framework.blocktools import create_block, create_coinbase
+
+'''
+SendHeadersTest -- test behavior of headers messages to announce blocks.
+
+Setup:
+
+- Two nodes, two p2p connections to node0. One p2p connection should only ever
+ receive inv's (omitted from testing description below, this is our control).
+ Second node is used for creating reorgs.
+
+Part 1: No headers announcements before "sendheaders"
+a. node mines a block [expect: inv]
+ send getdata for the block [expect: block]
+b. node mines another block [expect: inv]
+ send getheaders and getdata [expect: headers, then block]
+c. node mines another block [expect: inv]
+ peer mines a block, announces with header [expect: getdata]
+d. node mines another block [expect: inv]
+
+Part 2: After "sendheaders", headers announcements should generally work.
+a. peer sends sendheaders [expect: no response]
+ peer sends getheaders with current tip [expect: no response]
+b. node mines a block [expect: tip header]
+c. for N in 1, ..., 10:
+ * for announce-type in {inv, header}
+ - peer mines N blocks, announces with announce-type
+ [ expect: getheaders/getdata or getdata, deliver block(s) ]
+ - node mines a block [ expect: 1 header ]
+
+Part 3: Headers announcements stop after large reorg and resume after getheaders or inv from peer.
+- For response-type in {inv, getheaders}
+ * node mines a 7 block reorg [ expect: headers announcement of 8 blocks ]
+ * node mines an 8-block reorg [ expect: inv at tip ]
+ * peer responds with getblocks/getdata [expect: inv, blocks ]
+ * node mines another block [ expect: inv at tip, peer sends getdata, expect: block ]
+ * node mines another block at tip [ expect: inv ]
+ * peer responds with getheaders with an old hashstop more than 8 blocks back [expect: headers]
+ * peer requests block [ expect: block ]
+ * node mines another block at tip [ expect: inv, peer sends getdata, expect: block ]
+ * peer sends response-type [expect headers if getheaders, getheaders/getdata if mining new block]
+ * node mines 1 block [expect: 1 header, peer responds with getdata]
+
+Part 4: Test direct fetch behavior
+a. Announce 2 old block headers.
+ Expect: no getdata requests.
+b. Announce 3 new blocks via 1 headers message.
+ Expect: one getdata request for all 3 blocks.
+ (Send blocks.)
+c. Announce 1 header that forks off the last two blocks.
+ Expect: no response.
+d. Announce 1 more header that builds on that fork.
+ Expect: one getdata request for two blocks.
+e. Announce 16 more headers that build on that fork.
+ Expect: getdata request for 14 more blocks.
+f. Announce 1 more header that builds on that fork.
+ Expect: no response.
+
+Part 5: Test handling of headers that don't connect.
+a. Repeat 10 times:
+ 1. Announce a header that doesn't connect.
+ Expect: getheaders message
+ 2. Send headers chain.
+ Expect: getdata for the missing blocks, tip update.
+b. Then send 9 more headers that don't connect.
+ Expect: getheaders message each time.
+c. Announce a header that does connect.
+ Expect: no response.
+d. Announce 49 headers that don't connect.
+ Expect: getheaders message each time.
+e. Announce one more that doesn't connect.
+ Expect: disconnect.
+'''
+
+class BaseNode(NodeConnCB):
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.connection = None
+ self.last_inv = None
+ self.last_headers = None
+ self.last_block = None
+ self.ping_counter = 1
+ self.last_pong = msg_pong(0)
+ self.last_getdata = None
+ self.sleep_time = 0.05
+ self.block_announced = False
+ self.last_getheaders = None
+ self.disconnected = False
+
+ def clear_last_announcement(self):
+ with mininode_lock:
+ self.block_announced = False
+ self.last_inv = None
+ self.last_headers = None
+
+ def add_connection(self, conn):
+ self.connection = conn
+
+ # Request data for a list of block hashes
+ def get_data(self, block_hashes):
+ msg = msg_getdata()
+ for x in block_hashes:
+ msg.inv.append(CInv(2, x))
+ self.connection.send_message(msg)
+
+ def get_headers(self, locator, hashstop):
+ msg = msg_getheaders()
+ msg.locator.vHave = locator
+ msg.hashstop = hashstop
+ self.connection.send_message(msg)
+
+ def send_block_inv(self, blockhash):
+ msg = msg_inv()
+ msg.inv = [CInv(2, blockhash)]
+ self.connection.send_message(msg)
+
+ # Wrapper for the NodeConn's send_message function
+ def send_message(self, message):
+ self.connection.send_message(message)
+
+ def on_inv(self, conn, message):
+ self.last_inv = message
+ self.block_announced = True
+
+ def on_headers(self, conn, message):
+ self.last_headers = message
+ self.block_announced = True
+
+ def on_block(self, conn, message):
+ self.last_block = message.block
+ self.last_block.calc_sha256()
+
+ def on_getdata(self, conn, message):
+ self.last_getdata = message
+
+ def on_pong(self, conn, message):
+ self.last_pong = message
+
+ def on_getheaders(self, conn, message):
+ self.last_getheaders = message
+
+ def on_close(self, conn):
+ self.disconnected = True
+
+ # Test whether the last announcement we received had the
+ # right header or the right inv
+ # inv and headers should be lists of block hashes
+ def check_last_announcement(self, headers=None, inv=None):
+ expect_headers = headers if headers != None else []
+ expect_inv = inv if inv != None else []
+ test_function = lambda: self.block_announced
+ self.sync(test_function)
+ with mininode_lock:
+ self.block_announced = False
+
+ success = True
+ compare_inv = []
+ if self.last_inv != None:
+ compare_inv = [x.hash for x in self.last_inv.inv]
+ if compare_inv != expect_inv:
+ success = False
+
+ hash_headers = []
+ if self.last_headers != None:
+ # treat headers as a list of block hashes
+ hash_headers = [ x.sha256 for x in self.last_headers.headers ]
+ if hash_headers != expect_headers:
+ success = False
+
+ self.last_inv = None
+ self.last_headers = None
+ return success
+
+ # Syncing helpers
+ def sync(self, test_function, timeout=60):
+ while timeout > 0:
+ with mininode_lock:
+ if test_function():
+ return
+ time.sleep(self.sleep_time)
+ timeout -= self.sleep_time
+ raise AssertionError("Sync failed to complete")
+
+ def sync_with_ping(self, timeout=60):
+ self.send_message(msg_ping(nonce=self.ping_counter))
+ test_function = lambda: self.last_pong.nonce == self.ping_counter
+ self.sync(test_function, timeout)
+ self.ping_counter += 1
+ return
+
+ def wait_for_block(self, blockhash, timeout=60):
+ test_function = lambda: self.last_block != None and self.last_block.sha256 == blockhash
+ self.sync(test_function, timeout)
+ return
+
+ def wait_for_getheaders(self, timeout=60):
+ test_function = lambda: self.last_getheaders != None
+ self.sync(test_function, timeout)
+ return
+
+ def wait_for_getdata(self, hash_list, timeout=60):
+ if hash_list == []:
+ return
+
+ test_function = lambda: self.last_getdata != None and [x.hash for x in self.last_getdata.inv] == hash_list
+ self.sync(test_function, timeout)
+ return
+
+ def wait_for_disconnect(self, timeout=60):
+ test_function = lambda: self.disconnected
+ self.sync(test_function, timeout)
+ return
+
+ def send_header_for_blocks(self, new_blocks):
+ headers_message = msg_headers()
+ headers_message.headers = [ CBlockHeader(b) for b in new_blocks ]
+ self.send_message(headers_message)
+
+ def send_getblocks(self, locator):
+ getblocks_message = msg_getblocks()
+ getblocks_message.locator.vHave = locator
+ self.send_message(getblocks_message)
+
+# InvNode: This peer should only ever receive inv's, because it doesn't ever send a
+# "sendheaders" message.
+class InvNode(BaseNode):
+ def __init__(self):
+ BaseNode.__init__(self)
+
+# TestNode: This peer is the one we use for most of the testing.
+class TestNode(BaseNode):
+ def __init__(self):
+ BaseNode.__init__(self)
+
+class SendHeadersTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+
+ def setup_network(self):
+ self.nodes = []
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [["-debug", "-logtimemicros=1"]]*2)
+ connect_nodes(self.nodes[0], 1)
+
+ # mine count blocks and return the new tip
+ def mine_blocks(self, count):
+ # Clear out last block announcement from each p2p listener
+ [ x.clear_last_announcement() for x in self.p2p_connections ]
+ self.nodes[0].generate(count)
+ return int(self.nodes[0].getbestblockhash(), 16)
+
+ # mine a reorg that invalidates length blocks (replacing them with
+ # length+1 blocks).
+ # Note: we clear the state of our p2p connections after the
+ # to-be-reorged-out blocks are mined, so that we don't break later tests.
+ # return the list of block hashes newly mined
+ def mine_reorg(self, length):
+ self.nodes[0].generate(length) # make sure all invalidated blocks are node0's
+ sync_blocks(self.nodes, wait=0.1)
+ [x.clear_last_announcement() for x in self.p2p_connections]
+
+ tip_height = self.nodes[1].getblockcount()
+ hash_to_invalidate = self.nodes[1].getblockhash(tip_height-(length-1))
+ self.nodes[1].invalidateblock(hash_to_invalidate)
+ all_hashes = self.nodes[1].generate(length+1) # Must be longer than the orig chain
+ sync_blocks(self.nodes, wait=0.1)
+ return [int(x, 16) for x in all_hashes]
+
+ def run_test(self):
+ # Setup the p2p connections and start up the network thread.
+ inv_node = InvNode()
+ test_node = TestNode()
+
+ self.p2p_connections = [inv_node, test_node]
+
+ connections = []
+ connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], inv_node))
+ # Set nServices to 0 for test_node, so no block download will occur outside of
+ # direct fetching
+ connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node, services=0))
+ inv_node.add_connection(connections[0])
+ test_node.add_connection(connections[1])
+
+ NetworkThread().start() # Start up network handling in another thread
+
+ # Test logic begins here
+ inv_node.wait_for_verack()
+ test_node.wait_for_verack()
+
+ tip = int(self.nodes[0].getbestblockhash(), 16)
+
+ # PART 1
+ # 1. Mine a block; expect inv announcements each time
+ print("Part 1: headers don't start before sendheaders message...")
+ for i in range(4):
+ old_tip = tip
+ tip = self.mine_blocks(1)
+ assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
+ assert_equal(test_node.check_last_announcement(inv=[tip]), True)
+ # Try a few different responses; none should affect next announcement
+ if i == 0:
+ # first request the block
+ test_node.get_data([tip])
+ test_node.wait_for_block(tip, timeout=5)
+ elif i == 1:
+ # next try requesting header and block
+ test_node.get_headers(locator=[old_tip], hashstop=tip)
+ test_node.get_data([tip])
+ test_node.wait_for_block(tip)
+ test_node.clear_last_announcement() # since we requested headers...
+ elif i == 2:
+ # this time announce own block via headers
+ height = self.nodes[0].getblockcount()
+ last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
+ block_time = last_time + 1
+ new_block = create_block(tip, create_coinbase(height+1), block_time)
+ new_block.solve()
+ test_node.send_header_for_blocks([new_block])
+ test_node.wait_for_getdata([new_block.sha256], timeout=5)
+ test_node.send_message(msg_block(new_block))
+ test_node.sync_with_ping() # make sure this block is processed
+ inv_node.clear_last_announcement()
+ test_node.clear_last_announcement()
+
+ print("Part 1: success!")
+ print("Part 2: announce blocks with headers after sendheaders message...")
+ # PART 2
+ # 2. Send a sendheaders message and test that headers announcements
+ # commence and keep working.
+ test_node.send_message(msg_sendheaders())
+ prev_tip = int(self.nodes[0].getbestblockhash(), 16)
+ test_node.get_headers(locator=[prev_tip], hashstop=0)
+ test_node.sync_with_ping()
+
+ # Now that we've synced headers, headers announcements should work
+ tip = self.mine_blocks(1)
+ assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
+ assert_equal(test_node.check_last_announcement(headers=[tip]), True)
+
+ height = self.nodes[0].getblockcount()+1
+ block_time += 10 # Advance far enough ahead
+ for i in range(10):
+ # Mine i blocks, and alternate announcing either via
+ # inv (of tip) or via headers. After each, new blocks
+ # mined by the node should successfully be announced
+ # with block header, even though the blocks are never requested
+ for j in range(2):
+ blocks = []
+ for b in range(i+1):
+ blocks.append(create_block(tip, create_coinbase(height), block_time))
+ blocks[-1].solve()
+ tip = blocks[-1].sha256
+ block_time += 1
+ height += 1
+ if j == 0:
+ # Announce via inv
+ test_node.send_block_inv(tip)
+ test_node.wait_for_getdata([tip], timeout=5)
+ # Test that duplicate inv's won't result in duplicate
+ # getdata requests, or duplicate headers announcements
+ inv_node.send_block_inv(tip)
+ # Should have received a getheaders as well!
+ test_node.send_header_for_blocks(blocks)
+ test_node.wait_for_getdata([x.sha256 for x in blocks[0:-1]], timeout=5)
+ [ inv_node.send_block_inv(x.sha256) for x in blocks[0:-1] ]
+ inv_node.sync_with_ping()
+ else:
+ # Announce via headers
+ test_node.send_header_for_blocks(blocks)
+ test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=5)
+ # Test that duplicate headers won't result in duplicate
+ # getdata requests (the check is further down)
+ inv_node.send_header_for_blocks(blocks)
+ inv_node.sync_with_ping()
+ [ test_node.send_message(msg_block(x)) for x in blocks ]
+ test_node.sync_with_ping()
+ inv_node.sync_with_ping()
+ # This block should not be announced to the inv node (since it also
+ # broadcast it)
+ assert_equal(inv_node.last_inv, None)
+ assert_equal(inv_node.last_headers, None)
+ tip = self.mine_blocks(1)
+ assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
+ assert_equal(test_node.check_last_announcement(headers=[tip]), True)
+ height += 1
+ block_time += 1
+
+ print("Part 2: success!")
+
+ print("Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer...")
+
+ # PART 3. Headers announcements can stop after large reorg, and resume after
+ # getheaders or inv from peer.
+ for j in range(2):
+ # First try mining a reorg that can propagate with header announcement
+ new_block_hashes = self.mine_reorg(length=7)
+ tip = new_block_hashes[-1]
+ assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
+ assert_equal(test_node.check_last_announcement(headers=new_block_hashes), True)
+
+ block_time += 8
+
+ # Mine a too-large reorg, which should be announced with a single inv
+ new_block_hashes = self.mine_reorg(length=8)
+ tip = new_block_hashes[-1]
+ assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
+ assert_equal(test_node.check_last_announcement(inv=[tip]), True)
+
+ block_time += 9
+
+ fork_point = self.nodes[0].getblock("%02x" % new_block_hashes[0])["previousblockhash"]
+ fork_point = int(fork_point, 16)
+
+ # Use getblocks/getdata
+ test_node.send_getblocks(locator = [fork_point])
+ assert_equal(test_node.check_last_announcement(inv=new_block_hashes), True)
+ test_node.get_data(new_block_hashes)
+ test_node.wait_for_block(new_block_hashes[-1])
+
+ for i in range(3):
+ # Mine another block, still should get only an inv
+ tip = self.mine_blocks(1)
+ assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
+ assert_equal(test_node.check_last_announcement(inv=[tip]), True)
+ if i == 0:
+ # Just get the data -- shouldn't cause headers announcements to resume
+ test_node.get_data([tip])
+ test_node.wait_for_block(tip)
+ elif i == 1:
+ # Send a getheaders message that shouldn't trigger headers announcements
+ # to resume (best header sent will be too old)
+ test_node.get_headers(locator=[fork_point], hashstop=new_block_hashes[1])
+ test_node.get_data([tip])
+ test_node.wait_for_block(tip)
+ elif i == 2:
+ test_node.get_data([tip])
+ test_node.wait_for_block(tip)
+ # This time, try sending either a getheaders to trigger resumption
+ # of headers announcements, or mine a new block and inv it, also
+ # triggering resumption of headers announcements.
+ if j == 0:
+ test_node.get_headers(locator=[tip], hashstop=0)
+ test_node.sync_with_ping()
+ else:
+ test_node.send_block_inv(tip)
+ test_node.sync_with_ping()
+ # New blocks should now be announced with header
+ tip = self.mine_blocks(1)
+ assert_equal(inv_node.check_last_announcement(inv=[tip]), True)
+ assert_equal(test_node.check_last_announcement(headers=[tip]), True)
+
+ print("Part 3: success!")
+
+ print("Part 4: Testing direct fetch behavior...")
+ tip = self.mine_blocks(1)
+ height = self.nodes[0].getblockcount() + 1
+ last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time']
+ block_time = last_time + 1
+
+ # Create 2 blocks. Send the blocks, then send the headers.
+ blocks = []
+ for b in range(2):
+ blocks.append(create_block(tip, create_coinbase(height), block_time))
+ blocks[-1].solve()
+ tip = blocks[-1].sha256
+ block_time += 1
+ height += 1
+ inv_node.send_message(msg_block(blocks[-1]))
+
+ inv_node.sync_with_ping() # Make sure blocks are processed
+ test_node.last_getdata = None
+ test_node.send_header_for_blocks(blocks)
+ test_node.sync_with_ping()
+ # should not have received any getdata messages
+ with mininode_lock:
+ assert_equal(test_node.last_getdata, None)
+
+ # This time, direct fetch should work
+ blocks = []
+ for b in range(3):
+ blocks.append(create_block(tip, create_coinbase(height), block_time))
+ blocks[-1].solve()
+ tip = blocks[-1].sha256
+ block_time += 1
+ height += 1
+
+ test_node.send_header_for_blocks(blocks)
+ test_node.sync_with_ping()
+ test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=test_node.sleep_time)
+
+ [ test_node.send_message(msg_block(x)) for x in blocks ]
+
+ test_node.sync_with_ping()
+
+ # Now announce a header that forks the last two blocks
+ tip = blocks[0].sha256
+ height -= 1
+ blocks = []
+
+ # Create extra blocks for later
+ for b in range(20):
+ blocks.append(create_block(tip, create_coinbase(height), block_time))
+ blocks[-1].solve()
+ tip = blocks[-1].sha256
+ block_time += 1
+ height += 1
+
+ # Announcing one block on fork should not trigger direct fetch
+ # (less work than tip)
+ test_node.last_getdata = None
+ test_node.send_header_for_blocks(blocks[0:1])
+ test_node.sync_with_ping()
+ with mininode_lock:
+ assert_equal(test_node.last_getdata, None)
+
+ # Announcing one more block on fork should trigger direct fetch for
+ # both blocks (same work as tip)
+ test_node.send_header_for_blocks(blocks[1:2])
+ test_node.sync_with_ping()
+ test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=test_node.sleep_time)
+
+ # Announcing 16 more headers should trigger direct fetch for 14 more
+ # blocks
+ test_node.send_header_for_blocks(blocks[2:18])
+ test_node.sync_with_ping()
+ test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=test_node.sleep_time)
+
+ # Announcing 1 more header should not trigger any response
+ test_node.last_getdata = None
+ test_node.send_header_for_blocks(blocks[18:19])
+ test_node.sync_with_ping()
+ with mininode_lock:
+ assert_equal(test_node.last_getdata, None)
+
+ print("Part 4: success!")
+
+ # Now deliver all those blocks we announced.
+ [ test_node.send_message(msg_block(x)) for x in blocks ]
+
+ print("Part 5: Testing handling of unconnecting headers")
+ # First we test that receipt of an unconnecting header doesn't prevent
+ # chain sync.
+ for i in range(10):
+ test_node.last_getdata = None
+ blocks = []
+ # Create two more blocks.
+ for j in range(2):
+ blocks.append(create_block(tip, create_coinbase(height), block_time))
+ blocks[-1].solve()
+ tip = blocks[-1].sha256
+ block_time += 1
+ height += 1
+ # Send the header of the second block -> this won't connect.
+ with mininode_lock:
+ test_node.last_getheaders = None
+ test_node.send_header_for_blocks([blocks[1]])
+ test_node.wait_for_getheaders(timeout=1)
+ test_node.send_header_for_blocks(blocks)
+ test_node.wait_for_getdata([x.sha256 for x in blocks])
+ [ test_node.send_message(msg_block(x)) for x in blocks ]
+ test_node.sync_with_ping()
+ assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256)
+
+ blocks = []
+ # Now we test that if we repeatedly don't send connecting headers, we
+ # don't go into an infinite loop trying to get them to connect.
+ MAX_UNCONNECTING_HEADERS = 10
+ for j in range(MAX_UNCONNECTING_HEADERS+1):
+ blocks.append(create_block(tip, create_coinbase(height), block_time))
+ blocks[-1].solve()
+ tip = blocks[-1].sha256
+ block_time += 1
+ height += 1
+
+ for i in range(1, MAX_UNCONNECTING_HEADERS):
+ # Send a header that doesn't connect, check that we get a getheaders.
+ with mininode_lock:
+ test_node.last_getheaders = None
+ test_node.send_header_for_blocks([blocks[i]])
+ test_node.wait_for_getheaders(timeout=1)
+
+ # Next header will connect, should re-set our count:
+ test_node.send_header_for_blocks([blocks[0]])
+
+ # Remove the first two entries (blocks[1] would connect):
+ blocks = blocks[2:]
+
+ # Now try to see how many unconnecting headers we can send
+ # before we get disconnected. Should be 5*MAX_UNCONNECTING_HEADERS
+ for i in range(5*MAX_UNCONNECTING_HEADERS - 1):
+ # Send a header that doesn't connect, check that we get a getheaders.
+ with mininode_lock:
+ test_node.last_getheaders = None
+ test_node.send_header_for_blocks([blocks[i%len(blocks)]])
+ test_node.wait_for_getheaders(timeout=1)
+
+ # Eventually this stops working.
+ with mininode_lock:
+ self.last_getheaders = None
+ test_node.send_header_for_blocks([blocks[-1]])
+
+ # Should get disconnected
+ test_node.wait_for_disconnect()
+ with mininode_lock:
+ self.last_getheaders = True
+
+ print("Part 5: success!")
+
+ # Finally, check that the inv node never received a getdata request,
+ # throughout the test
+ assert_equal(inv_node.last_getdata, None)
+
+if __name__ == '__main__':
+ SendHeadersTest().main()
diff --git a/qa/rpc-tests/signmessages.py b/qa/rpc-tests/signmessages.py
new file mode 100755
index 0000000000..31b6f14a26
--- /dev/null
+++ b/qa/rpc-tests/signmessages.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+
+class SignMessagesTest(BitcoinTestFramework):
+ """Tests RPC commands for signing and verifying messages."""
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+ self.is_network_split = False
+
+ def run_test(self):
+ message = 'This is just a test message'
+
+ # Test the signing with a privkey
+ privKey = 'cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N'
+ address = 'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB'
+ signature = self.nodes[0].signmessagewithprivkey(privKey, message)
+
+ # Verify the message
+ assert(self.nodes[0].verifymessage(address, signature, message))
+
+ # Test the signing with an address with wallet
+ address = self.nodes[0].getnewaddress()
+ signature = self.nodes[0].signmessage(address, message)
+
+ # Verify the message
+ assert(self.nodes[0].verifymessage(address, signature, message))
+
+if __name__ == '__main__':
+ SignMessagesTest().main()
diff --git a/qa/rpc-tests/signrawtransactions.py b/qa/rpc-tests/signrawtransactions.py
new file mode 100755
index 0000000000..c61a280616
--- /dev/null
+++ b/qa/rpc-tests/signrawtransactions.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+
+class SignRawTransactionsTest(BitcoinTestFramework):
+ """Tests transaction signing via RPC command "signrawtransaction"."""
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+ self.is_network_split = False
+
+ def successful_signing_test(self):
+ """Creates and signs a valid raw transaction with one input.
+
+ Expected results:
+
+ 1) The transaction has a complete set of signatures
+ 2) No script verification error occurred"""
+ privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N']
+
+ inputs = [
+ # Valid pay-to-pubkey script
+ {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0,
+ 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}
+ ]
+
+ outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
+
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ rawTxSigned = self.nodes[0].signrawtransaction(rawTx, inputs, privKeys)
+
+ # 1) The transaction has a complete set of signatures
+ assert 'complete' in rawTxSigned
+ assert_equal(rawTxSigned['complete'], True)
+
+ # 2) No script verification error occurred
+ assert 'errors' not in rawTxSigned
+
+ def script_verification_error_test(self):
+ """Creates and signs a raw transaction with valid (vin 0), invalid (vin 1) and one missing (vin 2) input script.
+
+ Expected results:
+
+ 3) The transaction has no complete set of signatures
+ 4) Two script verification errors occurred
+ 5) Script verification errors have certain properties ("txid", "vout", "scriptSig", "sequence", "error")
+ 6) The verification errors refer to the invalid (vin 1) and missing input (vin 2)"""
+ privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N']
+
+ inputs = [
+ # Valid pay-to-pubkey script
+ {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0},
+ # Invalid script
+ {'txid': '5b8673686910442c644b1f4993d8f7753c7c8fcb5c87ee40d56eaeef25204547', 'vout': 7},
+ # Missing scriptPubKey
+ {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 1},
+ ]
+
+ scripts = [
+ # Valid pay-to-pubkey script
+ {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0,
+ 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'},
+ # Invalid script
+ {'txid': '5b8673686910442c644b1f4993d8f7753c7c8fcb5c87ee40d56eaeef25204547', 'vout': 7,
+ 'scriptPubKey': 'badbadbadbad'}
+ ]
+
+ outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
+
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ rawTxSigned = self.nodes[0].signrawtransaction(rawTx, scripts, privKeys)
+
+ # 3) The transaction has no complete set of signatures
+ assert 'complete' in rawTxSigned
+ assert_equal(rawTxSigned['complete'], False)
+
+ # 4) Two script verification errors occurred
+ assert 'errors' in rawTxSigned
+ assert_equal(len(rawTxSigned['errors']), 2)
+
+ # 5) Script verification errors have certain properties
+ assert 'txid' in rawTxSigned['errors'][0]
+ assert 'vout' in rawTxSigned['errors'][0]
+ assert 'scriptSig' in rawTxSigned['errors'][0]
+ assert 'sequence' in rawTxSigned['errors'][0]
+ assert 'error' in rawTxSigned['errors'][0]
+
+ # 6) The verification errors refer to the invalid (vin 1) and missing input (vin 2)
+ assert_equal(rawTxSigned['errors'][0]['txid'], inputs[1]['txid'])
+ assert_equal(rawTxSigned['errors'][0]['vout'], inputs[1]['vout'])
+ assert_equal(rawTxSigned['errors'][1]['txid'], inputs[2]['txid'])
+ assert_equal(rawTxSigned['errors'][1]['vout'], inputs[2]['vout'])
+
+ def run_test(self):
+ self.successful_signing_test()
+ self.script_verification_error_test()
+
+
+if __name__ == '__main__':
+ SignRawTransactionsTest().main()
diff --git a/qa/rpc-tests/smartfees.py b/qa/rpc-tests/smartfees.py
index 924d160102..d76fba4b07 100755
--- a/qa/rpc-tests/smartfees.py
+++ b/qa/rpc-tests/smartfees.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -7,86 +7,261 @@
# Test fee estimation code
#
-from test_framework import BitcoinTestFramework
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
+from collections import OrderedDict
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+# Construct 2 trivial P2SH's and the ScriptSigs that spend them
+# So we can create many many transactions without needing to spend
+# time signing.
+P2SH_1 = "2MySexEGVzZpRgNQ1JdjdP5bRETznm3roQ2" # P2SH of "OP_1 OP_DROP"
+P2SH_2 = "2NBdpwq8Aoo1EEKEXPNrKvr5xQr3M9UfcZA" # P2SH of "OP_2 OP_DROP"
+# Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2
+# 4 bytes of OP_TRUE and push 2-byte redeem script of "OP_1 OP_DROP" or "OP_2 OP_DROP"
+SCRIPT_SIG = ["0451025175", "0451025275"]
+
+def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment):
+ '''
+ Create and send a transaction with a random fee.
+ The transaction pays to a trivial P2SH script, and assumes that its inputs
+ are of the same form.
+ The function takes a list of confirmed outputs and unconfirmed outputs
+ and attempts to use the confirmed list first for its inputs.
+ It adds the newly created outputs to the unconfirmed list.
+ Returns (raw transaction, fee)
+ '''
+ # It's best to exponentially distribute our random fees
+ # because the buckets are exponentially spaced.
+ # Exponentially distributed from 1-128 * fee_increment
+ rand_fee = float(fee_increment)*(1.1892**random.randint(0,28))
+ # Total fee ranges from min_fee to min_fee + 127*fee_increment
+ fee = min_fee - fee_increment + satoshi_round(rand_fee)
+ inputs = []
+ total_in = Decimal("0.00000000")
+ while total_in <= (amount + fee) and len(conflist) > 0:
+ t = conflist.pop(0)
+ total_in += t["amount"]
+ inputs.append({ "txid" : t["txid"], "vout" : t["vout"]} )
+ if total_in <= amount + fee:
+ while total_in <= (amount + fee) and len(unconflist) > 0:
+ t = unconflist.pop(0)
+ total_in += t["amount"]
+ inputs.append({ "txid" : t["txid"], "vout" : t["vout"]} )
+ if total_in <= amount + fee:
+ raise RuntimeError("Insufficient funds: need %d, have %d"%(amount+fee, total_in))
+ outputs = {}
+ outputs = OrderedDict([(P2SH_1, total_in - amount - fee),
+ (P2SH_2, amount)])
+ rawtx = from_node.createrawtransaction(inputs, outputs)
+ # createrawtransaction constructs a transaction that is ready to be signed.
+ # These transactions don't need to be signed, but we still have to insert the ScriptSig
+ # that will satisfy the ScriptPubKey.
+ completetx = rawtx[0:10]
+ inputnum = 0
+ for inp in inputs:
+ completetx += rawtx[10+82*inputnum:82+82*inputnum]
+ completetx += SCRIPT_SIG[inp["vout"]]
+ completetx += rawtx[84+82*inputnum:92+82*inputnum]
+ inputnum += 1
+ completetx += rawtx[10+82*inputnum:]
+ txid = from_node.sendrawtransaction(completetx, True)
+ unconflist.append({ "txid" : txid, "vout" : 0 , "amount" : total_in - amount - fee})
+ unconflist.append({ "txid" : txid, "vout" : 1 , "amount" : amount})
+
+ return (completetx, fee)
+
+def split_inputs(from_node, txins, txouts, initial_split = False):
+ '''
+ We need to generate a lot of very small inputs so we can generate a ton of transactions
+ and they will have low priority.
+ This function takes an input from txins, and creates and sends a transaction
+ which splits the value into 2 outputs which are appended to txouts.
+ '''
+ prevtxout = txins.pop()
+ inputs = []
+ inputs.append({ "txid" : prevtxout["txid"], "vout" : prevtxout["vout"] })
+ half_change = satoshi_round(prevtxout["amount"]/2)
+ rem_change = prevtxout["amount"] - half_change - Decimal("0.00001000")
+ outputs = OrderedDict([(P2SH_1, half_change), (P2SH_2, rem_change)])
+ rawtx = from_node.createrawtransaction(inputs, outputs)
+ # If this is the initial split we actually need to sign the transaction
+ # Otherwise we just need to insert the property ScriptSig
+ if (initial_split) :
+ completetx = from_node.signrawtransaction(rawtx)["hex"]
+ else :
+ completetx = rawtx[0:82] + SCRIPT_SIG[prevtxout["vout"]] + rawtx[84:]
+ txid = from_node.sendrawtransaction(completetx, True)
+ txouts.append({ "txid" : txid, "vout" : 0 , "amount" : half_change})
+ txouts.append({ "txid" : txid, "vout" : 1 , "amount" : rem_change})
+
+def check_estimates(node, fees_seen, max_invalid, print_estimates = True):
+ '''
+ This function calls estimatefee and verifies that the estimates
+ meet certain invariants.
+ '''
+ all_estimates = [ node.estimatefee(i) for i in range(1,26) ]
+ if print_estimates:
+ print([str(all_estimates[e-1]) for e in [1,2,3,6,15,25]])
+ delta = 1.0e-6 # account for rounding error
+ last_e = max(fees_seen)
+ for e in [x for x in all_estimates if x >= 0]:
+ # Estimates should be within the bounds of what transactions fees actually were:
+ if float(e)+delta < min(fees_seen) or float(e)-delta > max(fees_seen):
+ raise AssertionError("Estimated fee (%f) out of range (%f,%f)"
+ %(float(e), min(fees_seen), max(fees_seen)))
+ # Estimates should be monotonically decreasing
+ if float(e)-delta > last_e:
+ raise AssertionError("Estimated fee (%f) larger than last fee (%f) for lower number of confirms"
+ %(float(e),float(last_e)))
+ last_e = e
+ valid_estimate = False
+ invalid_estimates = 0
+ for i,e in enumerate(all_estimates): # estimate is for i+1
+ if e >= 0:
+ valid_estimate = True
+ # estimatesmartfee should return the same result
+ assert_equal(node.estimatesmartfee(i+1)["feerate"], e)
+
+ else:
+ invalid_estimates += 1
+
+ # estimatesmartfee should still be valid
+ approx_estimate = node.estimatesmartfee(i+1)["feerate"]
+ answer_found = node.estimatesmartfee(i+1)["blocks"]
+ assert(approx_estimate > 0)
+ assert(answer_found > i+1)
+
+ # Once we're at a high enough confirmation count that we can give an estimate
+ # We should have estimates for all higher confirmation counts
+ if valid_estimate:
+ raise AssertionError("Invalid estimate appears at higher confirm count than valid estimate")
+
+ # Check on the expected number of different confirmation counts
+ # that we might not have valid estimates for
+ if invalid_estimates > max_invalid:
+ raise AssertionError("More than (%d) invalid estimates"%(max_invalid))
+ return all_estimates
+
class EstimateFeeTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 3
+ self.setup_clean_chain = False
+
def setup_network(self):
+ '''
+ We'll setup the network to have 3 nodes that all mine with different parameters.
+ But first we need to use one node to create a lot of small low priority outputs
+ which we will use to generate our transactions.
+ '''
self.nodes = []
- self.nodes.append(start_node(0, self.options.tmpdir,
- ["-debug=mempool", "-debug=estimatefee"]))
- # Node1 mines small-but-not-tiny blocks, and allows free transactions.
+ # Use node0 to mine blocks for input splitting
+ self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000",
+ "-relaypriority=0", "-whitelist=127.0.0.1"]))
+
+ print("This test is time consuming, please be patient")
+ print("Splitting inputs to small size so we can generate low priority tx's")
+ self.txouts = []
+ self.txouts2 = []
+ # Split a coinbase into two transaction puzzle outputs
+ split_inputs(self.nodes[0], self.nodes[0].listunspent(0), self.txouts, True)
+
+ # Mine
+ while (len(self.nodes[0].getrawmempool()) > 0):
+ self.nodes[0].generate(1)
+
+ # Repeatedly split those 2 outputs, doubling twice for each rep
+ # Use txouts to monitor the available utxo, since these won't be tracked in wallet
+ reps = 0
+ while (reps < 5):
+ #Double txouts to txouts2
+ while (len(self.txouts)>0):
+ split_inputs(self.nodes[0], self.txouts, self.txouts2)
+ while (len(self.nodes[0].getrawmempool()) > 0):
+ self.nodes[0].generate(1)
+ #Double txouts2 to txouts
+ while (len(self.txouts2)>0):
+ split_inputs(self.nodes[0], self.txouts2, self.txouts)
+ while (len(self.nodes[0].getrawmempool()) > 0):
+ self.nodes[0].generate(1)
+ reps += 1
+ print("Finished splitting")
+
+ # Now we can connect the other nodes, didn't want to connect them earlier
+ # so the estimates would not be affected by the splitting transactions
+ # Node1 mines small blocks but that are bigger than the expected transaction rate,
+ # and allows free transactions.
# NOTE: the CreateNewBlock code starts counting block size at 1,000 bytes,
- # so blockmaxsize of 2,000 is really just 1,000 bytes (room enough for
- # 6 or 7 transactions)
+ # (17k is room enough for 110 or so transactions)
self.nodes.append(start_node(1, self.options.tmpdir,
- ["-blockprioritysize=1500", "-blockmaxsize=2000",
- "-debug=mempool", "-debug=estimatefee"]))
+ ["-blockprioritysize=1500", "-blockmaxsize=17000",
+ "-maxorphantx=1000", "-relaypriority=0", "-debug=estimatefee"]))
connect_nodes(self.nodes[1], 0)
# Node2 is a stingy miner, that
- # produces very small blocks (room for only 3 or so transactions)
- node2args = [ "-blockprioritysize=0", "-blockmaxsize=1500",
- "-debug=mempool", "-debug=estimatefee"]
+ # produces too small blocks (room for only 55 or so transactions)
+ node2args = ["-blockprioritysize=0", "-blockmaxsize=8000", "-maxorphantx=1000", "-relaypriority=0"]
+
self.nodes.append(start_node(2, self.options.tmpdir, node2args))
- connect_nodes(self.nodes[2], 0)
+ connect_nodes(self.nodes[0], 2)
+ connect_nodes(self.nodes[2], 1)
self.is_network_split = False
self.sync_all()
-
+
+ def transact_and_mine(self, numblocks, mining_node):
+ min_fee = Decimal("0.00001")
+ # We will now mine numblocks blocks generating on average 100 transactions between each block
+ # We shuffle our confirmed txout set before each set of transactions
+ # small_txpuzzle_randfee will use the transactions that have inputs already in the chain when possible
+ # resorting to tx's that depend on the mempool when those run out
+ for i in range(numblocks):
+ random.shuffle(self.confutxo)
+ for j in range(random.randrange(100-50,100+50)):
+ from_index = random.randint(1,2)
+ (txhex, fee) = small_txpuzzle_randfee(self.nodes[from_index], self.confutxo,
+ self.memutxo, Decimal("0.005"), min_fee, min_fee)
+ tx_kbytes = (len(txhex) // 2) / 1000.0
+ self.fees_per_kb.append(float(fee)/tx_kbytes)
+ sync_mempools(self.nodes[0:3],.1)
+ mined = mining_node.getblock(mining_node.generate(1)[0],True)["tx"]
+ sync_blocks(self.nodes[0:3],.1)
+ # update which txouts are confirmed
+ newmem = []
+ for utx in self.memutxo:
+ if utx["txid"] in mined:
+ self.confutxo.append(utx)
+ else:
+ newmem.append(utx)
+ self.memutxo = newmem
def run_test(self):
- # Prime the memory pool with pairs of transactions
- # (high-priority, random fee and zero-priority, random fee)
- min_fee = Decimal("0.001")
- fees_per_kb = [];
- for i in range(12):
- (txid, txhex, fee) = random_zeropri_transaction(self.nodes, Decimal("1.1"),
- min_fee, min_fee, 20)
- tx_kbytes = (len(txhex)/2)/1000.0
- fees_per_kb.append(float(fee)/tx_kbytes)
-
- # Mine blocks with node2 until the memory pool clears:
- count_start = self.nodes[2].getblockcount()
- while len(self.nodes[2].getrawmempool()) > 0:
- self.nodes[2].setgenerate(True, 1)
- self.sync_all()
-
- all_estimates = [ self.nodes[0].estimatefee(i) for i in range(1,20) ]
- print("Fee estimates, super-stingy miner: "+str([str(e) for e in all_estimates]))
+ self.fees_per_kb = []
+ self.memutxo = []
+ self.confutxo = self.txouts # Start with the set of confirmed txouts after splitting
+ print("Will output estimates for 1/2/3/6/15/25 blocks")
- # Estimates should be within the bounds of what transactions fees actually were:
- delta = 1.0e-6 # account for rounding error
- for e in filter(lambda x: x >= 0, all_estimates):
- if float(e)+delta < min(fees_per_kb) or float(e)-delta > max(fees_per_kb):
- raise AssertionError("Estimated fee (%f) out of range (%f,%f)"%(float(e), min_fee_kb, max_fee_kb))
-
- # Generate transactions while mining 30 more blocks, this time with node1:
- for i in range(30):
- for j in range(random.randrange(6-4,6+4)):
- (txid, txhex, fee) = random_transaction(self.nodes, Decimal("1.1"),
- Decimal("0.0"), min_fee, 20)
- tx_kbytes = (len(txhex)/2)/1000.0
- fees_per_kb.append(float(fee)/tx_kbytes)
- self.nodes[1].setgenerate(True, 1)
- self.sync_all()
-
- all_estimates = [ self.nodes[0].estimatefee(i) for i in range(1,20) ]
- print("Fee estimates, more generous miner: "+str([ str(e) for e in all_estimates]))
- for e in filter(lambda x: x >= 0, all_estimates):
- if float(e)+delta < min(fees_per_kb) or float(e)-delta > max(fees_per_kb):
- raise AssertionError("Estimated fee (%f) out of range (%f,%f)"%(float(e), min_fee_kb, max_fee_kb))
+ for i in range(2):
+ print("Creating transactions and mining them with a block size that can't keep up")
+ # Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine
+ self.transact_and_mine(10, self.nodes[2])
+ check_estimates(self.nodes[1], self.fees_per_kb, 14)
- # Finish by mining a normal-sized block:
- while len(self.nodes[0].getrawmempool()) > 0:
- self.nodes[0].setgenerate(True, 1)
- self.sync_all()
+ print("Creating transactions and mining them at a block size that is just big enough")
+ # Generate transactions while mining 10 more blocks, this time with node1
+ # which mines blocks with capacity just above the rate that transactions are being created
+ self.transact_and_mine(10, self.nodes[1])
+ check_estimates(self.nodes[1], self.fees_per_kb, 2)
- final_estimates = [ self.nodes[0].estimatefee(i) for i in range(1,20) ]
- print("Final fee estimates: "+str([ str(e) for e in final_estimates]))
+ # Finish by mining a normal-sized block:
+ while len(self.nodes[1].getrawmempool()) > 0:
+ self.nodes[1].generate(1)
+ sync_blocks(self.nodes[0:3],.1)
+ print("Final estimates after emptying mempools")
+ check_estimates(self.nodes[1], self.fees_per_kb, 2)
if __name__ == '__main__':
EstimateFeeTest().main()
diff --git a/qa/rpc-tests/test_framework.py b/qa/rpc-tests/test_framework.py
deleted file mode 100755
index 6c4ec073c2..0000000000
--- a/qa/rpc-tests/test_framework.py
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/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.
-
-# Base class for RPC 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"))
-
-import shutil
-import tempfile
-import traceback
-
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
-
-
-class BitcoinTestFramework(object):
-
- # These may be over-ridden by subclasses:
- def run_test(self):
- for node in self.nodes:
- assert_equal(node.getblockcount(), 200)
- assert_equal(node.getbalance(), 25*50)
-
- def add_options(self, parser):
- pass
-
- def setup_chain(self):
- print("Initializing test directory "+self.options.tmpdir)
- initialize_chain(self.options.tmpdir)
-
- def setup_network(self, split = False):
- self.nodes = start_nodes(4, self.options.tmpdir)
-
- # Connect the nodes as a "chain". This allows us
- # to split the network between nodes 1 and 2 to get
- # two halves that can work on competing chains.
-
- # If we joined network halves, connect the nodes from the joint
- # on outward. This ensures that chains are properly reorganised.
- if not split:
- connect_nodes_bi(self.nodes, 1, 2)
- sync_blocks(self.nodes[1:3])
- sync_mempools(self.nodes[1:3])
-
- connect_nodes_bi(self.nodes, 0, 1)
- connect_nodes_bi(self.nodes, 2, 3)
- self.is_network_split = split
- self.sync_all()
-
- def split_network(self):
- """
- Split the network of four nodes into nodes 0/1 and 2/3.
- """
- assert not self.is_network_split
- stop_nodes(self.nodes)
- wait_bitcoinds()
- self.setup_network(True)
-
- def sync_all(self):
- if self.is_network_split:
- sync_blocks(self.nodes[:2])
- sync_blocks(self.nodes[2:])
- sync_mempools(self.nodes[:2])
- sync_mempools(self.nodes[2:])
- else:
- sync_blocks(self.nodes)
- sync_mempools(self.nodes)
-
- def join_network(self):
- """
- Join the (previously split) network halves together.
- """
- assert self.is_network_split
- stop_nodes(self.nodes)
- wait_bitcoinds()
- self.setup_network(False)
-
- def main(self):
- 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")
- parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true",
- help="Print out all RPC calls as they are made")
- self.add_options(parser)
- (self.options, self.args) = parser.parse_args()
-
- if self.options.trace_rpc:
- import logging
- logging.basicConfig(level=logging.DEBUG)
-
- os.environ['PATH'] = self.options.srcdir+":"+os.environ['PATH']
-
- check_json_precision()
-
- success = False
- try:
- if not os.path.isdir(self.options.tmpdir):
- os.makedirs(self.options.tmpdir)
- self.setup_chain()
-
- self.setup_network()
-
- self.run_test()
-
- success = True
-
- except JSONRPCException as e:
- print("JSONRPC error: "+e.error['message'])
- traceback.print_tb(sys.exc_info()[2])
- except AssertionError as e:
- print("Assertion failed: "+e.message)
- traceback.print_tb(sys.exc_info()[2])
- except Exception as e:
- print("Unexpected exception caught during testing: "+str(e))
- traceback.print_tb(sys.exc_info()[2])
-
- if not self.options.nocleanup:
- print("Cleaning up")
- stop_nodes(self.nodes)
- wait_bitcoinds()
- shutil.rmtree(self.options.tmpdir)
-
- if success:
- print("Tests successful")
- sys.exit(0)
- else:
- print("Failed")
- sys.exit(1)
diff --git a/qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/__init__.py b/qa/rpc-tests/test_framework/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/__init__.py
+++ b/qa/rpc-tests/test_framework/__init__.py
diff --git a/qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/authproxy.py b/qa/rpc-tests/test_framework/authproxy.py
index bc7d655fdf..d095a56ce7 100644
--- a/qa/rpc-tests/python-bitcoinrpc/bitcoinrpc/authproxy.py
+++ b/qa/rpc-tests/test_framework/authproxy.py
@@ -61,15 +61,17 @@ class JSONRPCException(Exception):
def EncodeDecimal(o):
if isinstance(o, decimal.Decimal):
- return round(o, 8)
+ return str(o)
raise TypeError(repr(o) + " is not JSON serializable")
class AuthServiceProxy(object):
__id_count = 0
- def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None):
+ # ensure_ascii: escape unicode as \uXXXX, passed to json.dumps
+ def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True):
self.__service_url = service_url
- self.__service_name = service_name
+ self._service_name = service_name
+ self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests
self.__url = urlparse.urlparse(service_url)
if self.__url.port is None:
port = 80
@@ -92,36 +94,54 @@ class AuthServiceProxy(object):
self.__conn = connection
elif self.__url.scheme == 'https':
self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
- None, None, False,
- timeout)
+ timeout=timeout)
else:
self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
- False, timeout)
+ timeout=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)
+ if self._service_name is not None:
+ name = "%s.%s" % (self._service_name, name)
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
+ def _request(self, method, path, postdata):
+ '''
+ Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
+ This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
+ '''
+ headers = {'Host': self.__url.hostname,
+ 'User-Agent': USER_AGENT,
+ 'Authorization': self.__auth_header,
+ 'Content-type': 'application/json'}
+ try:
+ self.__conn.request(method, path, postdata, headers)
+ return self._get_response()
+ except httplib.BadStatusLine as e:
+ if e.line == "''": # if connection was closed, try again
+ self.__conn.close()
+ self.__conn.request(method, path, postdata, headers)
+ return self._get_response()
+ else:
+ raise
+ except BrokenPipeError:
+ # Python 3.5+ raises this instead of BadStatusLine when the connection was reset
+ self.__conn.close()
+ self.__conn.request(method, path, postdata, headers)
+ return self._get_response()
+
def __call__(self, *args):
AuthServiceProxy.__id_count += 1
- log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self.__service_name,
- json.dumps(args, default=EncodeDecimal)))
+ log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self._service_name,
+ json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
postdata = json.dumps({'version': '1.1',
- 'method': self.__service_name,
+ 'method': self._service_name,
'params': args,
- 'id': AuthServiceProxy.__id_count}, default=EncodeDecimal)
- 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()
+ 'id': AuthServiceProxy.__id_count}, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
+ response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
if response['error'] is not None:
raise JSONRPCException(response['error'])
elif 'result' not in response:
@@ -131,15 +151,9 @@ class AuthServiceProxy(object):
return response['result']
def _batch(self, rpc_call_list):
- postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal)
+ postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
log.debug("--> "+postdata)
- 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()
+ return self._request('POST', self.__url.path, postdata.encode('utf-8'))
def _get_response(self):
http_response = self.__conn.getresponse()
@@ -147,10 +161,15 @@ class AuthServiceProxy(object):
raise JSONRPCException({
'code': -342, 'message': 'missing HTTP response from server'})
+ content_type = http_response.getheader('Content-Type')
+ if content_type != 'application/json':
+ raise JSONRPCException({
+ 'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
+
responsedata = http_response.read().decode('utf8')
response = json.loads(responsedata, parse_float=decimal.Decimal)
if "error" in response and response["error"] is None:
- log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal)))
+ log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
else:
log.debug("<-- "+responsedata)
return response
diff --git a/qa/rpc-tests/test_framework/bignum.py b/qa/rpc-tests/test_framework/bignum.py
new file mode 100644
index 0000000000..ef800e4d57
--- /dev/null
+++ b/qa/rpc-tests/test_framework/bignum.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+#
+# bignum.py
+#
+# This file is copied from python-bitcoinlib.
+#
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+
+"""Bignum routines"""
+
+
+import struct
+
+
+# generic big endian MPI format
+
+def bn_bytes(v, have_ext=False):
+ ext = 0
+ if have_ext:
+ ext = 1
+ return ((v.bit_length()+7)//8) + ext
+
+def bn2bin(v):
+ s = bytearray()
+ i = bn_bytes(v)
+ while i > 0:
+ s.append((v >> ((i-1) * 8)) & 0xff)
+ i -= 1
+ return s
+
+def bin2bn(s):
+ l = 0
+ for ch in s:
+ l = (l << 8) | ch
+ return l
+
+def bn2mpi(v):
+ have_ext = False
+ if v.bit_length() > 0:
+ have_ext = (v.bit_length() & 0x07) == 0
+
+ neg = False
+ if v < 0:
+ neg = True
+ v = -v
+
+ s = struct.pack(b">I", bn_bytes(v, have_ext))
+ ext = bytearray()
+ if have_ext:
+ ext.append(0)
+ v_bin = bn2bin(v)
+ if neg:
+ if have_ext:
+ ext[0] |= 0x80
+ else:
+ v_bin[0] |= 0x80
+ return s + ext + v_bin
+
+def mpi2bn(s):
+ if len(s) < 4:
+ return None
+ s_size = bytes(s[:4])
+ v_len = struct.unpack(b">I", s_size)[0]
+ if len(s) != (v_len + 4):
+ return None
+ if v_len == 0:
+ return 0
+
+ v_str = bytearray(s[4:])
+ neg = False
+ i = v_str[0]
+ if i & 0x80:
+ neg = True
+ i &= ~0x80
+ v_str[0] = i
+
+ v = bin2bn(v_str)
+
+ if neg:
+ return -v
+ return v
+
+# bitcoin-specific little endian format, with implicit size
+def mpi2vch(s):
+ r = s[4:] # strip size
+ r = r[::-1] # reverse string, converting BE->LE
+ return r
+
+def bn2vch(v):
+ return bytes(mpi2vch(bn2mpi(v)))
+
+def vch2mpi(s):
+ r = struct.pack(b">I", len(s)) # size
+ r += s[::-1] # reverse string, converting LE->BE
+ return r
+
+def vch2bn(s):
+ return mpi2bn(vch2mpi(s))
+
diff --git a/qa/rpc-tests/test_framework/blockstore.py b/qa/rpc-tests/test_framework/blockstore.py
new file mode 100644
index 0000000000..6120dd574b
--- /dev/null
+++ b/qa/rpc-tests/test_framework/blockstore.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+# BlockStore: a helper class that keeps a map of blocks and implements
+# helper functions for responding to getheaders and getdata,
+# and for constructing a getheaders message
+#
+
+from .mininode import *
+from io import BytesIO
+import dbm.ndbm
+
+class BlockStore(object):
+ def __init__(self, datadir):
+ self.blockDB = dbm.ndbm.open(datadir + "/blocks", 'c')
+ self.currentBlock = 0
+ self.headers_map = dict()
+
+ def close(self):
+ self.blockDB.close()
+
+ def erase(self, blockhash):
+ del self.blockDB[repr(blockhash)]
+
+ # lookup an entry and return the item as raw bytes
+ def get(self, blockhash):
+ value = None
+ try:
+ value = self.blockDB[repr(blockhash)]
+ except KeyError:
+ return None
+ return value
+
+ # lookup an entry and return it as a CBlock
+ def get_block(self, blockhash):
+ ret = None
+ serialized_block = self.get(blockhash)
+ if serialized_block is not None:
+ f = BytesIO(serialized_block)
+ ret = CBlock()
+ ret.deserialize(f)
+ ret.calc_sha256()
+ return ret
+
+ def get_header(self, blockhash):
+ try:
+ return self.headers_map[blockhash]
+ except KeyError:
+ return None
+
+ # Note: this pulls full blocks out of the database just to retrieve
+ # the headers -- perhaps we could keep a separate data structure
+ # to avoid this overhead.
+ def headers_for(self, locator, hash_stop, current_tip=None):
+ if current_tip is None:
+ current_tip = self.currentBlock
+ current_block_header = self.get_header(current_tip)
+ if current_block_header is None:
+ return None
+
+ response = msg_headers()
+ headersList = [ current_block_header ]
+ maxheaders = 2000
+ while (headersList[0].sha256 not in locator.vHave):
+ prevBlockHash = headersList[0].hashPrevBlock
+ prevBlockHeader = self.get_header(prevBlockHash)
+ if prevBlockHeader is not None:
+ headersList.insert(0, prevBlockHeader)
+ else:
+ break
+ headersList = headersList[:maxheaders] # truncate if we have too many
+ hashList = [x.sha256 for x in headersList]
+ index = len(headersList)
+ if (hash_stop in hashList):
+ index = hashList.index(hash_stop)+1
+ response.headers = headersList[:index]
+ return response
+
+ def add_block(self, block):
+ block.calc_sha256()
+ try:
+ self.blockDB[repr(block.sha256)] = bytes(block.serialize())
+ except TypeError as e:
+ print("Unexpected error: ", sys.exc_info()[0], e.args)
+ self.currentBlock = block.sha256
+ self.headers_map[block.sha256] = CBlockHeader(block)
+
+ def add_header(self, header):
+ self.headers_map[header.sha256] = header
+
+ # lookup the hashes in "inv", and return p2p messages for delivering
+ # blocks found.
+ def get_blocks(self, inv):
+ responses = []
+ for i in inv:
+ if (i.type == 2): # MSG_BLOCK
+ data = self.get(i.hash)
+ if data is not None:
+ # Use msg_generic to avoid re-serialization
+ responses.append(msg_generic(b"block", data))
+ return responses
+
+ def get_locator(self, current_tip=None):
+ if current_tip is None:
+ current_tip = self.currentBlock
+ r = []
+ counter = 0
+ step = 1
+ lastBlock = self.get_block(current_tip)
+ while lastBlock is not None:
+ r.append(lastBlock.hashPrevBlock)
+ for i in range(step):
+ lastBlock = self.get_block(lastBlock.hashPrevBlock)
+ if lastBlock is None:
+ break
+ counter += 1
+ if counter > 10:
+ step *= 2
+ locator = CBlockLocator()
+ locator.vHave = r
+ return locator
+
+class TxStore(object):
+ def __init__(self, datadir):
+ self.txDB = dbm.ndbm.open(datadir + "/transactions", 'c')
+
+ def close(self):
+ self.txDB.close()
+
+ # lookup an entry and return the item as raw bytes
+ def get(self, txhash):
+ value = None
+ try:
+ value = self.txDB[repr(txhash)]
+ except KeyError:
+ return None
+ return value
+
+ def get_transaction(self, txhash):
+ ret = None
+ serialized_tx = self.get(txhash)
+ if serialized_tx is not None:
+ f = BytesIO(serialized_tx)
+ ret = CTransaction()
+ ret.deserialize(f)
+ ret.calc_sha256()
+ return ret
+
+ def add_transaction(self, tx):
+ tx.calc_sha256()
+ try:
+ self.txDB[repr(tx.sha256)] = bytes(tx.serialize())
+ except TypeError as e:
+ print("Unexpected error: ", sys.exc_info()[0], e.args)
+
+ def get_transactions(self, inv):
+ responses = []
+ for i in inv:
+ if (i.type == 1): # MSG_TX
+ tx = self.get(i.hash)
+ if tx is not None:
+ responses.append(msg_generic(b"tx", tx))
+ return responses
diff --git a/qa/rpc-tests/test_framework/blocktools.py b/qa/rpc-tests/test_framework/blocktools.py
new file mode 100644
index 0000000000..f69958823c
--- /dev/null
+++ b/qa/rpc-tests/test_framework/blocktools.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# blocktools.py - utilities for manipulating blocks and transactions
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from .mininode import *
+from .script import CScript, OP_TRUE, OP_CHECKSIG, OP_RETURN
+
+# Create a block (with regtest difficulty)
+def create_block(hashprev, coinbase, nTime=None):
+ block = CBlock()
+ if nTime is None:
+ import time
+ block.nTime = int(time.time()+600)
+ else:
+ block.nTime = nTime
+ block.hashPrevBlock = hashprev
+ block.nBits = 0x207fffff # Will break after a difficulty adjustment...
+ block.vtx.append(coinbase)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.calc_sha256()
+ return block
+
+# From BIP141
+WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed"
+
+# According to BIP141, blocks with witness rules active must commit to the
+# hash of all in-block transactions including witness.
+def add_witness_commitment(block, nonce=0):
+ # First calculate the merkle root of the block's
+ # transactions, with witnesses.
+ witness_nonce = nonce
+ witness_root = block.calc_witness_merkle_root()
+ witness_commitment = uint256_from_str(hash256(ser_uint256(witness_root)+ser_uint256(witness_nonce)))
+ # witness_nonce should go to coinbase witness.
+ block.vtx[0].wit.vtxinwit = [CTxInWitness()]
+ block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ser_uint256(witness_nonce)]
+
+ # witness commitment is the last OP_RETURN output in coinbase
+ output_data = WITNESS_COMMITMENT_HEADER + ser_uint256(witness_commitment)
+ block.vtx[0].vout.append(CTxOut(0, CScript([OP_RETURN, output_data])))
+ block.vtx[0].rehash()
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.rehash()
+
+
+def serialize_script_num(value):
+ r = bytearray(0)
+ if value == 0:
+ return r
+ neg = value < 0
+ absvalue = -value if neg else value
+ while (absvalue):
+ r.append(int(absvalue & 0xff))
+ absvalue >>= 8
+ if r[-1] & 0x80:
+ r.append(0x80 if neg else 0)
+ elif neg:
+ r[-1] |= 0x80
+ return r
+
+# Create a coinbase transaction, assuming no miner fees.
+# If pubkey is passed in, the coinbase output will be a P2PK output;
+# otherwise an anyone-can-spend output.
+def create_coinbase(height, pubkey = None):
+ coinbase = CTransaction()
+ coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff),
+ ser_string(serialize_script_num(height)), 0xffffffff))
+ coinbaseoutput = CTxOut()
+ coinbaseoutput.nValue = 50 * COIN
+ halvings = int(height/150) # regtest
+ coinbaseoutput.nValue >>= halvings
+ if (pubkey != None):
+ coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG])
+ else:
+ coinbaseoutput.scriptPubKey = CScript([OP_TRUE])
+ coinbase.vout = [ coinbaseoutput ]
+ coinbase.calc_sha256()
+ return coinbase
+
+# Create a transaction.
+# If the scriptPubKey is not specified, make it anyone-can-spend.
+def create_transaction(prevtx, n, sig, value, scriptPubKey=CScript()):
+ tx = CTransaction()
+ assert(n < len(prevtx.vout))
+ tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), sig, 0xffffffff))
+ tx.vout.append(CTxOut(value, scriptPubKey))
+ tx.calc_sha256()
+ return tx
+
+def get_legacy_sigopcount_block(block, fAccurate=True):
+ count = 0
+ for tx in block.vtx:
+ count += get_legacy_sigopcount_tx(tx, fAccurate)
+ return count
+
+def get_legacy_sigopcount_tx(tx, fAccurate=True):
+ count = 0
+ for i in tx.vout:
+ count += i.scriptPubKey.GetSigOpCount(fAccurate)
+ for j in tx.vin:
+ # scriptSig might be of type bytes, so convert to CScript for the moment
+ count += CScript(j.scriptSig).GetSigOpCount(fAccurate)
+ return count
diff --git a/qa/rpc-tests/test_framework/comptool.py b/qa/rpc-tests/test_framework/comptool.py
new file mode 100755
index 0000000000..7c92d3f828
--- /dev/null
+++ b/qa/rpc-tests/test_framework/comptool.py
@@ -0,0 +1,401 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from .mininode import *
+from .blockstore import BlockStore, TxStore
+from .util import p2p_port
+
+'''
+This is a tool for comparing two or more bitcoinds to each other
+using a script provided.
+
+To use, create a class that implements get_tests(), and pass it in
+as the test generator to TestManager. get_tests() should be a python
+generator that returns TestInstance objects. See below for definition.
+'''
+
+# TestNode behaves as follows:
+# Configure with a BlockStore and TxStore
+# on_inv: log the message but don't request
+# on_headers: log the chain tip
+# on_pong: update ping response map (for synchronization)
+# on_getheaders: provide headers via BlockStore
+# on_getdata: provide blocks via BlockStore
+
+global mininode_lock
+
+class RejectResult(object):
+ '''
+ Outcome that expects rejection of a transaction or block.
+ '''
+ def __init__(self, code, reason=b''):
+ self.code = code
+ self.reason = reason
+ def match(self, other):
+ if self.code != other.code:
+ return False
+ return other.reason.startswith(self.reason)
+ def __repr__(self):
+ return '%i:%s' % (self.code,self.reason or '*')
+
+class TestNode(NodeConnCB):
+
+ def __init__(self, block_store, tx_store):
+ NodeConnCB.__init__(self)
+ self.conn = None
+ self.bestblockhash = None
+ self.block_store = block_store
+ self.block_request_map = {}
+ self.tx_store = tx_store
+ self.tx_request_map = {}
+ self.block_reject_map = {}
+ self.tx_reject_map = {}
+
+ # When the pingmap is non-empty we're waiting for
+ # a response
+ self.pingMap = {}
+ self.lastInv = []
+ self.closed = False
+
+ def on_close(self, conn):
+ self.closed = True
+
+ def add_connection(self, conn):
+ self.conn = conn
+
+ def on_headers(self, conn, message):
+ if len(message.headers) > 0:
+ best_header = message.headers[-1]
+ best_header.calc_sha256()
+ self.bestblockhash = best_header.sha256
+
+ def on_getheaders(self, conn, message):
+ response = self.block_store.headers_for(message.locator, message.hashstop)
+ if response is not None:
+ conn.send_message(response)
+
+ def on_getdata(self, conn, message):
+ [conn.send_message(r) for r in self.block_store.get_blocks(message.inv)]
+ [conn.send_message(r) for r in self.tx_store.get_transactions(message.inv)]
+
+ for i in message.inv:
+ if i.type == 1:
+ self.tx_request_map[i.hash] = True
+ elif i.type == 2:
+ self.block_request_map[i.hash] = True
+
+ def on_inv(self, conn, message):
+ self.lastInv = [x.hash for x in message.inv]
+
+ def on_pong(self, conn, message):
+ try:
+ del self.pingMap[message.nonce]
+ except KeyError:
+ raise AssertionError("Got pong for unknown ping [%s]" % repr(message))
+
+ def on_reject(self, conn, message):
+ if message.message == b'tx':
+ self.tx_reject_map[message.data] = RejectResult(message.code, message.reason)
+ if message.message == b'block':
+ self.block_reject_map[message.data] = RejectResult(message.code, message.reason)
+
+ def send_inv(self, obj):
+ mtype = 2 if isinstance(obj, CBlock) else 1
+ self.conn.send_message(msg_inv([CInv(mtype, obj.sha256)]))
+
+ def send_getheaders(self):
+ # We ask for headers from their last tip.
+ m = msg_getheaders()
+ m.locator = self.block_store.get_locator(self.bestblockhash)
+ self.conn.send_message(m)
+
+ # This assumes BIP31
+ def send_ping(self, nonce):
+ self.pingMap[nonce] = True
+ self.conn.send_message(msg_ping(nonce))
+
+ def received_ping_response(self, nonce):
+ return nonce not in self.pingMap
+
+ def send_mempool(self):
+ self.lastInv = []
+ self.conn.send_message(msg_mempool())
+
+# TestInstance:
+#
+# Instances of these are generated by the test generator, and fed into the
+# comptool.
+#
+# "blocks_and_transactions" should be an array of
+# [obj, True/False/None, hash/None]:
+# - obj is either a CBlock, CBlockHeader, or a CTransaction, and
+# - the second value indicates whether the object should be accepted
+# into the blockchain or mempool (for tests where we expect a certain
+# answer), or "None" if we don't expect a certain answer and are just
+# comparing the behavior of the nodes being tested.
+# - the third value is the hash to test the tip against (if None or omitted,
+# use the hash of the block)
+# - NOTE: if a block header, no test is performed; instead the header is
+# just added to the block_store. This is to facilitate block delivery
+# when communicating with headers-first clients (when withholding an
+# intermediate block).
+# sync_every_block: if True, then each block will be inv'ed, synced, and
+# nodes will be tested based on the outcome for the block. If False,
+# then inv's accumulate until all blocks are processed (or max inv size
+# is reached) and then sent out in one inv message. Then the final block
+# will be synced across all connections, and the outcome of the final
+# block will be tested.
+# sync_every_tx: analogous to behavior for sync_every_block, except if outcome
+# on the final tx is None, then contents of entire mempool are compared
+# across all connections. (If outcome of final tx is specified as true
+# or false, then only the last tx is tested against outcome.)
+
+class TestInstance(object):
+ def __init__(self, objects=None, sync_every_block=True, sync_every_tx=False):
+ self.blocks_and_transactions = objects if objects else []
+ self.sync_every_block = sync_every_block
+ self.sync_every_tx = sync_every_tx
+
+class TestManager(object):
+
+ def __init__(self, testgen, datadir):
+ self.test_generator = testgen
+ self.connections = []
+ self.test_nodes = []
+ self.block_store = BlockStore(datadir)
+ self.tx_store = TxStore(datadir)
+ self.ping_counter = 1
+
+ def add_all_connections(self, nodes):
+ for i in range(len(nodes)):
+ # Create a p2p connection to each node
+ test_node = TestNode(self.block_store, self.tx_store)
+ self.test_nodes.append(test_node)
+ self.connections.append(NodeConn('127.0.0.1', p2p_port(i), nodes[i], test_node))
+ # Make sure the TestNode (callback class) has a reference to its
+ # associated NodeConn
+ test_node.add_connection(self.connections[-1])
+
+ def clear_all_connections(self):
+ self.connections = []
+ self.test_nodes = []
+
+ def wait_for_disconnections(self):
+ def disconnected():
+ return all(node.closed for node in self.test_nodes)
+ return wait_until(disconnected, timeout=10)
+
+ def wait_for_verack(self):
+ def veracked():
+ return all(node.verack_received for node in self.test_nodes)
+ return wait_until(veracked, timeout=10)
+
+ def wait_for_pings(self, counter):
+ def received_pongs():
+ return all(node.received_ping_response(counter) for node in self.test_nodes)
+ return wait_until(received_pongs)
+
+ # sync_blocks: Wait for all connections to request the blockhash given
+ # then send get_headers to find out the tip of each node, and synchronize
+ # the response by using a ping (and waiting for pong with same nonce).
+ def sync_blocks(self, blockhash, num_blocks):
+ def blocks_requested():
+ return all(
+ blockhash in node.block_request_map and node.block_request_map[blockhash]
+ for node in self.test_nodes
+ )
+
+ # --> error if not requested
+ if not wait_until(blocks_requested, attempts=20*num_blocks):
+ # print [ c.cb.block_request_map for c in self.connections ]
+ raise AssertionError("Not all nodes requested block")
+
+ # Send getheaders message
+ [ c.cb.send_getheaders() for c in self.connections ]
+
+ # Send ping and wait for response -- synchronization hack
+ [ c.cb.send_ping(self.ping_counter) for c in self.connections ]
+ self.wait_for_pings(self.ping_counter)
+ self.ping_counter += 1
+
+ # Analogous to sync_block (see above)
+ def sync_transaction(self, txhash, num_events):
+ # Wait for nodes to request transaction (50ms sleep * 20 tries * num_events)
+ def transaction_requested():
+ return all(
+ txhash in node.tx_request_map and node.tx_request_map[txhash]
+ for node in self.test_nodes
+ )
+
+ # --> error if not requested
+ if not wait_until(transaction_requested, attempts=20*num_events):
+ # print [ c.cb.tx_request_map for c in self.connections ]
+ raise AssertionError("Not all nodes requested transaction")
+
+ # Get the mempool
+ [ c.cb.send_mempool() for c in self.connections ]
+
+ # Send ping and wait for response -- synchronization hack
+ [ c.cb.send_ping(self.ping_counter) for c in self.connections ]
+ self.wait_for_pings(self.ping_counter)
+ self.ping_counter += 1
+
+ # Sort inv responses from each node
+ with mininode_lock:
+ [ c.cb.lastInv.sort() for c in self.connections ]
+
+ # Verify that the tip of each connection all agree with each other, and
+ # with the expected outcome (if given)
+ def check_results(self, blockhash, outcome):
+ with mininode_lock:
+ for c in self.connections:
+ if outcome is None:
+ if c.cb.bestblockhash != self.connections[0].cb.bestblockhash:
+ return False
+ elif isinstance(outcome, RejectResult): # Check that block was rejected w/ code
+ if c.cb.bestblockhash == blockhash:
+ return False
+ if blockhash not in c.cb.block_reject_map:
+ print('Block not in reject map: %064x' % (blockhash))
+ return False
+ if not outcome.match(c.cb.block_reject_map[blockhash]):
+ print('Block rejected with %s instead of expected %s: %064x' % (c.cb.block_reject_map[blockhash], outcome, blockhash))
+ return False
+ elif ((c.cb.bestblockhash == blockhash) != outcome):
+ # print c.cb.bestblockhash, blockhash, outcome
+ return False
+ return True
+
+ # Either check that the mempools all agree with each other, or that
+ # txhash's presence in the mempool matches the outcome specified.
+ # This is somewhat of a strange comparison, in that we're either comparing
+ # a particular tx to an outcome, or the entire mempools altogether;
+ # perhaps it would be useful to add the ability to check explicitly that
+ # a particular tx's existence in the mempool is the same across all nodes.
+ def check_mempool(self, txhash, outcome):
+ with mininode_lock:
+ for c in self.connections:
+ if outcome is None:
+ # Make sure the mempools agree with each other
+ if c.cb.lastInv != self.connections[0].cb.lastInv:
+ # print c.rpc.getrawmempool()
+ return False
+ elif isinstance(outcome, RejectResult): # Check that tx was rejected w/ code
+ if txhash in c.cb.lastInv:
+ return False
+ if txhash not in c.cb.tx_reject_map:
+ print('Tx not in reject map: %064x' % (txhash))
+ return False
+ if not outcome.match(c.cb.tx_reject_map[txhash]):
+ print('Tx rejected with %s instead of expected %s: %064x' % (c.cb.tx_reject_map[txhash], outcome, txhash))
+ return False
+ elif ((txhash in c.cb.lastInv) != outcome):
+ # print c.rpc.getrawmempool(), c.cb.lastInv
+ return False
+ return True
+
+ def run(self):
+ # Wait until verack is received
+ self.wait_for_verack()
+
+ test_number = 1
+ for test_instance in self.test_generator.get_tests():
+ # We use these variables to keep track of the last block
+ # and last transaction in the tests, which are used
+ # if we're not syncing on every block or every tx.
+ [ block, block_outcome, tip ] = [ None, None, None ]
+ [ tx, tx_outcome ] = [ None, None ]
+ invqueue = []
+
+ for test_obj in test_instance.blocks_and_transactions:
+ b_or_t = test_obj[0]
+ outcome = test_obj[1]
+ # Determine if we're dealing with a block or tx
+ if isinstance(b_or_t, CBlock): # Block test runner
+ block = b_or_t
+ block_outcome = outcome
+ tip = block.sha256
+ # each test_obj can have an optional third argument
+ # to specify the tip we should compare with
+ # (default is to use the block being tested)
+ if len(test_obj) >= 3:
+ tip = test_obj[2]
+
+ # Add to shared block_store, set as current block
+ # If there was an open getdata request for the block
+ # previously, and we didn't have an entry in the
+ # block_store, then immediately deliver, because the
+ # node wouldn't send another getdata request while
+ # the earlier one is outstanding.
+ first_block_with_hash = True
+ if self.block_store.get(block.sha256) is not None:
+ first_block_with_hash = False
+ with mininode_lock:
+ self.block_store.add_block(block)
+ for c in self.connections:
+ if first_block_with_hash and block.sha256 in c.cb.block_request_map and c.cb.block_request_map[block.sha256] == True:
+ # There was a previous request for this block hash
+ # Most likely, we delivered a header for this block
+ # but never had the block to respond to the getdata
+ c.send_message(msg_block(block))
+ else:
+ c.cb.block_request_map[block.sha256] = False
+ # Either send inv's to each node and sync, or add
+ # to invqueue for later inv'ing.
+ if (test_instance.sync_every_block):
+ [ c.cb.send_inv(block) for c in self.connections ]
+ self.sync_blocks(block.sha256, 1)
+ if (not self.check_results(tip, outcome)):
+ raise AssertionError("Test failed at test %d" % test_number)
+ else:
+ invqueue.append(CInv(2, block.sha256))
+ elif isinstance(b_or_t, CBlockHeader):
+ block_header = b_or_t
+ self.block_store.add_header(block_header)
+ else: # Tx test runner
+ assert(isinstance(b_or_t, CTransaction))
+ tx = b_or_t
+ tx_outcome = outcome
+ # Add to shared tx store and clear map entry
+ with mininode_lock:
+ self.tx_store.add_transaction(tx)
+ for c in self.connections:
+ c.cb.tx_request_map[tx.sha256] = False
+ # Again, either inv to all nodes or save for later
+ if (test_instance.sync_every_tx):
+ [ c.cb.send_inv(tx) for c in self.connections ]
+ self.sync_transaction(tx.sha256, 1)
+ if (not self.check_mempool(tx.sha256, outcome)):
+ raise AssertionError("Test failed at test %d" % test_number)
+ else:
+ invqueue.append(CInv(1, tx.sha256))
+ # Ensure we're not overflowing the inv queue
+ if len(invqueue) == MAX_INV_SZ:
+ [ c.send_message(msg_inv(invqueue)) for c in self.connections ]
+ invqueue = []
+
+ # Do final sync if we weren't syncing on every block or every tx.
+ if (not test_instance.sync_every_block and block is not None):
+ if len(invqueue) > 0:
+ [ c.send_message(msg_inv(invqueue)) for c in self.connections ]
+ invqueue = []
+ self.sync_blocks(block.sha256, len(test_instance.blocks_and_transactions))
+ if (not self.check_results(tip, block_outcome)):
+ raise AssertionError("Block test failed at test %d" % test_number)
+ if (not test_instance.sync_every_tx and tx is not None):
+ if len(invqueue) > 0:
+ [ c.send_message(msg_inv(invqueue)) for c in self.connections ]
+ invqueue = []
+ self.sync_transaction(tx.sha256, len(test_instance.blocks_and_transactions))
+ if (not self.check_mempool(tx.sha256, tx_outcome)):
+ raise AssertionError("Mempool test failed at test %d" % test_number)
+
+ print("Test %d: PASS" % test_number, [ c.rpc.getblockcount() for c in self.connections ])
+ test_number += 1
+
+ [ c.disconnect_node() for c in self.connections ]
+ self.wait_for_disconnections()
+ self.block_store.close()
+ self.tx_store.close()
diff --git a/qa/rpc-tests/test_framework/coverage.py b/qa/rpc-tests/test_framework/coverage.py
new file mode 100644
index 0000000000..23fce61014
--- /dev/null
+++ b/qa/rpc-tests/test_framework/coverage.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+"""
+This module contains utilities for doing coverage analysis on the RPC
+interface.
+
+It provides a way to track which RPC commands are exercised during
+testing.
+
+"""
+import os
+
+
+REFERENCE_FILENAME = 'rpc_interface.txt'
+
+
+class AuthServiceProxyWrapper(object):
+ """
+ An object that wraps AuthServiceProxy to record specific RPC calls.
+
+ """
+ def __init__(self, auth_service_proxy_instance, coverage_logfile=None):
+ """
+ Kwargs:
+ auth_service_proxy_instance (AuthServiceProxy): the instance
+ being wrapped.
+ coverage_logfile (str): if specified, write each service_name
+ out to a file when called.
+
+ """
+ self.auth_service_proxy_instance = auth_service_proxy_instance
+ self.coverage_logfile = coverage_logfile
+
+ def __getattr__(self, *args, **kwargs):
+ return_val = self.auth_service_proxy_instance.__getattr__(
+ *args, **kwargs)
+
+ return AuthServiceProxyWrapper(return_val, self.coverage_logfile)
+
+ def __call__(self, *args, **kwargs):
+ """
+ Delegates to AuthServiceProxy, then writes the particular RPC method
+ called to a file.
+
+ """
+ return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
+ rpc_method = self.auth_service_proxy_instance._service_name
+
+ if self.coverage_logfile:
+ with open(self.coverage_logfile, 'a+') as f:
+ f.write("%s\n" % rpc_method)
+
+ return return_val
+
+ @property
+ def url(self):
+ return self.auth_service_proxy_instance.url
+
+
+def get_filename(dirname, n_node):
+ """
+ Get a filename unique to the test process ID and node.
+
+ This file will contain a list of RPC commands covered.
+ """
+ pid = str(os.getpid())
+ return os.path.join(
+ dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node)))
+
+
+def write_all_rpc_commands(dirname, node):
+ """
+ Write out a list of all RPC functions available in `bitcoin-cli` for
+ coverage comparison. This will only happen once per coverage
+ directory.
+
+ Args:
+ dirname (str): temporary test dir
+ node (AuthServiceProxy): client
+
+ Returns:
+ bool. if the RPC interface file was written.
+
+ """
+ filename = os.path.join(dirname, REFERENCE_FILENAME)
+
+ if os.path.isfile(filename):
+ return False
+
+ help_output = node.help().split('\n')
+ commands = set()
+
+ for line in help_output:
+ line = line.strip()
+
+ # Ignore blanks and headers
+ if line and not line.startswith('='):
+ commands.add("%s\n" % line.split()[0])
+
+ with open(filename, 'w') as f:
+ f.writelines(list(commands))
+
+ return True
diff --git a/qa/rpc-tests/test_framework/key.py b/qa/rpc-tests/test_framework/key.py
new file mode 100644
index 0000000000..ba3038fe04
--- /dev/null
+++ b/qa/rpc-tests/test_framework/key.py
@@ -0,0 +1,215 @@
+# Copyright (c) 2011 Sam Rushing
+#
+# key.py - OpenSSL wrapper
+#
+# This file is modified from python-bitcoinlib.
+#
+
+"""ECC secp256k1 crypto routines
+
+WARNING: This module does not mlock() secrets; your private keys may end up on
+disk in swap! Use with caution!
+"""
+
+import ctypes
+import ctypes.util
+import hashlib
+import sys
+
+ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library ('ssl') or 'libeay32')
+
+ssl.BN_new.restype = ctypes.c_void_p
+ssl.BN_new.argtypes = []
+
+ssl.BN_bin2bn.restype = ctypes.c_void_p
+ssl.BN_bin2bn.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p]
+
+ssl.BN_CTX_free.restype = None
+ssl.BN_CTX_free.argtypes = [ctypes.c_void_p]
+
+ssl.BN_CTX_new.restype = ctypes.c_void_p
+ssl.BN_CTX_new.argtypes = []
+
+ssl.ECDH_compute_key.restype = ctypes.c_int
+ssl.ECDH_compute_key.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.ECDSA_sign.restype = ctypes.c_int
+ssl.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.ECDSA_verify.restype = ctypes.c_int
+ssl.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
+
+ssl.EC_KEY_free.restype = None
+ssl.EC_KEY_free.argtypes = [ctypes.c_void_p]
+
+ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
+ssl.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int]
+
+ssl.EC_KEY_get0_group.restype = ctypes.c_void_p
+ssl.EC_KEY_get0_group.argtypes = [ctypes.c_void_p]
+
+ssl.EC_KEY_get0_public_key.restype = ctypes.c_void_p
+ssl.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p]
+
+ssl.EC_KEY_set_private_key.restype = ctypes.c_int
+ssl.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.EC_KEY_set_conv_form.restype = None
+ssl.EC_KEY_set_conv_form.argtypes = [ctypes.c_void_p, ctypes.c_int]
+
+ssl.EC_KEY_set_public_key.restype = ctypes.c_int
+ssl.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.i2o_ECPublicKey.restype = ctypes.c_void_p
+ssl.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ssl.EC_POINT_new.restype = ctypes.c_void_p
+ssl.EC_POINT_new.argtypes = [ctypes.c_void_p]
+
+ssl.EC_POINT_free.restype = None
+ssl.EC_POINT_free.argtypes = [ctypes.c_void_p]
+
+ssl.EC_POINT_mul.restype = ctypes.c_int
+ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+# this specifies the curve used with ECDSA.
+NID_secp256k1 = 714 # from openssl/obj_mac.h
+
+# Thx to Sam Devlin for the ctypes magic 64-bit fix.
+def _check_result(val, func, args):
+ if val == 0:
+ raise ValueError
+ else:
+ return ctypes.c_void_p (val)
+
+ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
+ssl.EC_KEY_new_by_curve_name.errcheck = _check_result
+
+class CECKey(object):
+ """Wrapper around OpenSSL's EC_KEY"""
+
+ POINT_CONVERSION_COMPRESSED = 2
+ POINT_CONVERSION_UNCOMPRESSED = 4
+
+ def __init__(self):
+ self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1)
+
+ def __del__(self):
+ if ssl:
+ ssl.EC_KEY_free(self.k)
+ self.k = None
+
+ def set_secretbytes(self, secret):
+ priv_key = ssl.BN_bin2bn(secret, 32, ssl.BN_new())
+ group = ssl.EC_KEY_get0_group(self.k)
+ pub_key = ssl.EC_POINT_new(group)
+ ctx = ssl.BN_CTX_new()
+ if not ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx):
+ raise ValueError("Could not derive public key from the supplied secret.")
+ ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx)
+ ssl.EC_KEY_set_private_key(self.k, priv_key)
+ ssl.EC_KEY_set_public_key(self.k, pub_key)
+ ssl.EC_POINT_free(pub_key)
+ ssl.BN_CTX_free(ctx)
+ return self.k
+
+ def set_privkey(self, key):
+ self.mb = ctypes.create_string_buffer(key)
+ return ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
+
+ def set_pubkey(self, key):
+ self.mb = ctypes.create_string_buffer(key)
+ return ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key))
+
+ def get_privkey(self):
+ size = ssl.i2d_ECPrivateKey(self.k, 0)
+ mb_pri = ctypes.create_string_buffer(size)
+ ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri)))
+ return mb_pri.raw
+
+ def get_pubkey(self):
+ size = ssl.i2o_ECPublicKey(self.k, 0)
+ mb = ctypes.create_string_buffer(size)
+ ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb)))
+ return mb.raw
+
+ def get_raw_ecdh_key(self, other_pubkey):
+ ecdh_keybuffer = ctypes.create_string_buffer(32)
+ r = ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32,
+ ssl.EC_KEY_get0_public_key(other_pubkey.k),
+ self.k, 0)
+ if r != 32:
+ raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed')
+ return ecdh_keybuffer.raw
+
+ def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()):
+ # FIXME: be warned it's not clear what the kdf should be as a default
+ r = self.get_raw_ecdh_key(other_pubkey)
+ return kdf(r)
+
+ def sign(self, hash):
+ # FIXME: need unit tests for below cases
+ if not isinstance(hash, bytes):
+ raise TypeError('Hash must be bytes instance; got %r' % hash.__class__)
+ if len(hash) != 32:
+ raise ValueError('Hash must be exactly 32 bytes long')
+
+ sig_size0 = ctypes.c_uint32()
+ sig_size0.value = ssl.ECDSA_size(self.k)
+ mb_sig = ctypes.create_string_buffer(sig_size0.value)
+ result = ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k)
+ assert 1 == result
+ return mb_sig.raw[:sig_size0.value]
+
+ def verify(self, hash, sig):
+ """Verify a DER signature"""
+ return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1
+
+ def set_compressed(self, compressed):
+ if compressed:
+ form = self.POINT_CONVERSION_COMPRESSED
+ else:
+ form = self.POINT_CONVERSION_UNCOMPRESSED
+ ssl.EC_KEY_set_conv_form(self.k, form)
+
+
+class CPubKey(bytes):
+ """An encapsulated public key
+
+ Attributes:
+
+ is_valid - Corresponds to CPubKey.IsValid()
+ is_fullyvalid - Corresponds to CPubKey.IsFullyValid()
+ is_compressed - Corresponds to CPubKey.IsCompressed()
+ """
+
+ def __new__(cls, buf, _cec_key=None):
+ self = super(CPubKey, cls).__new__(cls, buf)
+ if _cec_key is None:
+ _cec_key = CECKey()
+ self._cec_key = _cec_key
+ self.is_fullyvalid = _cec_key.set_pubkey(self) != 0
+ return self
+
+ @property
+ def is_valid(self):
+ return len(self) > 0
+
+ @property
+ def is_compressed(self):
+ return len(self) == 33
+
+ def verify(self, hash, sig):
+ return self._cec_key.verify(hash, sig)
+
+ def __str__(self):
+ return repr(self)
+
+ def __repr__(self):
+ # Always have represent as b'<secret>' so test cases don't have to
+ # change for py2/3
+ if sys.version > '3':
+ return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__())
+ else:
+ return '%s(b%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__())
+
diff --git a/qa/rpc-tests/test_framework/mininode.py b/qa/rpc-tests/test_framework/mininode.py
new file mode 100755
index 0000000000..cdd5292cd6
--- /dev/null
+++ b/qa/rpc-tests/test_framework/mininode.py
@@ -0,0 +1,1531 @@
+#!/usr/bin/env python3
+# Copyright (c) 2010 ArtForz -- public domain half-a-node
+# Copyright (c) 2012 Jeff Garzik
+# Copyright (c) 2010-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# mininode.py - Bitcoin P2P network half-a-node
+#
+# This python code was modified from ArtForz' public domain half-a-node, as
+# found in the mini-node branch of http://github.com/jgarzik/pynode.
+#
+# NodeConn: an object which manages p2p connectivity to a bitcoin node
+# NodeConnCB: a base class that describes the interface for receiving
+# callbacks with network messages from a NodeConn
+# CBlock, CTransaction, CBlockHeader, CTxIn, CTxOut, etc....:
+# data structures that should map to corresponding structures in
+# bitcoin/primitives
+# msg_block, msg_tx, msg_headers, etc.:
+# data structures that represent network messages
+# ser_*, deser_*: functions that handle serialization/deserialization
+
+
+import struct
+import socket
+import asyncore
+import time
+import sys
+import random
+from .util import hex_str_to_bytes, bytes_to_hex_str
+from io import BytesIO
+from codecs import encode
+import hashlib
+from threading import RLock
+from threading import Thread
+import logging
+import copy
+
+BIP0031_VERSION = 60000
+MY_VERSION = 60001 # past bip-31 for ping/pong
+MY_SUBVERSION = b"/python-mininode-tester:0.0.3/"
+
+MAX_INV_SZ = 50000
+MAX_BLOCK_SIZE = 1000000
+
+COIN = 100000000 # 1 btc in satoshis
+
+NODE_NETWORK = (1 << 0)
+NODE_GETUTXO = (1 << 1)
+NODE_BLOOM = (1 << 2)
+NODE_WITNESS = (1 << 3)
+
+# Keep our own socket map for asyncore, so that we can track disconnects
+# ourselves (to workaround an issue with closing an asyncore socket when
+# using select)
+mininode_socket_map = dict()
+
+# One lock for synchronizing all data access between the networking thread (see
+# NetworkThread below) and the thread running the test logic. For simplicity,
+# NodeConn acquires this lock whenever delivering a message to to a NodeConnCB,
+# and whenever adding anything to the send buffer (in send_message()). This
+# lock should be acquired in the thread running the test logic to synchronize
+# access to any data shared with the NodeConnCB or NodeConn.
+mininode_lock = RLock()
+
+# Serialization/deserialization tools
+def sha256(s):
+ return hashlib.new('sha256', s).digest()
+
+def ripemd160(s):
+ return hashlib.new('ripemd160', s).digest()
+
+def hash256(s):
+ return sha256(sha256(s))
+
+
+def deser_string(f):
+ nit = struct.unpack("<B", f.read(1))[0]
+ if nit == 253:
+ nit = struct.unpack("<H", f.read(2))[0]
+ elif nit == 254:
+ nit = struct.unpack("<I", f.read(4))[0]
+ elif nit == 255:
+ nit = struct.unpack("<Q", f.read(8))[0]
+ return f.read(nit)
+
+def ser_string(s):
+ if len(s) < 253:
+ return struct.pack("B", len(s)) + s
+ elif len(s) < 0x10000:
+ return struct.pack("<BH", 253, len(s)) + s
+ elif len(s) < 0x100000000:
+ return struct.pack("<BI", 254, len(s)) + s
+ return struct.pack("<BQ", 255, len(s)) + s
+
+def deser_uint256(f):
+ r = 0
+ for i in range(8):
+ t = struct.unpack("<I", f.read(4))[0]
+ r += t << (i * 32)
+ return r
+
+
+def ser_uint256(u):
+ rs = b""
+ for i in range(8):
+ rs += struct.pack("<I", u & 0xFFFFFFFF)
+ u >>= 32
+ return rs
+
+
+def uint256_from_str(s):
+ r = 0
+ t = struct.unpack("<IIIIIIII", s[:32])
+ for i in range(8):
+ r += t[i] << (i * 32)
+ return r
+
+
+def uint256_from_compact(c):
+ nbytes = (c >> 24) & 0xFF
+ v = (c & 0xFFFFFF) << (8 * (nbytes - 3))
+ return v
+
+
+def deser_vector(f, c):
+ nit = struct.unpack("<B", f.read(1))[0]
+ if nit == 253:
+ nit = struct.unpack("<H", f.read(2))[0]
+ elif nit == 254:
+ nit = struct.unpack("<I", f.read(4))[0]
+ elif nit == 255:
+ nit = struct.unpack("<Q", f.read(8))[0]
+ r = []
+ for i in range(nit):
+ t = c()
+ t.deserialize(f)
+ r.append(t)
+ return r
+
+
+# ser_function_name: Allow for an alternate serialization function on the
+# entries in the vector (we use this for serializing the vector of transactions
+# for a witness block).
+def ser_vector(l, ser_function_name=None):
+ r = b""
+ if len(l) < 253:
+ r = struct.pack("B", len(l))
+ elif len(l) < 0x10000:
+ r = struct.pack("<BH", 253, len(l))
+ elif len(l) < 0x100000000:
+ r = struct.pack("<BI", 254, len(l))
+ else:
+ r = struct.pack("<BQ", 255, len(l))
+ for i in l:
+ if ser_function_name:
+ r += getattr(i, ser_function_name)()
+ else:
+ r += i.serialize()
+ return r
+
+
+def deser_uint256_vector(f):
+ nit = struct.unpack("<B", f.read(1))[0]
+ if nit == 253:
+ nit = struct.unpack("<H", f.read(2))[0]
+ elif nit == 254:
+ nit = struct.unpack("<I", f.read(4))[0]
+ elif nit == 255:
+ nit = struct.unpack("<Q", f.read(8))[0]
+ r = []
+ for i in range(nit):
+ t = deser_uint256(f)
+ r.append(t)
+ return r
+
+
+def ser_uint256_vector(l):
+ r = b""
+ if len(l) < 253:
+ r = struct.pack("B", len(l))
+ elif len(l) < 0x10000:
+ r = struct.pack("<BH", 253, len(l))
+ elif len(l) < 0x100000000:
+ r = struct.pack("<BI", 254, len(l))
+ else:
+ r = struct.pack("<BQ", 255, len(l))
+ for i in l:
+ r += ser_uint256(i)
+ return r
+
+
+def deser_string_vector(f):
+ nit = struct.unpack("<B", f.read(1))[0]
+ if nit == 253:
+ nit = struct.unpack("<H", f.read(2))[0]
+ elif nit == 254:
+ nit = struct.unpack("<I", f.read(4))[0]
+ elif nit == 255:
+ nit = struct.unpack("<Q", f.read(8))[0]
+ r = []
+ for i in range(nit):
+ t = deser_string(f)
+ r.append(t)
+ return r
+
+
+def ser_string_vector(l):
+ r = b""
+ if len(l) < 253:
+ r = struct.pack("B", len(l))
+ elif len(l) < 0x10000:
+ r = struct.pack("<BH", 253, len(l))
+ elif len(l) < 0x100000000:
+ r = struct.pack("<BI", 254, len(l))
+ else:
+ r = struct.pack("<BQ", 255, len(l))
+ for sv in l:
+ r += ser_string(sv)
+ return r
+
+
+def deser_int_vector(f):
+ nit = struct.unpack("<B", f.read(1))[0]
+ if nit == 253:
+ nit = struct.unpack("<H", f.read(2))[0]
+ elif nit == 254:
+ nit = struct.unpack("<I", f.read(4))[0]
+ elif nit == 255:
+ nit = struct.unpack("<Q", f.read(8))[0]
+ r = []
+ for i in range(nit):
+ t = struct.unpack("<i", f.read(4))[0]
+ r.append(t)
+ return r
+
+
+def ser_int_vector(l):
+ r = b""
+ if len(l) < 253:
+ r = struct.pack("B", len(l))
+ elif len(l) < 0x10000:
+ r = struct.pack("<BH", 253, len(l))
+ elif len(l) < 0x100000000:
+ r = struct.pack("<BI", 254, len(l))
+ else:
+ r = struct.pack("<BQ", 255, len(l))
+ for i in l:
+ r += struct.pack("<i", i)
+ return r
+
+# Deserialize from a hex string representation (eg from RPC)
+def FromHex(obj, hex_string):
+ obj.deserialize(BytesIO(hex_str_to_bytes(hex_string)))
+ return obj
+
+# Convert a binary-serializable object to hex (eg for submission via RPC)
+def ToHex(obj):
+ return bytes_to_hex_str(obj.serialize())
+
+# Objects that map to bitcoind objects, which can be serialized/deserialized
+
+class CAddress(object):
+ def __init__(self):
+ self.nServices = 1
+ self.pchReserved = b"\x00" * 10 + b"\xff" * 2
+ self.ip = "0.0.0.0"
+ self.port = 0
+
+ def deserialize(self, f):
+ self.nServices = struct.unpack("<Q", f.read(8))[0]
+ self.pchReserved = f.read(12)
+ self.ip = socket.inet_ntoa(f.read(4))
+ self.port = struct.unpack(">H", f.read(2))[0]
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<Q", self.nServices)
+ r += self.pchReserved
+ r += socket.inet_aton(self.ip)
+ r += struct.pack(">H", self.port)
+ return r
+
+ def __repr__(self):
+ return "CAddress(nServices=%i ip=%s port=%i)" % (self.nServices,
+ self.ip, self.port)
+
+MSG_WITNESS_FLAG = 1<<30
+
+class CInv(object):
+ typemap = {
+ 0: "Error",
+ 1: "TX",
+ 2: "Block",
+ 1|MSG_WITNESS_FLAG: "WitnessTx",
+ 2|MSG_WITNESS_FLAG : "WitnessBlock"
+ }
+
+ def __init__(self, t=0, h=0):
+ self.type = t
+ self.hash = h
+
+ def deserialize(self, f):
+ self.type = struct.unpack("<i", f.read(4))[0]
+ self.hash = deser_uint256(f)
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<i", self.type)
+ r += ser_uint256(self.hash)
+ return r
+
+ def __repr__(self):
+ return "CInv(type=%s hash=%064x)" \
+ % (self.typemap[self.type], self.hash)
+
+
+class CBlockLocator(object):
+ def __init__(self):
+ self.nVersion = MY_VERSION
+ self.vHave = []
+
+ def deserialize(self, f):
+ self.nVersion = struct.unpack("<i", f.read(4))[0]
+ self.vHave = deser_uint256_vector(f)
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<i", self.nVersion)
+ r += ser_uint256_vector(self.vHave)
+ return r
+
+ def __repr__(self):
+ return "CBlockLocator(nVersion=%i vHave=%s)" \
+ % (self.nVersion, repr(self.vHave))
+
+
+class COutPoint(object):
+ def __init__(self, hash=0, n=0):
+ self.hash = hash
+ self.n = n
+
+ def deserialize(self, f):
+ self.hash = deser_uint256(f)
+ self.n = struct.unpack("<I", f.read(4))[0]
+
+ def serialize(self):
+ r = b""
+ r += ser_uint256(self.hash)
+ r += struct.pack("<I", self.n)
+ return r
+
+ def __repr__(self):
+ return "COutPoint(hash=%064x n=%i)" % (self.hash, self.n)
+
+
+class CTxIn(object):
+ def __init__(self, outpoint=None, scriptSig=b"", nSequence=0):
+ if outpoint is None:
+ self.prevout = COutPoint()
+ else:
+ self.prevout = outpoint
+ self.scriptSig = scriptSig
+ self.nSequence = nSequence
+
+ def deserialize(self, f):
+ self.prevout = COutPoint()
+ self.prevout.deserialize(f)
+ self.scriptSig = deser_string(f)
+ self.nSequence = struct.unpack("<I", f.read(4))[0]
+
+ def serialize(self):
+ r = b""
+ r += self.prevout.serialize()
+ r += ser_string(self.scriptSig)
+ r += struct.pack("<I", self.nSequence)
+ return r
+
+ def __repr__(self):
+ return "CTxIn(prevout=%s scriptSig=%s nSequence=%i)" \
+ % (repr(self.prevout), bytes_to_hex_str(self.scriptSig),
+ self.nSequence)
+
+
+class CTxOut(object):
+ def __init__(self, nValue=0, scriptPubKey=b""):
+ self.nValue = nValue
+ self.scriptPubKey = scriptPubKey
+
+ def deserialize(self, f):
+ self.nValue = struct.unpack("<q", f.read(8))[0]
+ self.scriptPubKey = deser_string(f)
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<q", self.nValue)
+ r += ser_string(self.scriptPubKey)
+ return r
+
+ def __repr__(self):
+ return "CTxOut(nValue=%i.%08i scriptPubKey=%s)" \
+ % (self.nValue // COIN, self.nValue % COIN,
+ bytes_to_hex_str(self.scriptPubKey))
+
+
+class CScriptWitness(object):
+ def __init__(self):
+ # stack is a vector of strings
+ self.stack = []
+
+ def __repr__(self):
+ return "CScriptWitness(%s)" % \
+ (",".join([bytes_to_hex_str(x) for x in self.stack]))
+
+ def is_null(self):
+ if self.stack:
+ return False
+ return True
+
+
+class CTxInWitness(object):
+ def __init__(self):
+ self.scriptWitness = CScriptWitness()
+
+ def deserialize(self, f):
+ self.scriptWitness.stack = deser_string_vector(f)
+
+ def serialize(self):
+ return ser_string_vector(self.scriptWitness.stack)
+
+ def __repr__(self):
+ return repr(self.scriptWitness)
+
+ def is_null(self):
+ return self.scriptWitness.is_null()
+
+
+class CTxWitness(object):
+ def __init__(self):
+ self.vtxinwit = []
+
+ def deserialize(self, f):
+ for i in range(len(self.vtxinwit)):
+ self.vtxinwit[i].deserialize(f)
+
+ def serialize(self):
+ r = b""
+ # This is different than the usual vector serialization --
+ # we omit the length of the vector, which is required to be
+ # the same length as the transaction's vin vector.
+ for x in self.vtxinwit:
+ r += x.serialize()
+ return r
+
+ def __repr__(self):
+ return "CTxWitness(%s)" % \
+ (';'.join([repr(x) for x in self.vtxinwit]))
+
+ def is_null(self):
+ for x in self.vtxinwit:
+ if not x.is_null():
+ return False
+ return True
+
+
+class CTransaction(object):
+ def __init__(self, tx=None):
+ if tx is None:
+ self.nVersion = 1
+ self.vin = []
+ self.vout = []
+ self.wit = CTxWitness()
+ self.nLockTime = 0
+ self.sha256 = None
+ self.hash = None
+ else:
+ self.nVersion = tx.nVersion
+ self.vin = copy.deepcopy(tx.vin)
+ self.vout = copy.deepcopy(tx.vout)
+ self.nLockTime = tx.nLockTime
+ self.sha256 = tx.sha256
+ self.hash = tx.hash
+ self.wit = copy.deepcopy(tx.wit)
+
+ def deserialize(self, f):
+ self.nVersion = struct.unpack("<i", f.read(4))[0]
+ self.vin = deser_vector(f, CTxIn)
+ flags = 0
+ if len(self.vin) == 0:
+ flags = struct.unpack("<B", f.read(1))[0]
+ # Not sure why flags can't be zero, but this
+ # matches the implementation in bitcoind
+ if (flags != 0):
+ self.vin = deser_vector(f, CTxIn)
+ self.vout = deser_vector(f, CTxOut)
+ else:
+ self.vout = deser_vector(f, CTxOut)
+ if flags != 0:
+ self.wit.vtxinwit = [CTxInWitness()]*len(self.vin)
+ self.wit.deserialize(f)
+ self.nLockTime = struct.unpack("<I", f.read(4))[0]
+ self.sha256 = None
+ self.hash = None
+
+ def serialize_without_witness(self):
+ r = b""
+ r += struct.pack("<i", self.nVersion)
+ r += ser_vector(self.vin)
+ r += ser_vector(self.vout)
+ r += struct.pack("<I", self.nLockTime)
+ return r
+
+ # Only serialize with witness when explicitly called for
+ def serialize_with_witness(self):
+ flags = 0
+ if not self.wit.is_null():
+ flags |= 1
+ r = b""
+ r += struct.pack("<i", self.nVersion)
+ if flags:
+ dummy = []
+ r += ser_vector(dummy)
+ r += struct.pack("<B", flags)
+ r += ser_vector(self.vin)
+ r += ser_vector(self.vout)
+ if flags & 1:
+ if (len(self.wit.vtxinwit) != len(self.vin)):
+ # vtxinwit must have the same length as vin
+ self.wit.vtxinwit = self.wit.vtxinwit[:len(self.vin)]
+ for i in range(len(self.wit.vtxinwit), len(self.vin)):
+ self.wit.vtxinwit.append(CTxInWitness())
+ r += self.wit.serialize()
+ r += struct.pack("<I", self.nLockTime)
+ return r
+
+ # Regular serialization is without witness -- must explicitly
+ # call serialize_with_witness to include witness data.
+ def serialize(self):
+ return self.serialize_without_witness()
+
+ # Recalculate the txid (transaction hash without witness)
+ def rehash(self):
+ self.sha256 = None
+ self.calc_sha256()
+
+ # We will only cache the serialization without witness in
+ # self.sha256 and self.hash -- those are expected to be the txid.
+ def calc_sha256(self, with_witness=False):
+ if with_witness:
+ # Don't cache the result, just return it
+ return uint256_from_str(hash256(self.serialize_with_witness()))
+
+ if self.sha256 is None:
+ self.sha256 = uint256_from_str(hash256(self.serialize_without_witness()))
+ self.hash = encode(hash256(self.serialize())[::-1], 'hex_codec').decode('ascii')
+
+ def is_valid(self):
+ self.calc_sha256()
+ for tout in self.vout:
+ if tout.nValue < 0 or tout.nValue > 21000000 * COIN:
+ return False
+ return True
+
+ def __repr__(self):
+ return "CTransaction(nVersion=%i vin=%s vout=%s nLockTime=%i)" \
+ % (self.nVersion, repr(self.vin), repr(self.vout), self.nLockTime)
+
+
+class CBlockHeader(object):
+ def __init__(self, header=None):
+ if header is None:
+ self.set_null()
+ else:
+ self.nVersion = header.nVersion
+ self.hashPrevBlock = header.hashPrevBlock
+ self.hashMerkleRoot = header.hashMerkleRoot
+ self.nTime = header.nTime
+ self.nBits = header.nBits
+ self.nNonce = header.nNonce
+ self.sha256 = header.sha256
+ self.hash = header.hash
+ self.calc_sha256()
+
+ def set_null(self):
+ self.nVersion = 1
+ self.hashPrevBlock = 0
+ self.hashMerkleRoot = 0
+ self.nTime = 0
+ self.nBits = 0
+ self.nNonce = 0
+ self.sha256 = None
+ self.hash = None
+
+ def deserialize(self, f):
+ self.nVersion = struct.unpack("<i", f.read(4))[0]
+ self.hashPrevBlock = deser_uint256(f)
+ self.hashMerkleRoot = deser_uint256(f)
+ self.nTime = struct.unpack("<I", f.read(4))[0]
+ self.nBits = struct.unpack("<I", f.read(4))[0]
+ self.nNonce = struct.unpack("<I", f.read(4))[0]
+ self.sha256 = None
+ self.hash = None
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<i", self.nVersion)
+ r += ser_uint256(self.hashPrevBlock)
+ r += ser_uint256(self.hashMerkleRoot)
+ r += struct.pack("<I", self.nTime)
+ r += struct.pack("<I", self.nBits)
+ r += struct.pack("<I", self.nNonce)
+ return r
+
+ def calc_sha256(self):
+ if self.sha256 is None:
+ r = b""
+ r += struct.pack("<i", self.nVersion)
+ r += ser_uint256(self.hashPrevBlock)
+ r += ser_uint256(self.hashMerkleRoot)
+ r += struct.pack("<I", self.nTime)
+ r += struct.pack("<I", self.nBits)
+ r += struct.pack("<I", self.nNonce)
+ self.sha256 = uint256_from_str(hash256(r))
+ self.hash = encode(hash256(r)[::-1], 'hex_codec').decode('ascii')
+
+ def rehash(self):
+ self.sha256 = None
+ self.calc_sha256()
+ return self.sha256
+
+ def __repr__(self):
+ return "CBlockHeader(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s nBits=%08x nNonce=%08x)" \
+ % (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot,
+ time.ctime(self.nTime), self.nBits, self.nNonce)
+
+
+class CBlock(CBlockHeader):
+ def __init__(self, header=None):
+ super(CBlock, self).__init__(header)
+ self.vtx = []
+
+ def deserialize(self, f):
+ super(CBlock, self).deserialize(f)
+ self.vtx = deser_vector(f, CTransaction)
+
+ def serialize(self, with_witness=False):
+ r = b""
+ r += super(CBlock, self).serialize()
+ if with_witness:
+ r += ser_vector(self.vtx, "serialize_with_witness")
+ else:
+ r += ser_vector(self.vtx)
+ return r
+
+ # Calculate the merkle root given a vector of transaction hashes
+ def get_merkle_root(self, hashes):
+ while len(hashes) > 1:
+ newhashes = []
+ for i in range(0, len(hashes), 2):
+ i2 = min(i+1, len(hashes)-1)
+ newhashes.append(hash256(hashes[i] + hashes[i2]))
+ hashes = newhashes
+ return uint256_from_str(hashes[0])
+
+ def calc_merkle_root(self):
+ hashes = []
+ for tx in self.vtx:
+ tx.calc_sha256()
+ hashes.append(ser_uint256(tx.sha256))
+ return self.get_merkle_root(hashes)
+
+ def calc_witness_merkle_root(self):
+ # For witness root purposes, the hash of the
+ # coinbase, with witness, is defined to be 0...0
+ hashes = [ser_uint256(0)]
+
+ for tx in self.vtx[1:]:
+ # Calculate the hashes with witness data
+ hashes.append(ser_uint256(tx.calc_sha256(True)))
+
+ return self.get_merkle_root(hashes)
+
+ def is_valid(self):
+ self.calc_sha256()
+ target = uint256_from_compact(self.nBits)
+ if self.sha256 > target:
+ return False
+ for tx in self.vtx:
+ if not tx.is_valid():
+ return False
+ if self.calc_merkle_root() != self.hashMerkleRoot:
+ return False
+ return True
+
+ def solve(self):
+ self.rehash()
+ target = uint256_from_compact(self.nBits)
+ while self.sha256 > target:
+ self.nNonce += 1
+ self.rehash()
+
+ def __repr__(self):
+ return "CBlock(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s nBits=%08x nNonce=%08x vtx=%s)" \
+ % (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot,
+ time.ctime(self.nTime), self.nBits, self.nNonce, repr(self.vtx))
+
+
+class CUnsignedAlert(object):
+ def __init__(self):
+ self.nVersion = 1
+ self.nRelayUntil = 0
+ self.nExpiration = 0
+ self.nID = 0
+ self.nCancel = 0
+ self.setCancel = []
+ self.nMinVer = 0
+ self.nMaxVer = 0
+ self.setSubVer = []
+ self.nPriority = 0
+ self.strComment = b""
+ self.strStatusBar = b""
+ self.strReserved = b""
+
+ def deserialize(self, f):
+ self.nVersion = struct.unpack("<i", f.read(4))[0]
+ self.nRelayUntil = struct.unpack("<q", f.read(8))[0]
+ self.nExpiration = struct.unpack("<q", f.read(8))[0]
+ self.nID = struct.unpack("<i", f.read(4))[0]
+ self.nCancel = struct.unpack("<i", f.read(4))[0]
+ self.setCancel = deser_int_vector(f)
+ self.nMinVer = struct.unpack("<i", f.read(4))[0]
+ self.nMaxVer = struct.unpack("<i", f.read(4))[0]
+ self.setSubVer = deser_string_vector(f)
+ self.nPriority = struct.unpack("<i", f.read(4))[0]
+ self.strComment = deser_string(f)
+ self.strStatusBar = deser_string(f)
+ self.strReserved = deser_string(f)
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<i", self.nVersion)
+ r += struct.pack("<q", self.nRelayUntil)
+ r += struct.pack("<q", self.nExpiration)
+ r += struct.pack("<i", self.nID)
+ r += struct.pack("<i", self.nCancel)
+ r += ser_int_vector(self.setCancel)
+ r += struct.pack("<i", self.nMinVer)
+ r += struct.pack("<i", self.nMaxVer)
+ r += ser_string_vector(self.setSubVer)
+ r += struct.pack("<i", self.nPriority)
+ r += ser_string(self.strComment)
+ r += ser_string(self.strStatusBar)
+ r += ser_string(self.strReserved)
+ return r
+
+ def __repr__(self):
+ return "CUnsignedAlert(nVersion %d, nRelayUntil %d, nExpiration %d, nID %d, nCancel %d, nMinVer %d, nMaxVer %d, nPriority %d, strComment %s, strStatusBar %s, strReserved %s)" \
+ % (self.nVersion, self.nRelayUntil, self.nExpiration, self.nID,
+ self.nCancel, self.nMinVer, self.nMaxVer, self.nPriority,
+ self.strComment, self.strStatusBar, self.strReserved)
+
+
+class CAlert(object):
+ def __init__(self):
+ self.vchMsg = b""
+ self.vchSig = b""
+
+ def deserialize(self, f):
+ self.vchMsg = deser_string(f)
+ self.vchSig = deser_string(f)
+
+ def serialize(self):
+ r = b""
+ r += ser_string(self.vchMsg)
+ r += ser_string(self.vchSig)
+ return r
+
+ def __repr__(self):
+ return "CAlert(vchMsg.sz %d, vchSig.sz %d)" \
+ % (len(self.vchMsg), len(self.vchSig))
+
+
+# Objects that correspond to messages on the wire
+class msg_version(object):
+ command = b"version"
+
+ def __init__(self):
+ self.nVersion = MY_VERSION
+ self.nServices = 1
+ self.nTime = int(time.time())
+ self.addrTo = CAddress()
+ self.addrFrom = CAddress()
+ self.nNonce = random.getrandbits(64)
+ self.strSubVer = MY_SUBVERSION
+ self.nStartingHeight = -1
+
+ def deserialize(self, f):
+ self.nVersion = struct.unpack("<i", f.read(4))[0]
+ if self.nVersion == 10300:
+ self.nVersion = 300
+ self.nServices = struct.unpack("<Q", f.read(8))[0]
+ self.nTime = struct.unpack("<q", f.read(8))[0]
+ self.addrTo = CAddress()
+ self.addrTo.deserialize(f)
+ if self.nVersion >= 106:
+ self.addrFrom = CAddress()
+ self.addrFrom.deserialize(f)
+ self.nNonce = struct.unpack("<Q", f.read(8))[0]
+ self.strSubVer = deser_string(f)
+ if self.nVersion >= 209:
+ self.nStartingHeight = struct.unpack("<i", f.read(4))[0]
+ else:
+ self.nStartingHeight = None
+ else:
+ self.addrFrom = None
+ self.nNonce = None
+ self.strSubVer = None
+ self.nStartingHeight = None
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<i", self.nVersion)
+ r += struct.pack("<Q", self.nServices)
+ r += struct.pack("<q", self.nTime)
+ r += self.addrTo.serialize()
+ r += self.addrFrom.serialize()
+ r += struct.pack("<Q", self.nNonce)
+ r += ser_string(self.strSubVer)
+ r += struct.pack("<i", self.nStartingHeight)
+ return r
+
+ def __repr__(self):
+ return 'msg_version(nVersion=%i nServices=%i nTime=%s addrTo=%s addrFrom=%s nNonce=0x%016X strSubVer=%s nStartingHeight=%i)' \
+ % (self.nVersion, self.nServices, time.ctime(self.nTime),
+ repr(self.addrTo), repr(self.addrFrom), self.nNonce,
+ self.strSubVer, self.nStartingHeight)
+
+
+class msg_verack(object):
+ command = b"verack"
+
+ def __init__(self):
+ pass
+
+ def deserialize(self, f):
+ pass
+
+ def serialize(self):
+ return b""
+
+ def __repr__(self):
+ return "msg_verack()"
+
+
+class msg_addr(object):
+ command = b"addr"
+
+ def __init__(self):
+ self.addrs = []
+
+ def deserialize(self, f):
+ self.addrs = deser_vector(f, CAddress)
+
+ def serialize(self):
+ return ser_vector(self.addrs)
+
+ def __repr__(self):
+ return "msg_addr(addrs=%s)" % (repr(self.addrs))
+
+
+class msg_alert(object):
+ command = b"alert"
+
+ def __init__(self):
+ self.alert = CAlert()
+
+ def deserialize(self, f):
+ self.alert = CAlert()
+ self.alert.deserialize(f)
+
+ def serialize(self):
+ r = b""
+ r += self.alert.serialize()
+ return r
+
+ def __repr__(self):
+ return "msg_alert(alert=%s)" % (repr(self.alert), )
+
+
+class msg_inv(object):
+ command = b"inv"
+
+ def __init__(self, inv=None):
+ if inv is None:
+ self.inv = []
+ else:
+ self.inv = inv
+
+ def deserialize(self, f):
+ self.inv = deser_vector(f, CInv)
+
+ def serialize(self):
+ return ser_vector(self.inv)
+
+ def __repr__(self):
+ return "msg_inv(inv=%s)" % (repr(self.inv))
+
+
+class msg_getdata(object):
+ command = b"getdata"
+
+ def __init__(self, inv=None):
+ self.inv = inv if inv != None else []
+
+ def deserialize(self, f):
+ self.inv = deser_vector(f, CInv)
+
+ def serialize(self):
+ return ser_vector(self.inv)
+
+ def __repr__(self):
+ return "msg_getdata(inv=%s)" % (repr(self.inv))
+
+
+class msg_getblocks(object):
+ command = b"getblocks"
+
+ def __init__(self):
+ self.locator = CBlockLocator()
+ self.hashstop = 0
+
+ def deserialize(self, f):
+ self.locator = CBlockLocator()
+ self.locator.deserialize(f)
+ self.hashstop = deser_uint256(f)
+
+ def serialize(self):
+ r = b""
+ r += self.locator.serialize()
+ r += ser_uint256(self.hashstop)
+ return r
+
+ def __repr__(self):
+ return "msg_getblocks(locator=%s hashstop=%064x)" \
+ % (repr(self.locator), self.hashstop)
+
+
+class msg_tx(object):
+ command = b"tx"
+
+ def __init__(self, tx=CTransaction()):
+ self.tx = tx
+
+ def deserialize(self, f):
+ self.tx.deserialize(f)
+
+ def serialize(self):
+ return self.tx.serialize_without_witness()
+
+ def __repr__(self):
+ return "msg_tx(tx=%s)" % (repr(self.tx))
+
+class msg_witness_tx(msg_tx):
+
+ def serialize(self):
+ return self.tx.serialize_with_witness()
+
+
+class msg_block(object):
+ command = b"block"
+
+ def __init__(self, block=None):
+ if block is None:
+ self.block = CBlock()
+ else:
+ self.block = block
+
+ def deserialize(self, f):
+ self.block.deserialize(f)
+
+ def serialize(self):
+ return self.block.serialize()
+
+ def __repr__(self):
+ return "msg_block(block=%s)" % (repr(self.block))
+
+# for cases where a user needs tighter control over what is sent over the wire
+# note that the user must supply the name of the command, and the data
+class msg_generic(object):
+ def __init__(self, command, data=None):
+ self.command = command
+ self.data = data
+
+ def serialize(self):
+ return self.data
+
+ def __repr__(self):
+ return "msg_generic()"
+
+class msg_witness_block(msg_block):
+
+ def serialize(self):
+ r = self.block.serialize(with_witness=True)
+ return r
+
+class msg_getaddr(object):
+ command = b"getaddr"
+
+ def __init__(self):
+ pass
+
+ def deserialize(self, f):
+ pass
+
+ def serialize(self):
+ return b""
+
+ def __repr__(self):
+ return "msg_getaddr()"
+
+
+class msg_ping_prebip31(object):
+ command = b"ping"
+
+ def __init__(self):
+ pass
+
+ def deserialize(self, f):
+ pass
+
+ def serialize(self):
+ return b""
+
+ def __repr__(self):
+ return "msg_ping() (pre-bip31)"
+
+
+class msg_ping(object):
+ command = b"ping"
+
+ def __init__(self, nonce=0):
+ self.nonce = nonce
+
+ def deserialize(self, f):
+ self.nonce = struct.unpack("<Q", f.read(8))[0]
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<Q", self.nonce)
+ return r
+
+ def __repr__(self):
+ return "msg_ping(nonce=%08x)" % self.nonce
+
+
+class msg_pong(object):
+ command = b"pong"
+
+ def __init__(self, nonce=0):
+ self.nonce = nonce
+
+ def deserialize(self, f):
+ self.nonce = struct.unpack("<Q", f.read(8))[0]
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<Q", self.nonce)
+ return r
+
+ def __repr__(self):
+ return "msg_pong(nonce=%08x)" % self.nonce
+
+
+class msg_mempool(object):
+ command = b"mempool"
+
+ def __init__(self):
+ pass
+
+ def deserialize(self, f):
+ pass
+
+ def serialize(self):
+ return b""
+
+ def __repr__(self):
+ return "msg_mempool()"
+
+class msg_sendheaders(object):
+ command = b"sendheaders"
+
+ def __init__(self):
+ pass
+
+ def deserialize(self, f):
+ pass
+
+ def serialize(self):
+ return b""
+
+ def __repr__(self):
+ return "msg_sendheaders()"
+
+
+# getheaders message has
+# number of entries
+# vector of hashes
+# hash_stop (hash of last desired block header, 0 to get as many as possible)
+class msg_getheaders(object):
+ command = b"getheaders"
+
+ def __init__(self):
+ self.locator = CBlockLocator()
+ self.hashstop = 0
+
+ def deserialize(self, f):
+ self.locator = CBlockLocator()
+ self.locator.deserialize(f)
+ self.hashstop = deser_uint256(f)
+
+ def serialize(self):
+ r = b""
+ r += self.locator.serialize()
+ r += ser_uint256(self.hashstop)
+ return r
+
+ def __repr__(self):
+ return "msg_getheaders(locator=%s, stop=%064x)" \
+ % (repr(self.locator), self.hashstop)
+
+
+# headers message has
+# <count> <vector of block headers>
+class msg_headers(object):
+ command = b"headers"
+
+ def __init__(self):
+ self.headers = []
+
+ def deserialize(self, f):
+ # comment in bitcoind indicates these should be deserialized as blocks
+ blocks = deser_vector(f, CBlock)
+ for x in blocks:
+ self.headers.append(CBlockHeader(x))
+
+ def serialize(self):
+ blocks = [CBlock(x) for x in self.headers]
+ return ser_vector(blocks)
+
+ def __repr__(self):
+ return "msg_headers(headers=%s)" % repr(self.headers)
+
+
+class msg_reject(object):
+ command = b"reject"
+ REJECT_MALFORMED = 1
+
+ def __init__(self):
+ self.message = b""
+ self.code = 0
+ self.reason = b""
+ self.data = 0
+
+ def deserialize(self, f):
+ self.message = deser_string(f)
+ self.code = struct.unpack("<B", f.read(1))[0]
+ self.reason = deser_string(f)
+ if (self.code != self.REJECT_MALFORMED and
+ (self.message == b"block" or self.message == b"tx")):
+ self.data = deser_uint256(f)
+
+ def serialize(self):
+ r = ser_string(self.message)
+ r += struct.pack("<B", self.code)
+ r += ser_string(self.reason)
+ if (self.code != self.REJECT_MALFORMED and
+ (self.message == b"block" or self.message == b"tx")):
+ r += ser_uint256(self.data)
+ return r
+
+ def __repr__(self):
+ return "msg_reject: %s %d %s [%064x]" \
+ % (self.message, self.code, self.reason, self.data)
+
+# Helper function
+def wait_until(predicate, attempts=float('inf'), timeout=float('inf')):
+ attempt = 0
+ elapsed = 0
+
+ while attempt < attempts and elapsed < timeout:
+ with mininode_lock:
+ if predicate():
+ return True
+ attempt += 1
+ elapsed += 0.05
+ time.sleep(0.05)
+
+ return False
+
+class msg_feefilter(object):
+ command = b"feefilter"
+
+ def __init__(self, feerate=0):
+ self.feerate = feerate
+
+ def deserialize(self, f):
+ self.feerate = struct.unpack("<Q", f.read(8))[0]
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<Q", self.feerate)
+ return r
+
+ def __repr__(self):
+ return "msg_feefilter(feerate=%08x)" % self.feerate
+
+# This is what a callback should look like for NodeConn
+# Reimplement the on_* functions to provide handling for events
+class NodeConnCB(object):
+ def __init__(self):
+ self.verack_received = False
+ # deliver_sleep_time is helpful for debugging race conditions in p2p
+ # tests; it causes message delivery to sleep for the specified time
+ # before acquiring the global lock and delivering the next message.
+ self.deliver_sleep_time = None
+ # Remember the services our peer has advertised
+ self.peer_services = None
+
+ def set_deliver_sleep_time(self, value):
+ with mininode_lock:
+ self.deliver_sleep_time = value
+
+ def get_deliver_sleep_time(self):
+ with mininode_lock:
+ return self.deliver_sleep_time
+
+ # Spin until verack message is received from the node.
+ # Tests may want to use this as a signal that the test can begin.
+ # This can be called from the testing thread, so it needs to acquire the
+ # global lock.
+ def wait_for_verack(self):
+ while True:
+ with mininode_lock:
+ if self.verack_received:
+ return
+ time.sleep(0.05)
+
+ def deliver(self, conn, message):
+ deliver_sleep = self.get_deliver_sleep_time()
+ if deliver_sleep is not None:
+ time.sleep(deliver_sleep)
+ with mininode_lock:
+ try:
+ getattr(self, 'on_' + message.command.decode('ascii'))(conn, message)
+ except:
+ print("ERROR delivering %s (%s)" % (repr(message),
+ sys.exc_info()[0]))
+
+ def on_version(self, conn, message):
+ if message.nVersion >= 209:
+ conn.send_message(msg_verack())
+ conn.ver_send = min(MY_VERSION, message.nVersion)
+ if message.nVersion < 209:
+ conn.ver_recv = conn.ver_send
+ conn.nServices = message.nServices
+
+ def on_verack(self, conn, message):
+ conn.ver_recv = conn.ver_send
+ self.verack_received = True
+
+ def on_inv(self, conn, message):
+ want = msg_getdata()
+ for i in message.inv:
+ if i.type != 0:
+ want.inv.append(i)
+ if len(want.inv):
+ conn.send_message(want)
+
+ def on_addr(self, conn, message): pass
+ def on_alert(self, conn, message): pass
+ def on_getdata(self, conn, message): pass
+ def on_getblocks(self, conn, message): pass
+ def on_tx(self, conn, message): pass
+ def on_block(self, conn, message): pass
+ def on_getaddr(self, conn, message): pass
+ def on_headers(self, conn, message): pass
+ def on_getheaders(self, conn, message): pass
+ def on_ping(self, conn, message):
+ if conn.ver_send > BIP0031_VERSION:
+ conn.send_message(msg_pong(message.nonce))
+ def on_reject(self, conn, message): pass
+ def on_close(self, conn): pass
+ def on_mempool(self, conn): pass
+ def on_pong(self, conn, message): pass
+ def on_feefilter(self, conn, message): pass
+ def on_sendheaders(self, conn, message): pass
+
+# More useful callbacks and functions for NodeConnCB's which have a single NodeConn
+class SingleNodeConnCB(NodeConnCB):
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.connection = None
+ self.ping_counter = 1
+ self.last_pong = msg_pong()
+
+ def add_connection(self, conn):
+ self.connection = conn
+
+ # Wrapper for the NodeConn's send_message function
+ def send_message(self, message):
+ self.connection.send_message(message)
+
+ def on_pong(self, conn, message):
+ self.last_pong = message
+
+ # Sync up with the node
+ def sync_with_ping(self, timeout=30):
+ def received_pong():
+ return (self.last_pong.nonce == self.ping_counter)
+ self.send_message(msg_ping(nonce=self.ping_counter))
+ success = wait_until(received_pong, timeout)
+ self.ping_counter += 1
+ return success
+
+# The actual NodeConn class
+# This class provides an interface for a p2p connection to a specified node
+class NodeConn(asyncore.dispatcher):
+ messagemap = {
+ b"version": msg_version,
+ b"verack": msg_verack,
+ b"addr": msg_addr,
+ b"alert": msg_alert,
+ b"inv": msg_inv,
+ b"getdata": msg_getdata,
+ b"getblocks": msg_getblocks,
+ b"tx": msg_tx,
+ b"block": msg_block,
+ b"getaddr": msg_getaddr,
+ b"ping": msg_ping,
+ b"pong": msg_pong,
+ b"headers": msg_headers,
+ b"getheaders": msg_getheaders,
+ b"reject": msg_reject,
+ b"mempool": msg_mempool,
+ b"feefilter": msg_feefilter,
+ b"sendheaders": msg_sendheaders
+ }
+ MAGIC_BYTES = {
+ "mainnet": b"\xf9\xbe\xb4\xd9", # mainnet
+ "testnet3": b"\x0b\x11\x09\x07", # testnet3
+ "regtest": b"\xfa\xbf\xb5\xda", # regtest
+ }
+
+ def __init__(self, dstaddr, dstport, rpc, callback, net="regtest", services=NODE_NETWORK):
+ asyncore.dispatcher.__init__(self, map=mininode_socket_map)
+ self.log = logging.getLogger("NodeConn(%s:%d)" % (dstaddr, dstport))
+ self.dstaddr = dstaddr
+ self.dstport = dstport
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sendbuf = b""
+ self.recvbuf = b""
+ self.ver_send = 209
+ self.ver_recv = 209
+ self.last_sent = 0
+ self.state = "connecting"
+ self.network = net
+ self.cb = callback
+ self.disconnect = False
+ self.nServices = 0
+
+ # stuff version msg into sendbuf
+ vt = msg_version()
+ vt.nServices = services
+ vt.addrTo.ip = self.dstaddr
+ vt.addrTo.port = self.dstport
+ vt.addrFrom.ip = "0.0.0.0"
+ vt.addrFrom.port = 0
+ self.send_message(vt, True)
+ print('MiniNode: Connecting to Bitcoin Node IP # ' + dstaddr + ':' \
+ + str(dstport))
+
+ try:
+ self.connect((dstaddr, dstport))
+ except:
+ self.handle_close()
+ self.rpc = rpc
+
+ def show_debug_msg(self, msg):
+ self.log.debug(msg)
+
+ def handle_connect(self):
+ self.show_debug_msg("MiniNode: Connected & Listening: \n")
+ self.state = "connected"
+
+ def handle_close(self):
+ self.show_debug_msg("MiniNode: Closing Connection to %s:%d... "
+ % (self.dstaddr, self.dstport))
+ self.state = "closed"
+ self.recvbuf = b""
+ self.sendbuf = b""
+ try:
+ self.close()
+ except:
+ pass
+ self.cb.on_close(self)
+
+ def handle_read(self):
+ try:
+ t = self.recv(8192)
+ if len(t) > 0:
+ self.recvbuf += t
+ self.got_data()
+ except:
+ pass
+
+ def readable(self):
+ return True
+
+ def writable(self):
+ with mininode_lock:
+ length = len(self.sendbuf)
+ return (length > 0)
+
+ def handle_write(self):
+ with mininode_lock:
+ try:
+ sent = self.send(self.sendbuf)
+ except:
+ self.handle_close()
+ return
+ self.sendbuf = self.sendbuf[sent:]
+
+ def got_data(self):
+ try:
+ while True:
+ if len(self.recvbuf) < 4:
+ return
+ if self.recvbuf[:4] != self.MAGIC_BYTES[self.network]:
+ raise ValueError("got garbage %s" % repr(self.recvbuf))
+ if self.ver_recv < 209:
+ if len(self.recvbuf) < 4 + 12 + 4:
+ return
+ command = self.recvbuf[4:4+12].split(b"\x00", 1)[0]
+ msglen = struct.unpack("<i", self.recvbuf[4+12:4+12+4])[0]
+ checksum = None
+ if len(self.recvbuf) < 4 + 12 + 4 + msglen:
+ return
+ msg = self.recvbuf[4+12+4:4+12+4+msglen]
+ self.recvbuf = self.recvbuf[4+12+4+msglen:]
+ else:
+ if len(self.recvbuf) < 4 + 12 + 4 + 4:
+ return
+ command = self.recvbuf[4:4+12].split(b"\x00", 1)[0]
+ msglen = struct.unpack("<i", self.recvbuf[4+12:4+12+4])[0]
+ checksum = self.recvbuf[4+12+4:4+12+4+4]
+ if len(self.recvbuf) < 4 + 12 + 4 + 4 + msglen:
+ return
+ msg = self.recvbuf[4+12+4+4:4+12+4+4+msglen]
+ th = sha256(msg)
+ h = sha256(th)
+ if checksum != h[:4]:
+ raise ValueError("got bad checksum " + repr(self.recvbuf))
+ self.recvbuf = self.recvbuf[4+12+4+4+msglen:]
+ if command in self.messagemap:
+ f = BytesIO(msg)
+ t = self.messagemap[command]()
+ t.deserialize(f)
+ self.got_message(t)
+ else:
+ self.show_debug_msg("Unknown command: '" + command + "' " +
+ repr(msg))
+ except Exception as e:
+ print('got_data:', repr(e))
+ # import traceback
+ # traceback.print_tb(sys.exc_info()[2])
+
+ def send_message(self, message, pushbuf=False):
+ if self.state != "connected" and not pushbuf:
+ raise IOError('Not connected, no pushbuf')
+ self.show_debug_msg("Send %s" % repr(message))
+ command = message.command
+ data = message.serialize()
+ tmsg = self.MAGIC_BYTES[self.network]
+ tmsg += command
+ tmsg += b"\x00" * (12 - len(command))
+ tmsg += struct.pack("<I", len(data))
+ if self.ver_send >= 209:
+ th = sha256(data)
+ h = sha256(th)
+ tmsg += h[:4]
+ tmsg += data
+ with mininode_lock:
+ self.sendbuf += tmsg
+ self.last_sent = time.time()
+
+ def got_message(self, message):
+ if message.command == b"version":
+ if message.nVersion <= BIP0031_VERSION:
+ self.messagemap[b'ping'] = msg_ping_prebip31
+ if self.last_sent + 30 * 60 < time.time():
+ self.send_message(self.messagemap[b'ping']())
+ self.show_debug_msg("Recv %s" % repr(message))
+ self.cb.deliver(self, message)
+
+ def disconnect_node(self):
+ self.disconnect = True
+
+
+class NetworkThread(Thread):
+ def run(self):
+ while mininode_socket_map:
+ # We check for whether to disconnect outside of the asyncore
+ # loop to workaround the behavior of asyncore when using
+ # select
+ disconnected = []
+ for fd, obj in mininode_socket_map.items():
+ if obj.disconnect:
+ disconnected.append(obj)
+ [ obj.handle_close() for obj in disconnected ]
+ asyncore.loop(0.1, use_poll=True, map=mininode_socket_map, count=1)
+
+
+# An exception we can raise if we detect a potential disconnect
+# (p2p or rpc) before the test is complete
+class EarlyDisconnectError(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
diff --git a/qa/rpc-tests/netutil.py b/qa/rpc-tests/test_framework/netutil.py
index b30a88a4f7..573b06772d 100644
--- a/qa/rpc-tests/netutil.py
+++ b/qa/rpc-tests/test_framework/netutil.py
@@ -1,16 +1,17 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Linux network utilities
+
import sys
import socket
import fcntl
import struct
import array
import os
-import binascii
+from binascii import unhexlify, hexlify
# Roughly based on http://voorloopnul.com/blog/a-python-netstat-in-less-than-100-lines-of-code/ by Ricardo Pascal
STATE_ESTABLISHED = '01'
@@ -43,9 +44,9 @@ def _remove_empty(array):
def _convert_ip_port(array):
host,port = array.split(':')
# convert host from mangled-per-four-bytes form as used by kernel
- host = binascii.unhexlify(host)
+ host = unhexlify(host)
host_out = ''
- for x in range(0, len(host)/4):
+ for x in range(0, len(host) // 4):
(val,) = struct.unpack('=I', host[x*4:(x+1)*4])
host_out += '%08x' % val
@@ -94,7 +95,7 @@ def all_interfaces():
max_possible = 8 # initial value
while True:
bytes = max_possible * struct_size
- names = array.array('B', '\0' * bytes)
+ names = array.array('B', b'\0' * bytes)
outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
@@ -105,7 +106,7 @@ def all_interfaces():
else:
break
namestr = names.tostring()
- return [(namestr[i:i+16].split('\0', 1)[0],
+ return [(namestr[i:i+16].split(b'\0', 1)[0],
socket.inet_ntoa(namestr[i+20:i+24]))
for i in range(0, outbytes, struct_size)]
@@ -136,4 +137,19 @@ def addr_to_hex(addr):
addr = sub[0] + ([0] * nullbytes) + sub[1]
else:
raise ValueError('Could not parse address %s' % addr)
- return binascii.hexlify(bytearray(addr))
+ return hexlify(bytearray(addr)).decode('ascii')
+
+def test_ipv6_local():
+ '''
+ Check for (local) IPv6 support.
+ '''
+ import socket
+ # By using SOCK_DGRAM this will not actually make a connection, but it will
+ # fail if there is no route to IPv6 localhost.
+ have_ipv6 = True
+ try:
+ s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+ s.connect(('::1', 0))
+ except socket.error:
+ have_ipv6 = False
+ return have_ipv6
diff --git a/qa/rpc-tests/test_framework/script.py b/qa/rpc-tests/test_framework/script.py
new file mode 100644
index 0000000000..b46c643ccb
--- /dev/null
+++ b/qa/rpc-tests/test_framework/script.py
@@ -0,0 +1,947 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# script.py
+#
+# This file is modified from python-bitcoinlib.
+#
+
+"""Scripts
+
+Functionality to build scripts, as well as SignatureHash().
+"""
+
+
+from .mininode import CTransaction, CTxOut, sha256, hash256, uint256_from_str, ser_uint256, ser_string
+from binascii import hexlify
+import hashlib
+
+import sys
+bchr = chr
+bord = ord
+if sys.version > '3':
+ long = int
+ bchr = lambda x: bytes([x])
+ bord = lambda x: x
+
+import struct
+
+from .bignum import bn2vch
+
+MAX_SCRIPT_SIZE = 10000
+MAX_SCRIPT_ELEMENT_SIZE = 520
+MAX_SCRIPT_OPCODES = 201
+
+OPCODE_NAMES = {}
+
+def hash160(s):
+ return hashlib.new('ripemd160', sha256(s)).digest()
+
+
+_opcode_instances = []
+class CScriptOp(int):
+ """A single script opcode"""
+ __slots__ = []
+
+ @staticmethod
+ def encode_op_pushdata(d):
+ """Encode a PUSHDATA op, returning bytes"""
+ if len(d) < 0x4c:
+ return b'' + bchr(len(d)) + d # OP_PUSHDATA
+ elif len(d) <= 0xff:
+ return b'\x4c' + bchr(len(d)) + d # OP_PUSHDATA1
+ elif len(d) <= 0xffff:
+ return b'\x4d' + struct.pack(b'<H', len(d)) + d # OP_PUSHDATA2
+ elif len(d) <= 0xffffffff:
+ return b'\x4e' + struct.pack(b'<I', len(d)) + d # OP_PUSHDATA4
+ else:
+ raise ValueError("Data too long to encode in a PUSHDATA op")
+
+ @staticmethod
+ def encode_op_n(n):
+ """Encode a small integer op, returning an opcode"""
+ if not (0 <= n <= 16):
+ raise ValueError('Integer must be in range 0 <= n <= 16, got %d' % n)
+
+ if n == 0:
+ return OP_0
+ else:
+ return CScriptOp(OP_1 + n-1)
+
+ def decode_op_n(self):
+ """Decode a small integer opcode, returning an integer"""
+ if self == OP_0:
+ return 0
+
+ if not (self == OP_0 or OP_1 <= self <= OP_16):
+ raise ValueError('op %r is not an OP_N' % self)
+
+ return int(self - OP_1+1)
+
+ def is_small_int(self):
+ """Return true if the op pushes a small integer to the stack"""
+ if 0x51 <= self <= 0x60 or self == 0:
+ return True
+ else:
+ return False
+
+ def __str__(self):
+ return repr(self)
+
+ def __repr__(self):
+ if self in OPCODE_NAMES:
+ return OPCODE_NAMES[self]
+ else:
+ return 'CScriptOp(0x%x)' % self
+
+ def __new__(cls, n):
+ try:
+ return _opcode_instances[n]
+ except IndexError:
+ assert len(_opcode_instances) == n
+ _opcode_instances.append(super(CScriptOp, cls).__new__(cls, n))
+ return _opcode_instances[n]
+
+# Populate opcode instance table
+for n in range(0xff+1):
+ CScriptOp(n)
+
+
+# push value
+OP_0 = CScriptOp(0x00)
+OP_FALSE = OP_0
+OP_PUSHDATA1 = CScriptOp(0x4c)
+OP_PUSHDATA2 = CScriptOp(0x4d)
+OP_PUSHDATA4 = CScriptOp(0x4e)
+OP_1NEGATE = CScriptOp(0x4f)
+OP_RESERVED = CScriptOp(0x50)
+OP_1 = CScriptOp(0x51)
+OP_TRUE=OP_1
+OP_2 = CScriptOp(0x52)
+OP_3 = CScriptOp(0x53)
+OP_4 = CScriptOp(0x54)
+OP_5 = CScriptOp(0x55)
+OP_6 = CScriptOp(0x56)
+OP_7 = CScriptOp(0x57)
+OP_8 = CScriptOp(0x58)
+OP_9 = CScriptOp(0x59)
+OP_10 = CScriptOp(0x5a)
+OP_11 = CScriptOp(0x5b)
+OP_12 = CScriptOp(0x5c)
+OP_13 = CScriptOp(0x5d)
+OP_14 = CScriptOp(0x5e)
+OP_15 = CScriptOp(0x5f)
+OP_16 = CScriptOp(0x60)
+
+# control
+OP_NOP = CScriptOp(0x61)
+OP_VER = CScriptOp(0x62)
+OP_IF = CScriptOp(0x63)
+OP_NOTIF = CScriptOp(0x64)
+OP_VERIF = CScriptOp(0x65)
+OP_VERNOTIF = CScriptOp(0x66)
+OP_ELSE = CScriptOp(0x67)
+OP_ENDIF = CScriptOp(0x68)
+OP_VERIFY = CScriptOp(0x69)
+OP_RETURN = CScriptOp(0x6a)
+
+# stack ops
+OP_TOALTSTACK = CScriptOp(0x6b)
+OP_FROMALTSTACK = CScriptOp(0x6c)
+OP_2DROP = CScriptOp(0x6d)
+OP_2DUP = CScriptOp(0x6e)
+OP_3DUP = CScriptOp(0x6f)
+OP_2OVER = CScriptOp(0x70)
+OP_2ROT = CScriptOp(0x71)
+OP_2SWAP = CScriptOp(0x72)
+OP_IFDUP = CScriptOp(0x73)
+OP_DEPTH = CScriptOp(0x74)
+OP_DROP = CScriptOp(0x75)
+OP_DUP = CScriptOp(0x76)
+OP_NIP = CScriptOp(0x77)
+OP_OVER = CScriptOp(0x78)
+OP_PICK = CScriptOp(0x79)
+OP_ROLL = CScriptOp(0x7a)
+OP_ROT = CScriptOp(0x7b)
+OP_SWAP = CScriptOp(0x7c)
+OP_TUCK = CScriptOp(0x7d)
+
+# splice ops
+OP_CAT = CScriptOp(0x7e)
+OP_SUBSTR = CScriptOp(0x7f)
+OP_LEFT = CScriptOp(0x80)
+OP_RIGHT = CScriptOp(0x81)
+OP_SIZE = CScriptOp(0x82)
+
+# bit logic
+OP_INVERT = CScriptOp(0x83)
+OP_AND = CScriptOp(0x84)
+OP_OR = CScriptOp(0x85)
+OP_XOR = CScriptOp(0x86)
+OP_EQUAL = CScriptOp(0x87)
+OP_EQUALVERIFY = CScriptOp(0x88)
+OP_RESERVED1 = CScriptOp(0x89)
+OP_RESERVED2 = CScriptOp(0x8a)
+
+# numeric
+OP_1ADD = CScriptOp(0x8b)
+OP_1SUB = CScriptOp(0x8c)
+OP_2MUL = CScriptOp(0x8d)
+OP_2DIV = CScriptOp(0x8e)
+OP_NEGATE = CScriptOp(0x8f)
+OP_ABS = CScriptOp(0x90)
+OP_NOT = CScriptOp(0x91)
+OP_0NOTEQUAL = CScriptOp(0x92)
+
+OP_ADD = CScriptOp(0x93)
+OP_SUB = CScriptOp(0x94)
+OP_MUL = CScriptOp(0x95)
+OP_DIV = CScriptOp(0x96)
+OP_MOD = CScriptOp(0x97)
+OP_LSHIFT = CScriptOp(0x98)
+OP_RSHIFT = CScriptOp(0x99)
+
+OP_BOOLAND = CScriptOp(0x9a)
+OP_BOOLOR = CScriptOp(0x9b)
+OP_NUMEQUAL = CScriptOp(0x9c)
+OP_NUMEQUALVERIFY = CScriptOp(0x9d)
+OP_NUMNOTEQUAL = CScriptOp(0x9e)
+OP_LESSTHAN = CScriptOp(0x9f)
+OP_GREATERTHAN = CScriptOp(0xa0)
+OP_LESSTHANOREQUAL = CScriptOp(0xa1)
+OP_GREATERTHANOREQUAL = CScriptOp(0xa2)
+OP_MIN = CScriptOp(0xa3)
+OP_MAX = CScriptOp(0xa4)
+
+OP_WITHIN = CScriptOp(0xa5)
+
+# crypto
+OP_RIPEMD160 = CScriptOp(0xa6)
+OP_SHA1 = CScriptOp(0xa7)
+OP_SHA256 = CScriptOp(0xa8)
+OP_HASH160 = CScriptOp(0xa9)
+OP_HASH256 = CScriptOp(0xaa)
+OP_CODESEPARATOR = CScriptOp(0xab)
+OP_CHECKSIG = CScriptOp(0xac)
+OP_CHECKSIGVERIFY = CScriptOp(0xad)
+OP_CHECKMULTISIG = CScriptOp(0xae)
+OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf)
+
+# expansion
+OP_NOP1 = CScriptOp(0xb0)
+OP_CHECKLOCKTIMEVERIFY = CScriptOp(0xb1)
+OP_CHECKSEQUENCEVERIFY = CScriptOp(0xb2)
+OP_NOP4 = CScriptOp(0xb3)
+OP_NOP5 = CScriptOp(0xb4)
+OP_NOP6 = CScriptOp(0xb5)
+OP_NOP7 = CScriptOp(0xb6)
+OP_NOP8 = CScriptOp(0xb7)
+OP_NOP9 = CScriptOp(0xb8)
+OP_NOP10 = CScriptOp(0xb9)
+
+# template matching params
+OP_SMALLINTEGER = CScriptOp(0xfa)
+OP_PUBKEYS = CScriptOp(0xfb)
+OP_PUBKEYHASH = CScriptOp(0xfd)
+OP_PUBKEY = CScriptOp(0xfe)
+
+OP_INVALIDOPCODE = CScriptOp(0xff)
+
+VALID_OPCODES = {
+ OP_1NEGATE,
+ OP_RESERVED,
+ OP_1,
+ OP_2,
+ OP_3,
+ OP_4,
+ OP_5,
+ OP_6,
+ OP_7,
+ OP_8,
+ OP_9,
+ OP_10,
+ OP_11,
+ OP_12,
+ OP_13,
+ OP_14,
+ OP_15,
+ OP_16,
+
+ OP_NOP,
+ OP_VER,
+ OP_IF,
+ OP_NOTIF,
+ OP_VERIF,
+ OP_VERNOTIF,
+ OP_ELSE,
+ OP_ENDIF,
+ OP_VERIFY,
+ OP_RETURN,
+
+ OP_TOALTSTACK,
+ OP_FROMALTSTACK,
+ OP_2DROP,
+ OP_2DUP,
+ OP_3DUP,
+ OP_2OVER,
+ OP_2ROT,
+ OP_2SWAP,
+ OP_IFDUP,
+ OP_DEPTH,
+ OP_DROP,
+ OP_DUP,
+ OP_NIP,
+ OP_OVER,
+ OP_PICK,
+ OP_ROLL,
+ OP_ROT,
+ OP_SWAP,
+ OP_TUCK,
+
+ OP_CAT,
+ OP_SUBSTR,
+ OP_LEFT,
+ OP_RIGHT,
+ OP_SIZE,
+
+ OP_INVERT,
+ OP_AND,
+ OP_OR,
+ OP_XOR,
+ OP_EQUAL,
+ OP_EQUALVERIFY,
+ OP_RESERVED1,
+ OP_RESERVED2,
+
+ OP_1ADD,
+ OP_1SUB,
+ OP_2MUL,
+ OP_2DIV,
+ OP_NEGATE,
+ OP_ABS,
+ OP_NOT,
+ OP_0NOTEQUAL,
+
+ OP_ADD,
+ OP_SUB,
+ OP_MUL,
+ OP_DIV,
+ OP_MOD,
+ OP_LSHIFT,
+ OP_RSHIFT,
+
+ OP_BOOLAND,
+ OP_BOOLOR,
+ OP_NUMEQUAL,
+ OP_NUMEQUALVERIFY,
+ OP_NUMNOTEQUAL,
+ OP_LESSTHAN,
+ OP_GREATERTHAN,
+ OP_LESSTHANOREQUAL,
+ OP_GREATERTHANOREQUAL,
+ OP_MIN,
+ OP_MAX,
+
+ OP_WITHIN,
+
+ OP_RIPEMD160,
+ OP_SHA1,
+ OP_SHA256,
+ OP_HASH160,
+ OP_HASH256,
+ OP_CODESEPARATOR,
+ OP_CHECKSIG,
+ OP_CHECKSIGVERIFY,
+ OP_CHECKMULTISIG,
+ OP_CHECKMULTISIGVERIFY,
+
+ OP_NOP1,
+ OP_CHECKLOCKTIMEVERIFY,
+ OP_CHECKSEQUENCEVERIFY,
+ OP_NOP4,
+ OP_NOP5,
+ OP_NOP6,
+ OP_NOP7,
+ OP_NOP8,
+ OP_NOP9,
+ OP_NOP10,
+
+ OP_SMALLINTEGER,
+ OP_PUBKEYS,
+ OP_PUBKEYHASH,
+ OP_PUBKEY,
+}
+
+OPCODE_NAMES.update({
+ OP_0 : 'OP_0',
+ OP_PUSHDATA1 : 'OP_PUSHDATA1',
+ OP_PUSHDATA2 : 'OP_PUSHDATA2',
+ OP_PUSHDATA4 : 'OP_PUSHDATA4',
+ OP_1NEGATE : 'OP_1NEGATE',
+ OP_RESERVED : 'OP_RESERVED',
+ OP_1 : 'OP_1',
+ OP_2 : 'OP_2',
+ OP_3 : 'OP_3',
+ OP_4 : 'OP_4',
+ OP_5 : 'OP_5',
+ OP_6 : 'OP_6',
+ OP_7 : 'OP_7',
+ OP_8 : 'OP_8',
+ OP_9 : 'OP_9',
+ OP_10 : 'OP_10',
+ OP_11 : 'OP_11',
+ OP_12 : 'OP_12',
+ OP_13 : 'OP_13',
+ OP_14 : 'OP_14',
+ OP_15 : 'OP_15',
+ OP_16 : 'OP_16',
+ OP_NOP : 'OP_NOP',
+ OP_VER : 'OP_VER',
+ OP_IF : 'OP_IF',
+ OP_NOTIF : 'OP_NOTIF',
+ OP_VERIF : 'OP_VERIF',
+ OP_VERNOTIF : 'OP_VERNOTIF',
+ OP_ELSE : 'OP_ELSE',
+ OP_ENDIF : 'OP_ENDIF',
+ OP_VERIFY : 'OP_VERIFY',
+ OP_RETURN : 'OP_RETURN',
+ OP_TOALTSTACK : 'OP_TOALTSTACK',
+ OP_FROMALTSTACK : 'OP_FROMALTSTACK',
+ OP_2DROP : 'OP_2DROP',
+ OP_2DUP : 'OP_2DUP',
+ OP_3DUP : 'OP_3DUP',
+ OP_2OVER : 'OP_2OVER',
+ OP_2ROT : 'OP_2ROT',
+ OP_2SWAP : 'OP_2SWAP',
+ OP_IFDUP : 'OP_IFDUP',
+ OP_DEPTH : 'OP_DEPTH',
+ OP_DROP : 'OP_DROP',
+ OP_DUP : 'OP_DUP',
+ OP_NIP : 'OP_NIP',
+ OP_OVER : 'OP_OVER',
+ OP_PICK : 'OP_PICK',
+ OP_ROLL : 'OP_ROLL',
+ OP_ROT : 'OP_ROT',
+ OP_SWAP : 'OP_SWAP',
+ OP_TUCK : 'OP_TUCK',
+ OP_CAT : 'OP_CAT',
+ OP_SUBSTR : 'OP_SUBSTR',
+ OP_LEFT : 'OP_LEFT',
+ OP_RIGHT : 'OP_RIGHT',
+ OP_SIZE : 'OP_SIZE',
+ OP_INVERT : 'OP_INVERT',
+ OP_AND : 'OP_AND',
+ OP_OR : 'OP_OR',
+ OP_XOR : 'OP_XOR',
+ OP_EQUAL : 'OP_EQUAL',
+ OP_EQUALVERIFY : 'OP_EQUALVERIFY',
+ OP_RESERVED1 : 'OP_RESERVED1',
+ OP_RESERVED2 : 'OP_RESERVED2',
+ OP_1ADD : 'OP_1ADD',
+ OP_1SUB : 'OP_1SUB',
+ OP_2MUL : 'OP_2MUL',
+ OP_2DIV : 'OP_2DIV',
+ OP_NEGATE : 'OP_NEGATE',
+ OP_ABS : 'OP_ABS',
+ OP_NOT : 'OP_NOT',
+ OP_0NOTEQUAL : 'OP_0NOTEQUAL',
+ OP_ADD : 'OP_ADD',
+ OP_SUB : 'OP_SUB',
+ OP_MUL : 'OP_MUL',
+ OP_DIV : 'OP_DIV',
+ OP_MOD : 'OP_MOD',
+ OP_LSHIFT : 'OP_LSHIFT',
+ OP_RSHIFT : 'OP_RSHIFT',
+ OP_BOOLAND : 'OP_BOOLAND',
+ OP_BOOLOR : 'OP_BOOLOR',
+ OP_NUMEQUAL : 'OP_NUMEQUAL',
+ OP_NUMEQUALVERIFY : 'OP_NUMEQUALVERIFY',
+ OP_NUMNOTEQUAL : 'OP_NUMNOTEQUAL',
+ OP_LESSTHAN : 'OP_LESSTHAN',
+ OP_GREATERTHAN : 'OP_GREATERTHAN',
+ OP_LESSTHANOREQUAL : 'OP_LESSTHANOREQUAL',
+ OP_GREATERTHANOREQUAL : 'OP_GREATERTHANOREQUAL',
+ OP_MIN : 'OP_MIN',
+ OP_MAX : 'OP_MAX',
+ OP_WITHIN : 'OP_WITHIN',
+ OP_RIPEMD160 : 'OP_RIPEMD160',
+ OP_SHA1 : 'OP_SHA1',
+ OP_SHA256 : 'OP_SHA256',
+ OP_HASH160 : 'OP_HASH160',
+ OP_HASH256 : 'OP_HASH256',
+ OP_CODESEPARATOR : 'OP_CODESEPARATOR',
+ OP_CHECKSIG : 'OP_CHECKSIG',
+ OP_CHECKSIGVERIFY : 'OP_CHECKSIGVERIFY',
+ OP_CHECKMULTISIG : 'OP_CHECKMULTISIG',
+ OP_CHECKMULTISIGVERIFY : 'OP_CHECKMULTISIGVERIFY',
+ OP_NOP1 : 'OP_NOP1',
+ OP_CHECKLOCKTIMEVERIFY : 'OP_CHECKLOCKTIMEVERIFY',
+ OP_CHECKSEQUENCEVERIFY : 'OP_CHECKSEQUENCEVERIFY',
+ OP_NOP4 : 'OP_NOP4',
+ OP_NOP5 : 'OP_NOP5',
+ OP_NOP6 : 'OP_NOP6',
+ OP_NOP7 : 'OP_NOP7',
+ OP_NOP8 : 'OP_NOP8',
+ OP_NOP9 : 'OP_NOP9',
+ OP_NOP10 : 'OP_NOP10',
+ OP_SMALLINTEGER : 'OP_SMALLINTEGER',
+ OP_PUBKEYS : 'OP_PUBKEYS',
+ OP_PUBKEYHASH : 'OP_PUBKEYHASH',
+ OP_PUBKEY : 'OP_PUBKEY',
+ OP_INVALIDOPCODE : 'OP_INVALIDOPCODE',
+})
+
+OPCODES_BY_NAME = {
+ 'OP_0' : OP_0,
+ 'OP_PUSHDATA1' : OP_PUSHDATA1,
+ 'OP_PUSHDATA2' : OP_PUSHDATA2,
+ 'OP_PUSHDATA4' : OP_PUSHDATA4,
+ 'OP_1NEGATE' : OP_1NEGATE,
+ 'OP_RESERVED' : OP_RESERVED,
+ 'OP_1' : OP_1,
+ 'OP_2' : OP_2,
+ 'OP_3' : OP_3,
+ 'OP_4' : OP_4,
+ 'OP_5' : OP_5,
+ 'OP_6' : OP_6,
+ 'OP_7' : OP_7,
+ 'OP_8' : OP_8,
+ 'OP_9' : OP_9,
+ 'OP_10' : OP_10,
+ 'OP_11' : OP_11,
+ 'OP_12' : OP_12,
+ 'OP_13' : OP_13,
+ 'OP_14' : OP_14,
+ 'OP_15' : OP_15,
+ 'OP_16' : OP_16,
+ 'OP_NOP' : OP_NOP,
+ 'OP_VER' : OP_VER,
+ 'OP_IF' : OP_IF,
+ 'OP_NOTIF' : OP_NOTIF,
+ 'OP_VERIF' : OP_VERIF,
+ 'OP_VERNOTIF' : OP_VERNOTIF,
+ 'OP_ELSE' : OP_ELSE,
+ 'OP_ENDIF' : OP_ENDIF,
+ 'OP_VERIFY' : OP_VERIFY,
+ 'OP_RETURN' : OP_RETURN,
+ 'OP_TOALTSTACK' : OP_TOALTSTACK,
+ 'OP_FROMALTSTACK' : OP_FROMALTSTACK,
+ 'OP_2DROP' : OP_2DROP,
+ 'OP_2DUP' : OP_2DUP,
+ 'OP_3DUP' : OP_3DUP,
+ 'OP_2OVER' : OP_2OVER,
+ 'OP_2ROT' : OP_2ROT,
+ 'OP_2SWAP' : OP_2SWAP,
+ 'OP_IFDUP' : OP_IFDUP,
+ 'OP_DEPTH' : OP_DEPTH,
+ 'OP_DROP' : OP_DROP,
+ 'OP_DUP' : OP_DUP,
+ 'OP_NIP' : OP_NIP,
+ 'OP_OVER' : OP_OVER,
+ 'OP_PICK' : OP_PICK,
+ 'OP_ROLL' : OP_ROLL,
+ 'OP_ROT' : OP_ROT,
+ 'OP_SWAP' : OP_SWAP,
+ 'OP_TUCK' : OP_TUCK,
+ 'OP_CAT' : OP_CAT,
+ 'OP_SUBSTR' : OP_SUBSTR,
+ 'OP_LEFT' : OP_LEFT,
+ 'OP_RIGHT' : OP_RIGHT,
+ 'OP_SIZE' : OP_SIZE,
+ 'OP_INVERT' : OP_INVERT,
+ 'OP_AND' : OP_AND,
+ 'OP_OR' : OP_OR,
+ 'OP_XOR' : OP_XOR,
+ 'OP_EQUAL' : OP_EQUAL,
+ 'OP_EQUALVERIFY' : OP_EQUALVERIFY,
+ 'OP_RESERVED1' : OP_RESERVED1,
+ 'OP_RESERVED2' : OP_RESERVED2,
+ 'OP_1ADD' : OP_1ADD,
+ 'OP_1SUB' : OP_1SUB,
+ 'OP_2MUL' : OP_2MUL,
+ 'OP_2DIV' : OP_2DIV,
+ 'OP_NEGATE' : OP_NEGATE,
+ 'OP_ABS' : OP_ABS,
+ 'OP_NOT' : OP_NOT,
+ 'OP_0NOTEQUAL' : OP_0NOTEQUAL,
+ 'OP_ADD' : OP_ADD,
+ 'OP_SUB' : OP_SUB,
+ 'OP_MUL' : OP_MUL,
+ 'OP_DIV' : OP_DIV,
+ 'OP_MOD' : OP_MOD,
+ 'OP_LSHIFT' : OP_LSHIFT,
+ 'OP_RSHIFT' : OP_RSHIFT,
+ 'OP_BOOLAND' : OP_BOOLAND,
+ 'OP_BOOLOR' : OP_BOOLOR,
+ 'OP_NUMEQUAL' : OP_NUMEQUAL,
+ 'OP_NUMEQUALVERIFY' : OP_NUMEQUALVERIFY,
+ 'OP_NUMNOTEQUAL' : OP_NUMNOTEQUAL,
+ 'OP_LESSTHAN' : OP_LESSTHAN,
+ 'OP_GREATERTHAN' : OP_GREATERTHAN,
+ 'OP_LESSTHANOREQUAL' : OP_LESSTHANOREQUAL,
+ 'OP_GREATERTHANOREQUAL' : OP_GREATERTHANOREQUAL,
+ 'OP_MIN' : OP_MIN,
+ 'OP_MAX' : OP_MAX,
+ 'OP_WITHIN' : OP_WITHIN,
+ 'OP_RIPEMD160' : OP_RIPEMD160,
+ 'OP_SHA1' : OP_SHA1,
+ 'OP_SHA256' : OP_SHA256,
+ 'OP_HASH160' : OP_HASH160,
+ 'OP_HASH256' : OP_HASH256,
+ 'OP_CODESEPARATOR' : OP_CODESEPARATOR,
+ 'OP_CHECKSIG' : OP_CHECKSIG,
+ 'OP_CHECKSIGVERIFY' : OP_CHECKSIGVERIFY,
+ 'OP_CHECKMULTISIG' : OP_CHECKMULTISIG,
+ 'OP_CHECKMULTISIGVERIFY' : OP_CHECKMULTISIGVERIFY,
+ 'OP_NOP1' : OP_NOP1,
+ 'OP_CHECKLOCKTIMEVERIFY' : OP_CHECKLOCKTIMEVERIFY,
+ 'OP_CHECKSEQUENCEVERIFY' : OP_CHECKSEQUENCEVERIFY,
+ 'OP_NOP4' : OP_NOP4,
+ 'OP_NOP5' : OP_NOP5,
+ 'OP_NOP6' : OP_NOP6,
+ 'OP_NOP7' : OP_NOP7,
+ 'OP_NOP8' : OP_NOP8,
+ 'OP_NOP9' : OP_NOP9,
+ 'OP_NOP10' : OP_NOP10,
+ 'OP_SMALLINTEGER' : OP_SMALLINTEGER,
+ 'OP_PUBKEYS' : OP_PUBKEYS,
+ 'OP_PUBKEYHASH' : OP_PUBKEYHASH,
+ 'OP_PUBKEY' : OP_PUBKEY,
+}
+
+class CScriptInvalidError(Exception):
+ """Base class for CScript exceptions"""
+ pass
+
+class CScriptTruncatedPushDataError(CScriptInvalidError):
+ """Invalid pushdata due to truncation"""
+ def __init__(self, msg, data):
+ self.data = data
+ super(CScriptTruncatedPushDataError, self).__init__(msg)
+
+# This is used, eg, for blockchain heights in coinbase scripts (bip34)
+class CScriptNum(object):
+ def __init__(self, d=0):
+ self.value = d
+
+ @staticmethod
+ def encode(obj):
+ r = bytearray(0)
+ if obj.value == 0:
+ return bytes(r)
+ neg = obj.value < 0
+ absvalue = -obj.value if neg else obj.value
+ while (absvalue):
+ r.append(absvalue & 0xff)
+ absvalue >>= 8
+ if r[-1] & 0x80:
+ r.append(0x80 if neg else 0)
+ elif neg:
+ r[-1] |= 0x80
+ return bytes(bchr(len(r)) + r)
+
+
+class CScript(bytes):
+ """Serialized script
+
+ A bytes subclass, so you can use this directly whenever bytes are accepted.
+ Note that this means that indexing does *not* work - you'll get an index by
+ byte rather than opcode. This format was chosen for efficiency so that the
+ general case would not require creating a lot of little CScriptOP objects.
+
+ iter(script) however does iterate by opcode.
+ """
+ @classmethod
+ def __coerce_instance(cls, other):
+ # Coerce other into bytes
+ if isinstance(other, CScriptOp):
+ other = bchr(other)
+ elif isinstance(other, CScriptNum):
+ if (other.value == 0):
+ other = bchr(CScriptOp(OP_0))
+ else:
+ other = CScriptNum.encode(other)
+ elif isinstance(other, int):
+ if 0 <= other <= 16:
+ other = bytes(bchr(CScriptOp.encode_op_n(other)))
+ elif other == -1:
+ other = bytes(bchr(OP_1NEGATE))
+ else:
+ other = CScriptOp.encode_op_pushdata(bn2vch(other))
+ elif isinstance(other, (bytes, bytearray)):
+ other = CScriptOp.encode_op_pushdata(other)
+ return other
+
+ def __add__(self, other):
+ # Do the coercion outside of the try block so that errors in it are
+ # noticed.
+ other = self.__coerce_instance(other)
+
+ try:
+ # bytes.__add__ always returns bytes instances unfortunately
+ return CScript(super(CScript, self).__add__(other))
+ except TypeError:
+ raise TypeError('Can not add a %r instance to a CScript' % other.__class__)
+
+ def join(self, iterable):
+ # join makes no sense for a CScript()
+ raise NotImplementedError
+
+ def __new__(cls, value=b''):
+ if isinstance(value, bytes) or isinstance(value, bytearray):
+ return super(CScript, cls).__new__(cls, value)
+ else:
+ def coerce_iterable(iterable):
+ for instance in iterable:
+ yield cls.__coerce_instance(instance)
+ # Annoyingly on both python2 and python3 bytes.join() always
+ # returns a bytes instance even when subclassed.
+ return super(CScript, cls).__new__(cls, b''.join(coerce_iterable(value)))
+
+ def raw_iter(self):
+ """Raw iteration
+
+ Yields tuples of (opcode, data, sop_idx) so that the different possible
+ PUSHDATA encodings can be accurately distinguished, as well as
+ determining the exact opcode byte indexes. (sop_idx)
+ """
+ i = 0
+ while i < len(self):
+ sop_idx = i
+ opcode = bord(self[i])
+ i += 1
+
+ if opcode > OP_PUSHDATA4:
+ yield (opcode, None, sop_idx)
+ else:
+ datasize = None
+ pushdata_type = None
+ if opcode < OP_PUSHDATA1:
+ pushdata_type = 'PUSHDATA(%d)' % opcode
+ datasize = opcode
+
+ elif opcode == OP_PUSHDATA1:
+ pushdata_type = 'PUSHDATA1'
+ if i >= len(self):
+ raise CScriptInvalidError('PUSHDATA1: missing data length')
+ datasize = bord(self[i])
+ i += 1
+
+ elif opcode == OP_PUSHDATA2:
+ pushdata_type = 'PUSHDATA2'
+ if i + 1 >= len(self):
+ raise CScriptInvalidError('PUSHDATA2: missing data length')
+ datasize = bord(self[i]) + (bord(self[i+1]) << 8)
+ i += 2
+
+ elif opcode == OP_PUSHDATA4:
+ pushdata_type = 'PUSHDATA4'
+ if i + 3 >= len(self):
+ raise CScriptInvalidError('PUSHDATA4: missing data length')
+ datasize = bord(self[i]) + (bord(self[i+1]) << 8) + (bord(self[i+2]) << 16) + (bord(self[i+3]) << 24)
+ i += 4
+
+ else:
+ assert False # shouldn't happen
+
+
+ data = bytes(self[i:i+datasize])
+
+ # Check for truncation
+ if len(data) < datasize:
+ raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data)
+
+ i += datasize
+
+ yield (opcode, data, sop_idx)
+
+ def __iter__(self):
+ """'Cooked' iteration
+
+ Returns either a CScriptOP instance, an integer, or bytes, as
+ appropriate.
+
+ See raw_iter() if you need to distinguish the different possible
+ PUSHDATA encodings.
+ """
+ for (opcode, data, sop_idx) in self.raw_iter():
+ if data is not None:
+ yield data
+ else:
+ opcode = CScriptOp(opcode)
+
+ if opcode.is_small_int():
+ yield opcode.decode_op_n()
+ else:
+ yield CScriptOp(opcode)
+
+ def __repr__(self):
+ # For Python3 compatibility add b before strings so testcases don't
+ # need to change
+ def _repr(o):
+ if isinstance(o, bytes):
+ return b"x('%s')" % hexlify(o).decode('ascii')
+ else:
+ return repr(o)
+
+ ops = []
+ i = iter(self)
+ while True:
+ op = None
+ try:
+ op = _repr(next(i))
+ except CScriptTruncatedPushDataError as err:
+ op = '%s...<ERROR: %s>' % (_repr(err.data), err)
+ break
+ except CScriptInvalidError as err:
+ op = '<ERROR: %s>' % err
+ break
+ except StopIteration:
+ break
+ finally:
+ if op is not None:
+ ops.append(op)
+
+ return "CScript([%s])" % ', '.join(ops)
+
+ def GetSigOpCount(self, fAccurate):
+ """Get the SigOp count.
+
+ fAccurate - Accurately count CHECKMULTISIG, see BIP16 for details.
+
+ Note that this is consensus-critical.
+ """
+ n = 0
+ lastOpcode = OP_INVALIDOPCODE
+ for (opcode, data, sop_idx) in self.raw_iter():
+ if opcode in (OP_CHECKSIG, OP_CHECKSIGVERIFY):
+ n += 1
+ elif opcode in (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY):
+ if fAccurate and (OP_1 <= lastOpcode <= OP_16):
+ n += opcode.decode_op_n()
+ else:
+ n += 20
+ lastOpcode = opcode
+ return n
+
+
+SIGHASH_ALL = 1
+SIGHASH_NONE = 2
+SIGHASH_SINGLE = 3
+SIGHASH_ANYONECANPAY = 0x80
+
+def FindAndDelete(script, sig):
+ """Consensus critical, see FindAndDelete() in Satoshi codebase"""
+ r = b''
+ last_sop_idx = sop_idx = 0
+ skip = True
+ for (opcode, data, sop_idx) in script.raw_iter():
+ if not skip:
+ r += script[last_sop_idx:sop_idx]
+ last_sop_idx = sop_idx
+ if script[sop_idx:sop_idx + len(sig)] == sig:
+ skip = True
+ else:
+ skip = False
+ if not skip:
+ r += script[last_sop_idx:]
+ return CScript(r)
+
+
+def SignatureHash(script, txTo, inIdx, hashtype):
+ """Consensus-correct SignatureHash
+
+ Returns (hash, err) to precisely match the consensus-critical behavior of
+ the SIGHASH_SINGLE bug. (inIdx is *not* checked for validity)
+ """
+ HASH_ONE = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+
+ if inIdx >= len(txTo.vin):
+ return (HASH_ONE, "inIdx %d out of range (%d)" % (inIdx, len(txTo.vin)))
+ txtmp = CTransaction(txTo)
+
+ for txin in txtmp.vin:
+ txin.scriptSig = b''
+ txtmp.vin[inIdx].scriptSig = FindAndDelete(script, CScript([OP_CODESEPARATOR]))
+
+ if (hashtype & 0x1f) == SIGHASH_NONE:
+ txtmp.vout = []
+
+ for i in range(len(txtmp.vin)):
+ if i != inIdx:
+ txtmp.vin[i].nSequence = 0
+
+ elif (hashtype & 0x1f) == SIGHASH_SINGLE:
+ outIdx = inIdx
+ if outIdx >= len(txtmp.vout):
+ return (HASH_ONE, "outIdx %d out of range (%d)" % (outIdx, len(txtmp.vout)))
+
+ tmp = txtmp.vout[outIdx]
+ txtmp.vout = []
+ for i in range(outIdx):
+ txtmp.vout.append(CTxOut())
+ txtmp.vout.append(tmp)
+
+ for i in range(len(txtmp.vin)):
+ if i != inIdx:
+ txtmp.vin[i].nSequence = 0
+
+ if hashtype & SIGHASH_ANYONECANPAY:
+ tmp = txtmp.vin[inIdx]
+ txtmp.vin = []
+ txtmp.vin.append(tmp)
+
+ s = txtmp.serialize()
+ s += struct.pack(b"<I", hashtype)
+
+ hash = hash256(s)
+
+ return (hash, None)
+
+# TODO: Allow cached hashPrevouts/hashSequence/hashOutputs to be provided.
+# Performance optimization probably not necessary for python tests, however.
+# Note that this corresponds to sigversion == 1 in EvalScript, which is used
+# for version 0 witnesses.
+def SegwitVersion1SignatureHash(script, txTo, inIdx, hashtype, amount):
+
+ hashPrevouts = 0
+ hashSequence = 0
+ hashOutputs = 0
+
+ if not (hashtype & SIGHASH_ANYONECANPAY):
+ serialize_prevouts = bytes()
+ for i in txTo.vin:
+ serialize_prevouts += i.prevout.serialize()
+ hashPrevouts = uint256_from_str(hash256(serialize_prevouts))
+
+ if (not (hashtype & SIGHASH_ANYONECANPAY) and (hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
+ serialize_sequence = bytes()
+ for i in txTo.vin:
+ serialize_sequence += struct.pack("<I", i.nSequence)
+ hashSequence = uint256_from_str(hash256(serialize_sequence))
+
+ if ((hashtype & 0x1f) != SIGHASH_SINGLE and (hashtype & 0x1f) != SIGHASH_NONE):
+ serialize_outputs = bytes()
+ for o in txTo.vout:
+ serialize_outputs += o.serialize()
+ hashOutputs = uint256_from_str(hash256(serialize_outputs))
+ elif ((hashtype & 0x1f) == SIGHASH_SINGLE and inIdx < len(txTo.vout)):
+ serialize_outputs = txTo.vout[inIdx].serialize()
+ hashOutputs = uint256_from_str(hash256(serialize_outputs))
+
+ ss = bytes()
+ ss += struct.pack("<i", txTo.nVersion)
+ ss += ser_uint256(hashPrevouts)
+ ss += ser_uint256(hashSequence)
+ ss += txTo.vin[inIdx].prevout.serialize()
+ ss += ser_string(script)
+ ss += struct.pack("<q", amount)
+ ss += struct.pack("<I", txTo.vin[inIdx].nSequence)
+ ss += ser_uint256(hashOutputs)
+ ss += struct.pack("<i", txTo.nLockTime)
+ ss += struct.pack("<I", hashtype)
+
+ return hash256(ss)
diff --git a/qa/rpc-tests/test_framework/socks5.py b/qa/rpc-tests/test_framework/socks5.py
new file mode 100644
index 0000000000..372f5ed605
--- /dev/null
+++ b/qa/rpc-tests/test_framework/socks5.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+'''
+Dummy Socks5 server for testing.
+'''
+
+import socket, threading, queue
+import traceback, sys
+
+### Protocol constants
+class Command:
+ CONNECT = 0x01
+
+class AddressType:
+ IPV4 = 0x01
+ DOMAINNAME = 0x03
+ IPV6 = 0x04
+
+### Utility functions
+def recvall(s, n):
+ '''Receive n bytes from a socket, or fail'''
+ rv = bytearray()
+ while n > 0:
+ d = s.recv(n)
+ if not d:
+ raise IOError('Unexpected end of stream')
+ rv.extend(d)
+ n -= len(d)
+ return rv
+
+### Implementation classes
+class Socks5Configuration(object):
+ '''Proxy configuration'''
+ def __init__(self):
+ self.addr = None # Bind address (must be set)
+ self.af = socket.AF_INET # Bind address family
+ self.unauth = False # Support unauthenticated
+ self.auth = False # Support authentication
+
+class Socks5Command(object):
+ '''Information about an incoming socks5 command'''
+ def __init__(self, cmd, atyp, addr, port, username, password):
+ self.cmd = cmd # Command (one of Command.*)
+ self.atyp = atyp # Address type (one of AddressType.*)
+ self.addr = addr # Address
+ self.port = port # Port to connect to
+ self.username = username
+ self.password = password
+ def __repr__(self):
+ return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self.cmd, self.atyp, self.addr, self.port, self.username, self.password)
+
+class Socks5Connection(object):
+ def __init__(self, serv, conn, peer):
+ self.serv = serv
+ self.conn = conn
+ self.peer = peer
+
+ def handle(self):
+ '''
+ Handle socks5 request according to RFC1928
+ '''
+ try:
+ # Verify socks version
+ ver = recvall(self.conn, 1)[0]
+ if ver != 0x05:
+ raise IOError('Invalid socks version %i' % ver)
+ # Choose authentication method
+ nmethods = recvall(self.conn, 1)[0]
+ methods = bytearray(recvall(self.conn, nmethods))
+ method = None
+ if 0x02 in methods and self.serv.conf.auth:
+ method = 0x02 # username/password
+ elif 0x00 in methods and self.serv.conf.unauth:
+ method = 0x00 # unauthenticated
+ if method is None:
+ raise IOError('No supported authentication method was offered')
+ # Send response
+ self.conn.sendall(bytearray([0x05, method]))
+ # Read authentication (optional)
+ username = None
+ password = None
+ if method == 0x02:
+ ver = recvall(self.conn, 1)[0]
+ if ver != 0x01:
+ raise IOError('Invalid auth packet version %i' % ver)
+ ulen = recvall(self.conn, 1)[0]
+ username = str(recvall(self.conn, ulen))
+ plen = recvall(self.conn, 1)[0]
+ password = str(recvall(self.conn, plen))
+ # Send authentication response
+ self.conn.sendall(bytearray([0x01, 0x00]))
+
+ # Read connect request
+ (ver,cmd,rsv,atyp) = recvall(self.conn, 4)
+ if ver != 0x05:
+ raise IOError('Invalid socks version %i in connect request' % ver)
+ if cmd != Command.CONNECT:
+ raise IOError('Unhandled command %i in connect request' % cmd)
+
+ if atyp == AddressType.IPV4:
+ addr = recvall(self.conn, 4)
+ elif atyp == AddressType.DOMAINNAME:
+ n = recvall(self.conn, 1)[0]
+ addr = recvall(self.conn, n)
+ elif atyp == AddressType.IPV6:
+ addr = recvall(self.conn, 16)
+ else:
+ raise IOError('Unknown address type %i' % atyp)
+ port_hi,port_lo = recvall(self.conn, 2)
+ port = (port_hi << 8) | port_lo
+
+ # Send dummy response
+ self.conn.sendall(bytearray([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
+
+ cmdin = Socks5Command(cmd, atyp, addr, port, username, password)
+ self.serv.queue.put(cmdin)
+ print('Proxy: ', cmdin)
+ # Fall through to disconnect
+ except Exception as e:
+ traceback.print_exc(file=sys.stderr)
+ self.serv.queue.put(e)
+ finally:
+ self.conn.close()
+
+class Socks5Server(object):
+ def __init__(self, conf):
+ self.conf = conf
+ self.s = socket.socket(conf.af)
+ self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.s.bind(conf.addr)
+ self.s.listen(5)
+ self.running = False
+ self.thread = None
+ self.queue = queue.Queue() # report connections and exceptions to client
+
+ def run(self):
+ while self.running:
+ (sockconn, peer) = self.s.accept()
+ if self.running:
+ conn = Socks5Connection(self, sockconn, peer)
+ thread = threading.Thread(None, conn.handle)
+ thread.daemon = True
+ thread.start()
+
+ def start(self):
+ assert(not self.running)
+ self.running = True
+ self.thread = threading.Thread(None, self.run)
+ self.thread.daemon = True
+ self.thread.start()
+
+ def stop(self):
+ self.running = False
+ # connect to self to end run loop
+ s = socket.socket(self.conf.af)
+ s.connect(self.conf.addr)
+ s.close()
+ self.thread.join()
+
diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py
new file mode 100755
index 0000000000..0dfece6b27
--- /dev/null
+++ b/qa/rpc-tests/test_framework/test_framework.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+# Base class for RPC testing
+
+import logging
+import optparse
+import os
+import sys
+import shutil
+import tempfile
+import traceback
+
+from .util import (
+ initialize_chain,
+ start_nodes,
+ connect_nodes_bi,
+ sync_blocks,
+ sync_mempools,
+ stop_nodes,
+ stop_node,
+ wait_bitcoinds,
+ enable_coverage,
+ check_json_precision,
+ initialize_chain_clean,
+ PortSeed,
+)
+from .authproxy import JSONRPCException
+
+
+class BitcoinTestFramework(object):
+
+ def __init__(self):
+ self.num_nodes = 4
+ self.setup_clean_chain = False
+ self.nodes = None
+
+ def run_test(self):
+ raise NotImplementedError
+
+ def add_options(self, parser):
+ pass
+
+ def setup_chain(self):
+ print("Initializing test directory "+self.options.tmpdir)
+ if self.setup_clean_chain:
+ initialize_chain_clean(self.options.tmpdir, self.num_nodes)
+ else:
+ initialize_chain(self.options.tmpdir, self.num_nodes)
+
+ def stop_node(self, num_node):
+ stop_node(self.nodes[num_node], num_node)
+
+ def setup_nodes(self):
+ return start_nodes(self.num_nodes, self.options.tmpdir)
+
+ def setup_network(self, split = False):
+ self.nodes = self.setup_nodes()
+
+ # Connect the nodes as a "chain". This allows us
+ # to split the network between nodes 1 and 2 to get
+ # two halves that can work on competing chains.
+
+ # If we joined network halves, connect the nodes from the joint
+ # on outward. This ensures that chains are properly reorganised.
+ if not split:
+ connect_nodes_bi(self.nodes, 1, 2)
+ sync_blocks(self.nodes[1:3])
+ sync_mempools(self.nodes[1:3])
+
+ connect_nodes_bi(self.nodes, 0, 1)
+ connect_nodes_bi(self.nodes, 2, 3)
+ self.is_network_split = split
+ self.sync_all()
+
+ def split_network(self):
+ """
+ Split the network of four nodes into nodes 0/1 and 2/3.
+ """
+ assert not self.is_network_split
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+ self.setup_network(True)
+
+ def sync_all(self):
+ if self.is_network_split:
+ sync_blocks(self.nodes[:2])
+ sync_blocks(self.nodes[2:])
+ sync_mempools(self.nodes[:2])
+ sync_mempools(self.nodes[2:])
+ else:
+ sync_blocks(self.nodes)
+ sync_mempools(self.nodes)
+
+ def join_network(self):
+ """
+ Join the (previously split) network halves together.
+ """
+ assert self.is_network_split
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+ self.setup_network(False)
+
+ def main(self):
+
+ 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("--noshutdown", dest="noshutdown", default=False, action="store_true",
+ help="Don't stop bitcoinds after the test execution")
+ parser.add_option("--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__))+"/../../../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")
+ parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true",
+ help="Print out all RPC calls as they are made")
+ parser.add_option("--portseed", dest="port_seed", default=os.getpid(), type='int',
+ help="The seed to use for assigning port numbers (default: current process id)")
+ parser.add_option("--coveragedir", dest="coveragedir",
+ help="Write tested RPC commands into this directory")
+ self.add_options(parser)
+ (self.options, self.args) = parser.parse_args()
+
+ self.options.tmpdir += '/' + str(self.options.port_seed)
+
+ if self.options.trace_rpc:
+ logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
+
+ if self.options.coveragedir:
+ enable_coverage(self.options.coveragedir)
+
+ PortSeed.n = self.options.port_seed
+
+ os.environ['PATH'] = self.options.srcdir+":"+self.options.srcdir+"/qt:"+os.environ['PATH']
+
+ check_json_precision()
+
+ success = False
+ try:
+ if not os.path.isdir(self.options.tmpdir):
+ os.makedirs(self.options.tmpdir)
+ self.setup_chain()
+
+ self.setup_network()
+
+ self.run_test()
+
+ success = True
+
+ except JSONRPCException as e:
+ print("JSONRPC error: "+e.error['message'])
+ traceback.print_tb(sys.exc_info()[2])
+ except AssertionError as e:
+ print("Assertion failed: " + str(e))
+ traceback.print_tb(sys.exc_info()[2])
+ except KeyError as e:
+ print("key not found: "+ str(e))
+ traceback.print_tb(sys.exc_info()[2])
+ except Exception as e:
+ print("Unexpected exception caught during testing: " + repr(e))
+ traceback.print_tb(sys.exc_info()[2])
+ except KeyboardInterrupt as e:
+ print("Exiting after " + repr(e))
+
+ if not self.options.noshutdown:
+ print("Stopping nodes")
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+ else:
+ print("Note: bitcoinds were not stopped and may still be running")
+
+ if not self.options.nocleanup and not self.options.noshutdown and success:
+ print("Cleaning up")
+ shutil.rmtree(self.options.tmpdir)
+ else:
+ print("Not cleaning up dir %s" % self.options.tmpdir)
+
+ if success:
+ print("Tests successful")
+ sys.exit(0)
+ else:
+ print("Failed")
+ sys.exit(1)
+
+
+# Test framework for doing p2p comparison testing, which sets up some bitcoind
+# binaries:
+# 1 binary: test binary
+# 2 binaries: 1 test binary, 1 ref binary
+# n>2 binaries: 1 test binary, n-1 ref binaries
+
+class ComparisonTestFramework(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = True
+
+ def add_options(self, parser):
+ parser.add_option("--testbinary", dest="testbinary",
+ default=os.getenv("BITCOIND", "bitcoind"),
+ help="bitcoind binary to test")
+ parser.add_option("--refbinary", dest="refbinary",
+ default=os.getenv("BITCOIND", "bitcoind"),
+ help="bitcoind binary to use for reference nodes (if any)")
+
+ def setup_network(self):
+ self.nodes = start_nodes(
+ self.num_nodes, self.options.tmpdir,
+ extra_args=[['-debug', '-whitelist=127.0.0.1']] * self.num_nodes,
+ binary=[self.options.testbinary] +
+ [self.options.refbinary]*(self.num_nodes-1))
diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py
new file mode 100644
index 0000000000..32fe79efc3
--- /dev/null
+++ b/qa/rpc-tests/test_framework/util.py
@@ -0,0 +1,641 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#
+# Helpful routines for regression testing
+#
+
+import os
+import sys
+
+from binascii import hexlify, unhexlify
+from base64 import b64encode
+from decimal import Decimal, ROUND_DOWN
+import json
+import http.client
+import random
+import shutil
+import subprocess
+import time
+import re
+import errno
+
+from . import coverage
+from .authproxy import AuthServiceProxy, JSONRPCException
+
+COVERAGE_DIR = None
+
+# The maximum number of nodes a single test can spawn
+MAX_NODES = 8
+# Don't assign rpc or p2p ports lower than this
+PORT_MIN = 11000
+# The number of ports to "reserve" for p2p and rpc, each
+PORT_RANGE = 5000
+
+BITCOIND_PROC_WAIT_TIMEOUT = 60
+
+
+class PortSeed:
+ # Must be initialized with a unique integer for each process
+ n = None
+
+#Set Mocktime default to OFF.
+#MOCKTIME is only needed for scripts that use the
+#cached version of the blockchain. If the cached
+#version of the blockchain is used without MOCKTIME
+#then the mempools will not sync due to IBD.
+MOCKTIME = 0
+
+def enable_mocktime():
+ #For backwared compatibility of the python scripts
+ #with previous versions of the cache, set MOCKTIME
+ #to Jan 1, 2014 + (201 * 10 * 60)
+ global MOCKTIME
+ MOCKTIME = 1388534400 + (201 * 10 * 60)
+
+def disable_mocktime():
+ global MOCKTIME
+ MOCKTIME = 0
+
+def get_mocktime():
+ return MOCKTIME
+
+def enable_coverage(dirname):
+ """Maintain a log of which RPC calls are made during testing."""
+ global COVERAGE_DIR
+ COVERAGE_DIR = dirname
+
+
+def get_rpc_proxy(url, node_number, timeout=None):
+ """
+ Args:
+ url (str): URL of the RPC server to call
+ node_number (int): the node number (or id) that this calls to
+
+ Kwargs:
+ timeout (int): HTTP timeout in seconds
+
+ Returns:
+ AuthServiceProxy. convenience object for making RPC calls.
+
+ """
+ proxy_kwargs = {}
+ if timeout is not None:
+ proxy_kwargs['timeout'] = timeout
+
+ proxy = AuthServiceProxy(url, **proxy_kwargs)
+ proxy.url = url # store URL on proxy for info
+
+ coverage_logfile = coverage.get_filename(
+ COVERAGE_DIR, node_number) if COVERAGE_DIR else None
+
+ return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
+
+
+def p2p_port(n):
+ assert(n <= MAX_NODES)
+ return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
+
+def rpc_port(n):
+ return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
+
+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 count_bytes(hex_string):
+ return len(bytearray.fromhex(hex_string))
+
+def bytes_to_hex_str(byte_str):
+ return hexlify(byte_str).decode('ascii')
+
+def hex_str_to_bytes(hex_str):
+ return unhexlify(hex_str.encode('ascii'))
+
+def str_to_b64str(string):
+ return b64encode(string.encode('utf-8')).decode('ascii')
+
+def sync_blocks(rpc_connections, wait=1, timeout=60):
+ """
+ Wait until everybody has the same tip
+ """
+ while timeout > 0:
+ tips = [ x.getbestblockhash() for x in rpc_connections ]
+ if tips == [ tips[0] ]*len(tips):
+ return True
+ time.sleep(wait)
+ timeout -= wait
+ raise AssertionError("Block sync failed")
+
+def sync_mempools(rpc_connections, wait=1, timeout=60):
+ """
+ Wait until everybody has the same transactions in their memory
+ pools
+ """
+ while timeout > 0:
+ 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):
+ return True
+ time.sleep(wait)
+ timeout -= wait
+ raise AssertionError("Mempool sync failed")
+
+bitcoind_processes = {}
+
+def initialize_datadir(dirname, n):
+ datadir = os.path.join(dirname, "node"+str(n))
+ if not os.path.isdir(datadir):
+ os.makedirs(datadir)
+ rpc_u, rpc_p = rpc_auth_pair(n)
+ with open(os.path.join(datadir, "bitcoin.conf"), 'w') as f:
+ f.write("regtest=1\n")
+ f.write("rpcuser=" + rpc_u + "\n")
+ f.write("rpcpassword=" + rpc_p + "\n")
+ f.write("port="+str(p2p_port(n))+"\n")
+ f.write("rpcport="+str(rpc_port(n))+"\n")
+ f.write("listenonion=0\n")
+ return datadir
+
+def rpc_auth_pair(n):
+ return 'rpcuser💻' + str(n), 'rpcpass🔑' + str(n)
+
+def rpc_url(i, rpchost=None):
+ rpc_u, rpc_p = rpc_auth_pair(i)
+ return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, rpchost or '127.0.0.1', rpc_port(i))
+
+def wait_for_bitcoind_start(process, url, i):
+ '''
+ Wait for bitcoind to start. This means that RPC is accessible and fully initialized.
+ Raise an exception if bitcoind exits during initialization.
+ '''
+ while True:
+ if process.poll() is not None:
+ raise Exception('bitcoind exited with status %i during initialization' % process.returncode)
+ try:
+ rpc = get_rpc_proxy(url, i)
+ blocks = rpc.getblockcount()
+ break # break out of loop on success
+ except IOError as e:
+ if e.errno != errno.ECONNREFUSED: # Port not yet open?
+ raise # unknown IO error
+ except JSONRPCException as e: # Initialization phase
+ if e.error['code'] != -28: # RPC in warmup?
+ raise # unkown JSON RPC exception
+ time.sleep(0.25)
+
+def initialize_chain(test_dir, num_nodes):
+ """
+ Create a cache of a 200-block-long chain (with wallet) for MAX_NODES
+ Afterward, create num_nodes copies from the cache
+ """
+
+ assert num_nodes <= MAX_NODES
+ create_cache = False
+ for i in range(MAX_NODES):
+ if not os.path.isdir(os.path.join('cache', 'node'+str(i))):
+ create_cache = True
+ break
+
+ if create_cache:
+
+ #find and delete old cache directories if any exist
+ for i in range(MAX_NODES):
+ if os.path.isdir(os.path.join("cache","node"+str(i))):
+ shutil.rmtree(os.path.join("cache","node"+str(i)))
+
+ # Create cache directories, run bitcoinds:
+ for i in range(MAX_NODES):
+ datadir=initialize_datadir("cache", i)
+ args = [ os.getenv("BITCOIND", "bitcoind"), "-server", "-keypool=1", "-datadir="+datadir, "-discover=0" ]
+ if i > 0:
+ args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
+ bitcoind_processes[i] = subprocess.Popen(args)
+ if os.getenv("PYTHON_DEBUG", ""):
+ print("initialize_chain: bitcoind started, waiting for RPC to come up")
+ wait_for_bitcoind_start(bitcoind_processes[i], rpc_url(i), i)
+ if os.getenv("PYTHON_DEBUG", ""):
+ print("initialize_chain: RPC succesfully started")
+
+ rpcs = []
+ for i in range(MAX_NODES):
+ try:
+ rpcs.append(get_rpc_proxy(rpc_url(i), i))
+ except:
+ sys.stderr.write("Error connecting to "+url+"\n")
+ sys.exit(1)
+
+ # Create a 200-block-long chain; each of the 4 first nodes
+ # gets 25 mature blocks and 25 immature.
+ # Note: To preserve compatibility with older versions of
+ # initialize_chain, only 4 nodes will generate coins.
+ #
+ # blocks are created with timestamps 10 minutes apart
+ # starting from 2010 minutes in the past
+ enable_mocktime()
+ block_time = get_mocktime() - (201 * 10 * 60)
+ for i in range(2):
+ for peer in range(4):
+ for j in range(25):
+ set_node_times(rpcs, block_time)
+ rpcs[peer].generate(1)
+ block_time += 10*60
+ # Must sync before next peer starts generating blocks
+ sync_blocks(rpcs)
+
+ # Shut them down, and clean up cache directories:
+ stop_nodes(rpcs)
+ wait_bitcoinds()
+ disable_mocktime()
+ for i in range(MAX_NODES):
+ os.remove(log_filename("cache", i, "debug.log"))
+ os.remove(log_filename("cache", i, "db.log"))
+ os.remove(log_filename("cache", i, "peers.dat"))
+ os.remove(log_filename("cache", i, "fee_estimates.dat"))
+
+ for i in range(num_nodes):
+ 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)
+ initialize_datadir(test_dir, i) # Overwrite port/rpcport in bitcoin.conf
+
+def initialize_chain_clean(test_dir, num_nodes):
+ """
+ Create an empty blockchain and num_nodes wallets.
+ Useful if a test case wants complete control over initialization.
+ """
+ for i in range(num_nodes):
+ datadir=initialize_datadir(test_dir, i)
+
+
+def _rpchost_to_args(rpchost):
+ '''Convert optional IP:port spec to rpcconnect/rpcport args'''
+ if rpchost is None:
+ return []
+
+ match = re.match('(\[[0-9a-fA-f:]+\]|[^:]+)(?::([0-9]+))?$', rpchost)
+ if not match:
+ raise ValueError('Invalid RPC host spec ' + rpchost)
+
+ rpcconnect = match.group(1)
+ rpcport = match.group(2)
+
+ if rpcconnect.startswith('['): # remove IPv6 [...] wrapping
+ rpcconnect = rpcconnect[1:-1]
+
+ rv = ['-rpcconnect=' + rpcconnect]
+ if rpcport:
+ rv += ['-rpcport=' + rpcport]
+ return rv
+
+def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None):
+ """
+ Start a bitcoind and return RPC connection to it
+ """
+ datadir = os.path.join(dirname, "node"+str(i))
+ if binary is None:
+ binary = os.getenv("BITCOIND", "bitcoind")
+ args = [ binary, "-datadir="+datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-mocktime="+str(get_mocktime()) ]
+ if extra_args is not None: args.extend(extra_args)
+ bitcoind_processes[i] = subprocess.Popen(args)
+ if os.getenv("PYTHON_DEBUG", ""):
+ print("start_node: bitcoind started, waiting for RPC to come up")
+ url = rpc_url(i, rpchost)
+ wait_for_bitcoind_start(bitcoind_processes[i], url, i)
+ if os.getenv("PYTHON_DEBUG", ""):
+ print("start_node: RPC succesfully started")
+ proxy = get_rpc_proxy(url, i, timeout=timewait)
+
+ if COVERAGE_DIR:
+ coverage.write_all_rpc_commands(COVERAGE_DIR, proxy)
+
+ return proxy
+
+def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, binary=None):
+ """
+ Start multiple bitcoinds, return RPC connections to them
+ """
+ if extra_args is None: extra_args = [ None for _ in range(num_nodes) ]
+ if binary is None: binary = [ None for _ in range(num_nodes) ]
+ rpcs = []
+ try:
+ for i in range(num_nodes):
+ rpcs.append(start_node(i, dirname, extra_args[i], rpchost, binary=binary[i]))
+ except: # If one node failed to start, stop the others
+ stop_nodes(rpcs)
+ raise
+ return rpcs
+
+def log_filename(dirname, n_node, logname):
+ return os.path.join(dirname, "node"+str(n_node), "regtest", logname)
+
+def stop_node(node, i):
+ try:
+ node.stop()
+ except http.client.CannotSendRequest as e:
+ print("WARN: Unable to stop node: " + repr(e))
+ bitcoind_processes[i].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
+ del bitcoind_processes[i]
+
+def stop_nodes(nodes):
+ for node in nodes:
+ try:
+ node.stop()
+ except http.client.CannotSendRequest as e:
+ print("WARN: Unable to stop node: " + repr(e))
+ del nodes[:] # Emptying array closes connections as a side effect
+
+def set_node_times(nodes, t):
+ for node in nodes:
+ node.setmocktime(t)
+
+def wait_bitcoinds():
+ # Wait for all bitcoinds to cleanly exit
+ for bitcoind in bitcoind_processes.values():
+ bitcoind.wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
+ bitcoind_processes.clear()
+
+def connect_nodes(from_connection, node_num):
+ ip_port = "127.0.0.1:"+str(p2p_port(node_num))
+ from_connection.addnode(ip_port, "onetry")
+ # poll until version handshake complete to avoid race conditions
+ # with transaction relaying
+ while any(peer['version'] == 0 for peer in from_connection.getpeerinfo()):
+ time.sleep(0.1)
+
+def connect_nodes_bi(nodes, a, b):
+ connect_nodes(nodes[a], b)
+ connect_nodes(nodes[b], a)
+
+def find_output(node, txid, amount):
+ """
+ Return index to output of txid with value amount
+ Raises exception if there is none.
+ """
+ txdata = node.getrawtransaction(txid, 1)
+ for i in range(len(txdata["vout"])):
+ if txdata["vout"][i]["value"] == amount:
+ return i
+ raise RuntimeError("find_output txid %s : %s not found"%(txid,str(amount)))
+
+
+def gather_inputs(from_node, amount_needed, confirmations_required=1):
+ """
+ Return a random set of unspent txouts that are enough to pay amount_needed
+ """
+ assert(confirmations_required >=0)
+ utxo = from_node.listunspent(confirmations_required)
+ random.shuffle(utxo)
+ inputs = []
+ total_in = Decimal("0.00000000")
+ while total_in < amount_needed and len(utxo) > 0:
+ t = utxo.pop()
+ total_in += t["amount"]
+ inputs.append({ "txid" : t["txid"], "vout" : t["vout"], "address" : t["address"] } )
+ if total_in < amount_needed:
+ raise RuntimeError("Insufficient funds: need %d, have %d"%(amount_needed, total_in))
+ return (total_in, inputs)
+
+def make_change(from_node, amount_in, amount_out, fee):
+ """
+ Create change output(s), return them
+ """
+ outputs = {}
+ amount = amount_out+fee
+ change = amount_in - amount
+ if change > amount*2:
+ # Create an extra change output to break up big inputs
+ change_address = from_node.getnewaddress()
+ # Split change in two, being careful of rounding:
+ outputs[change_address] = Decimal(change/2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
+ change = amount_in - amount - outputs[change_address]
+ if change > 0:
+ outputs[from_node.getnewaddress()] = change
+ return outputs
+
+def send_zeropri_transaction(from_node, to_node, amount, fee):
+ """
+ Create&broadcast a zero-priority transaction.
+ Returns (txid, hex-encoded-txdata)
+ Ensures transaction is zero-priority by first creating a send-to-self,
+ then using its output
+ """
+
+ # Create a send-to-self with confirmed inputs:
+ self_address = from_node.getnewaddress()
+ (total_in, inputs) = gather_inputs(from_node, amount+fee*2)
+ outputs = make_change(from_node, total_in, amount+fee, fee)
+ outputs[self_address] = float(amount+fee)
+
+ self_rawtx = from_node.createrawtransaction(inputs, outputs)
+ self_signresult = from_node.signrawtransaction(self_rawtx)
+ self_txid = from_node.sendrawtransaction(self_signresult["hex"], True)
+
+ vout = find_output(from_node, self_txid, amount+fee)
+ # Now immediately spend the output to create a 1-input, 1-output
+ # zero-priority transaction:
+ inputs = [ { "txid" : self_txid, "vout" : vout } ]
+ outputs = { to_node.getnewaddress() : float(amount) }
+
+ rawtx = from_node.createrawtransaction(inputs, outputs)
+ signresult = from_node.signrawtransaction(rawtx)
+ txid = from_node.sendrawtransaction(signresult["hex"], True)
+
+ return (txid, signresult["hex"])
+
+def random_zeropri_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
+ """
+ Create a random zero-priority transaction.
+ Returns (txid, hex-encoded-transaction-data, fee)
+ """
+ from_node = random.choice(nodes)
+ to_node = random.choice(nodes)
+ fee = min_fee + fee_increment*random.randint(0,fee_variants)
+ (txid, txhex) = send_zeropri_transaction(from_node, to_node, amount, fee)
+ return (txid, txhex, fee)
+
+def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
+ """
+ Create a random transaction.
+ Returns (txid, hex-encoded-transaction-data, fee)
+ """
+ from_node = random.choice(nodes)
+ to_node = random.choice(nodes)
+ fee = min_fee + fee_increment*random.randint(0,fee_variants)
+
+ (total_in, inputs) = gather_inputs(from_node, amount+fee)
+ outputs = make_change(from_node, total_in, amount, fee)
+ outputs[to_node.getnewaddress()] = float(amount)
+
+ rawtx = from_node.createrawtransaction(inputs, outputs)
+ signresult = from_node.signrawtransaction(rawtx)
+ txid = from_node.sendrawtransaction(signresult["hex"], True)
+
+ return (txid, signresult["hex"], fee)
+
+def assert_fee_amount(fee, tx_size, fee_per_kB):
+ """Assert the fee was in range"""
+ target_fee = tx_size * fee_per_kB / 1000
+ if fee < target_fee:
+ raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)"%(str(fee), str(target_fee)))
+ # allow the wallet's estimation to be at most 2 bytes off
+ if fee > (tx_size + 2) * fee_per_kB / 1000:
+ raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)"%(str(fee), str(target_fee)))
+
+def assert_equal(thing1, thing2):
+ if thing1 != thing2:
+ raise AssertionError("%s != %s"%(str(thing1),str(thing2)))
+
+def assert_greater_than(thing1, thing2):
+ if thing1 <= thing2:
+ raise AssertionError("%s <= %s"%(str(thing1),str(thing2)))
+
+def assert_raises(exc, fun, *args, **kwds):
+ try:
+ fun(*args, **kwds)
+ except exc:
+ pass
+ except Exception as e:
+ raise AssertionError("Unexpected exception raised: "+type(e).__name__)
+ else:
+ raise AssertionError("No exception raised")
+
+def assert_is_hex_string(string):
+ try:
+ int(string, 16)
+ except Exception as e:
+ raise AssertionError(
+ "Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
+
+def assert_is_hash_string(string, length=64):
+ if not isinstance(string, str):
+ raise AssertionError("Expected a string, got type %r" % type(string))
+ elif length and len(string) != length:
+ raise AssertionError(
+ "String of length %d expected; got %d" % (length, len(string)))
+ elif not re.match('[abcdef0-9]+$', string):
+ raise AssertionError(
+ "String %r contains invalid characters for a hash." % string)
+
+def assert_array_result(object_array, to_match, expected, should_not_find = False):
+ """
+ Pass in array of JSON objects, a dictionary with key/value pairs
+ to match against, and another dictionary with expected key/value
+ pairs.
+ If the should_not_find flag is true, to_match should not be found
+ in object_array
+ """
+ if should_not_find == True:
+ assert_equal(expected, { })
+ 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
+ elif should_not_find == True:
+ num_matched = num_matched+1
+ 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 and should_not_find != True:
+ raise AssertionError("No objects matched %s"%(str(to_match)))
+ if num_matched > 0 and should_not_find == True:
+ raise AssertionError("Objects were found %s"%(str(to_match)))
+
+def satoshi_round(amount):
+ return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
+
+# Helper to create at least "count" utxos
+# Pass in a fee that is sufficient for relay and mining new transactions.
+def create_confirmed_utxos(fee, node, count):
+ node.generate(int(0.5*count)+101)
+ utxos = node.listunspent()
+ iterations = count - len(utxos)
+ addr1 = node.getnewaddress()
+ addr2 = node.getnewaddress()
+ if iterations <= 0:
+ return utxos
+ for i in range(iterations):
+ t = utxos.pop()
+ inputs = []
+ inputs.append({ "txid" : t["txid"], "vout" : t["vout"]})
+ outputs = {}
+ send_value = t['amount'] - fee
+ outputs[addr1] = satoshi_round(send_value/2)
+ outputs[addr2] = satoshi_round(send_value/2)
+ raw_tx = node.createrawtransaction(inputs, outputs)
+ signed_tx = node.signrawtransaction(raw_tx)["hex"]
+ txid = node.sendrawtransaction(signed_tx)
+
+ while (node.getmempoolinfo()['size'] > 0):
+ node.generate(1)
+
+ utxos = node.listunspent()
+ assert(len(utxos) >= count)
+ return utxos
+
+# Create large OP_RETURN txouts that can be appended to a transaction
+# to make it large (helper for constructing large transactions).
+def gen_return_txouts():
+ # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
+ # So we have big transactions (and therefore can't fit very many into each block)
+ # create one script_pubkey
+ script_pubkey = "6a4d0200" #OP_RETURN OP_PUSH2 512 bytes
+ for i in range (512):
+ script_pubkey = script_pubkey + "01"
+ # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
+ txouts = "81"
+ for k in range(128):
+ # add txout value
+ txouts = txouts + "0000000000000000"
+ # add length of script_pubkey
+ txouts = txouts + "fd0402"
+ # add script_pubkey
+ txouts = txouts + script_pubkey
+ return txouts
+
+def create_tx(node, coinbase, to_address, amount):
+ inputs = [{ "txid" : coinbase, "vout" : 0}]
+ outputs = { to_address : amount }
+ rawtx = node.createrawtransaction(inputs, outputs)
+ signresult = node.signrawtransaction(rawtx)
+ assert_equal(signresult["complete"], True)
+ return signresult["hex"]
+
+# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
+# transaction to make it large. See gen_return_txouts() above.
+def create_lots_of_big_transactions(node, txouts, utxos, fee):
+ addr = node.getnewaddress()
+ txids = []
+ for i in range(len(utxos)):
+ t = utxos.pop()
+ inputs = []
+ inputs.append({ "txid" : t["txid"], "vout" : t["vout"]})
+ outputs = {}
+ send_value = t['amount'] - fee
+ outputs[addr] = satoshi_round(send_value)
+ rawtx = node.createrawtransaction(inputs, outputs)
+ newtx = rawtx[0:92]
+ newtx = newtx + txouts
+ newtx = newtx + rawtx[94:]
+ signresult = node.signrawtransaction(newtx, None, None, "NONE")
+ txid = node.sendrawtransaction(signresult["hex"], True)
+ txids.append(txid)
+ return txids
+
+def get_bip9_status(node, key):
+ info = node.getblockchaininfo()
+ return info['bip9_softforks'][key]
diff --git a/qa/rpc-tests/txn_clone.py b/qa/rpc-tests/txn_clone.py
new file mode 100755
index 0000000000..22f850ece6
--- /dev/null
+++ b/qa/rpc-tests/txn_clone.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test proper accounting with an equivalent malleability clone
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+class TxnMallTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.setup_clean_chain = False
+
+ def add_options(self, parser):
+ parser.add_option("--mineblock", dest="mine_block", default=False, action="store_true",
+ help="Test double-spend of 1-confirmed transaction")
+
+ def setup_network(self):
+ # Start with split network:
+ return super(TxnMallTest, self).setup_network(True)
+
+ def run_test(self):
+ # All nodes should start with 1,250 BTC:
+ starting_balance = 1250
+ for i in range(4):
+ assert_equal(self.nodes[i].getbalance(), starting_balance)
+ self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress!
+
+ # Assign coins to foo and bar accounts:
+ self.nodes[0].settxfee(.001)
+
+ node0_address_foo = self.nodes[0].getnewaddress("foo")
+ fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219)
+ fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
+
+ node0_address_bar = self.nodes[0].getnewaddress("bar")
+ fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29)
+ fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid)
+
+ assert_equal(self.nodes[0].getbalance(""),
+ starting_balance - 1219 - 29 + fund_foo_tx["fee"] + fund_bar_tx["fee"])
+
+ # Coins are sent to node1_address
+ node1_address = self.nodes[1].getnewaddress("from0")
+
+ # Send tx1, and another transaction tx2 that won't be cloned
+ txid1 = self.nodes[0].sendfrom("foo", node1_address, 40, 0)
+ txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0)
+
+ # Construct a clone of tx1, to be malleated
+ rawtx1 = self.nodes[0].getrawtransaction(txid1,1)
+ clone_inputs = [{"txid":rawtx1["vin"][0]["txid"],"vout":rawtx1["vin"][0]["vout"]}]
+ clone_outputs = {rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]:rawtx1["vout"][0]["value"],
+ rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]:rawtx1["vout"][1]["value"]}
+ clone_locktime = rawtx1["locktime"]
+ clone_raw = self.nodes[0].createrawtransaction(clone_inputs, clone_outputs, clone_locktime)
+
+ # createrawtransaction randomizes the order of its outputs, so swap them if necessary.
+ # output 0 is at version+#inputs+input+sigstub+sequence+#outputs
+ # 40 BTC serialized is 00286bee00000000
+ pos0 = 2*(4+1+36+1+4+1)
+ hex40 = "00286bee00000000"
+ output_len = 16 + 2 + 2 * int("0x" + clone_raw[pos0 + 16 : pos0 + 16 + 2], 0)
+ if (rawtx1["vout"][0]["value"] == 40 and clone_raw[pos0 : pos0 + 16] != hex40 or
+ rawtx1["vout"][0]["value"] != 40 and clone_raw[pos0 : pos0 + 16] == hex40):
+ output0 = clone_raw[pos0 : pos0 + output_len]
+ output1 = clone_raw[pos0 + output_len : pos0 + 2 * output_len]
+ clone_raw = clone_raw[:pos0] + output1 + output0 + clone_raw[pos0 + 2 * output_len:]
+
+ # Use a different signature hash type to sign. This creates an equivalent but malleated clone.
+ # Don't send the clone anywhere yet
+ tx1_clone = self.nodes[0].signrawtransaction(clone_raw, None, None, "ALL|ANYONECANPAY")
+ assert_equal(tx1_clone["complete"], True)
+
+ # Have node0 mine a block, if requested:
+ if (self.options.mine_block):
+ self.nodes[0].generate(1)
+ sync_blocks(self.nodes[0:2])
+
+ tx1 = self.nodes[0].gettransaction(txid1)
+ tx2 = self.nodes[0].gettransaction(txid2)
+
+ # Node0's balance should be starting balance, plus 50BTC for another
+ # matured block, minus tx1 and tx2 amounts, and minus transaction fees:
+ expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]
+ if self.options.mine_block: expected += 50
+ expected += tx1["amount"] + tx1["fee"]
+ expected += tx2["amount"] + tx2["fee"]
+ assert_equal(self.nodes[0].getbalance(), expected)
+
+ # foo and bar accounts should be debited:
+ assert_equal(self.nodes[0].getbalance("foo", 0), 1219 + tx1["amount"] + tx1["fee"])
+ assert_equal(self.nodes[0].getbalance("bar", 0), 29 + tx2["amount"] + tx2["fee"])
+
+ if self.options.mine_block:
+ assert_equal(tx1["confirmations"], 1)
+ assert_equal(tx2["confirmations"], 1)
+ # Node1's "from0" balance should be both transaction amounts:
+ assert_equal(self.nodes[1].getbalance("from0"), -(tx1["amount"] + tx2["amount"]))
+ else:
+ assert_equal(tx1["confirmations"], 0)
+ assert_equal(tx2["confirmations"], 0)
+
+ # Send clone and its parent to miner
+ self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
+ txid1_clone = self.nodes[2].sendrawtransaction(tx1_clone["hex"])
+ # ... mine a block...
+ self.nodes[2].generate(1)
+
+ # Reconnect the split network, and sync chain:
+ connect_nodes(self.nodes[1], 2)
+ self.nodes[2].sendrawtransaction(fund_bar_tx["hex"])
+ self.nodes[2].sendrawtransaction(tx2["hex"])
+ self.nodes[2].generate(1) # Mine another block to make sure we sync
+ sync_blocks(self.nodes)
+
+ # Re-fetch transaction info:
+ tx1 = self.nodes[0].gettransaction(txid1)
+ tx1_clone = self.nodes[0].gettransaction(txid1_clone)
+ tx2 = self.nodes[0].gettransaction(txid2)
+
+ # Verify expected confirmations
+ assert_equal(tx1["confirmations"], -2)
+ assert_equal(tx1_clone["confirmations"], 2)
+ assert_equal(tx2["confirmations"], 1)
+
+ # Check node0's total balance; should be same as before the clone, + 100 BTC for 2 matured,
+ # less possible orphaned matured subsidy
+ expected += 100
+ if (self.options.mine_block):
+ expected -= 50
+ assert_equal(self.nodes[0].getbalance(), expected)
+ assert_equal(self.nodes[0].getbalance("*", 0), expected)
+
+ # Check node0's individual account balances.
+ # "foo" should have been debited by the equivalent clone of tx1
+ assert_equal(self.nodes[0].getbalance("foo"), 1219 + tx1["amount"] + tx1["fee"])
+ # "bar" should have been debited by (possibly unconfirmed) tx2
+ assert_equal(self.nodes[0].getbalance("bar", 0), 29 + tx2["amount"] + tx2["fee"])
+ # "" should have starting balance, less funding txes, plus subsidies
+ assert_equal(self.nodes[0].getbalance("", 0), starting_balance
+ - 1219
+ + fund_foo_tx["fee"]
+ - 29
+ + fund_bar_tx["fee"]
+ + 100)
+
+ # Node1's "from0" account balance
+ assert_equal(self.nodes[1].getbalance("from0", 0), -(tx1["amount"] + tx2["amount"]))
+
+if __name__ == '__main__':
+ TxnMallTest().main()
+
diff --git a/qa/rpc-tests/txn_doublespend.py b/qa/rpc-tests/txn_doublespend.py
index 942d9fc66c..84944c3c19 100755
--- a/qa/rpc-tests/txn_doublespend.py
+++ b/qa/rpc-tests/txn_doublespend.py
@@ -1,21 +1,22 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
-# Test proper accounting with malleable transactions
+# Test proper accounting with a double-spend conflict
#
-from test_framework import BitcoinTestFramework
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from decimal import Decimal
-from util import *
-import os
-import shutil
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
class TxnMallTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.setup_clean_chain = False
+
def add_options(self, parser):
parser.add_option("--mineblock", dest="mine_block", default=False, action="store_true",
help="Test double-spend of 1-confirmed transaction")
@@ -32,49 +33,61 @@ class TxnMallTest(BitcoinTestFramework):
self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress!
# Assign coins to foo and bar accounts:
- self.nodes[0].move("", "foo", 1220)
- self.nodes[0].move("", "bar", 30)
- assert_equal(self.nodes[0].getbalance(""), 0)
+ node0_address_foo = self.nodes[0].getnewaddress("foo")
+ fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219)
+ fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
+
+ node0_address_bar = self.nodes[0].getnewaddress("bar")
+ fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29)
+ fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid)
+
+ assert_equal(self.nodes[0].getbalance(""),
+ starting_balance - 1219 - 29 + fund_foo_tx["fee"] + fund_bar_tx["fee"])
# Coins are sent to node1_address
node1_address = self.nodes[1].getnewaddress("from0")
- # First: use raw transaction API to send 1210 BTC to node1_address,
+ # First: use raw transaction API to send 1240 BTC to node1_address,
# but don't broadcast:
- (total_in, inputs) = gather_inputs(self.nodes[0], 1210)
- change_address = self.nodes[0].getnewaddress("foo")
+ doublespend_fee = Decimal('-.02')
+ rawtx_input_0 = {}
+ rawtx_input_0["txid"] = fund_foo_txid
+ rawtx_input_0["vout"] = find_output(self.nodes[0], fund_foo_txid, 1219)
+ rawtx_input_1 = {}
+ rawtx_input_1["txid"] = fund_bar_txid
+ rawtx_input_1["vout"] = find_output(self.nodes[0], fund_bar_txid, 29)
+ inputs = [rawtx_input_0, rawtx_input_1]
+ change_address = self.nodes[0].getnewaddress()
outputs = {}
- outputs[change_address] = 40
- outputs[node1_address] = 1210
+ outputs[node1_address] = 1240
+ outputs[change_address] = 1248 - 1240 + doublespend_fee
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
doublespend = self.nodes[0].signrawtransaction(rawtx)
assert_equal(doublespend["complete"], True)
- # Create two transaction from node[0] to node[1]; the
- # second must spend change from the first because the first
- # spends all mature inputs:
- txid1 = self.nodes[0].sendfrom("foo", node1_address, 1210, 0)
+ # Create two spends using 1 50 BTC coin each
+ txid1 = self.nodes[0].sendfrom("foo", node1_address, 40, 0)
txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0)
# Have node0 mine a block:
if (self.options.mine_block):
- self.nodes[0].setgenerate(True, 1)
+ self.nodes[0].generate(1)
sync_blocks(self.nodes[0:2])
tx1 = self.nodes[0].gettransaction(txid1)
tx2 = self.nodes[0].gettransaction(txid2)
# Node0's balance should be starting balance, plus 50BTC for another
- # matured block, minus 1210, minus 20, and minus transaction fees:
- expected = starting_balance
+ # matured block, minus 40, minus 20, and minus transaction fees:
+ expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]
if self.options.mine_block: expected += 50
expected += tx1["amount"] + tx1["fee"]
expected += tx2["amount"] + tx2["fee"]
assert_equal(self.nodes[0].getbalance(), expected)
# foo and bar accounts should be debited:
- assert_equal(self.nodes[0].getbalance("foo"), 1220+tx1["amount"]+tx1["fee"])
- assert_equal(self.nodes[0].getbalance("bar"), 30+tx2["amount"]+tx2["fee"])
+ assert_equal(self.nodes[0].getbalance("foo", 0), 1219+tx1["amount"]+tx1["fee"])
+ assert_equal(self.nodes[0].getbalance("bar", 0), 29+tx2["amount"]+tx2["fee"])
if self.options.mine_block:
assert_equal(tx1["confirmations"], 1)
@@ -85,36 +98,50 @@ class TxnMallTest(BitcoinTestFramework):
assert_equal(tx1["confirmations"], 0)
assert_equal(tx2["confirmations"], 0)
- # Now give doublespend to miner:
- mutated_txid = self.nodes[2].sendrawtransaction(doublespend["hex"])
+ # Now give doublespend and its parents to miner:
+ self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
+ self.nodes[2].sendrawtransaction(fund_bar_tx["hex"])
+ doublespend_txid = self.nodes[2].sendrawtransaction(doublespend["hex"])
# ... mine a block...
- self.nodes[2].setgenerate(True, 1)
+ self.nodes[2].generate(1)
# Reconnect the split network, and sync chain:
connect_nodes(self.nodes[1], 2)
- self.nodes[2].setgenerate(True, 1) # Mine another block to make sure we sync
+ self.nodes[2].generate(1) # Mine another block to make sure we sync
sync_blocks(self.nodes)
+ assert_equal(self.nodes[0].gettransaction(doublespend_txid)["confirmations"], 2)
# Re-fetch transaction info:
tx1 = self.nodes[0].gettransaction(txid1)
tx2 = self.nodes[0].gettransaction(txid2)
-
+
# Both transactions should be conflicted
- assert_equal(tx1["confirmations"], -1)
- assert_equal(tx2["confirmations"], -1)
+ assert_equal(tx1["confirmations"], -2)
+ assert_equal(tx2["confirmations"], -2)
# Node0's total balance should be starting balance, plus 100BTC for
- # two more matured blocks, minus 1210 for the double-spend:
- expected = starting_balance + 100 - 1210
+ # two more matured blocks, minus 1240 for the double-spend, plus fees (which are
+ # negative):
+ expected = starting_balance + 100 - 1240 + fund_foo_tx["fee"] + fund_bar_tx["fee"] + doublespend_fee
assert_equal(self.nodes[0].getbalance(), expected)
assert_equal(self.nodes[0].getbalance("*"), expected)
- # foo account should be debited, but bar account should not:
- assert_equal(self.nodes[0].getbalance("foo"), 1220-1210)
- assert_equal(self.nodes[0].getbalance("bar"), 30)
-
- # Node1's "from" account balance should be just the mutated send:
- assert_equal(self.nodes[1].getbalance("from0"), 1210)
+ # Final "" balance is starting_balance - amount moved to accounts - doublespend + subsidies +
+ # fees (which are negative)
+ assert_equal(self.nodes[0].getbalance("foo"), 1219)
+ assert_equal(self.nodes[0].getbalance("bar"), 29)
+ assert_equal(self.nodes[0].getbalance(""), starting_balance
+ -1219
+ - 29
+ -1240
+ + 100
+ + fund_foo_tx["fee"]
+ + fund_bar_tx["fee"]
+ + doublespend_fee)
+
+ # Node1's "from0" account balance should be just the doublespend:
+ assert_equal(self.nodes[1].getbalance("from0"), 1240)
if __name__ == '__main__':
TxnMallTest().main()
+
diff --git a/qa/rpc-tests/util.py b/qa/rpc-tests/util.py
deleted file mode 100644
index ec65f783e8..0000000000
--- a/qa/rpc-tests/util.py
+++ /dev/null
@@ -1,343 +0,0 @@
-# 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.
-#
-# 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, ROUND_DOWN
-import json
-import random
-import shutil
-import subprocess
-import time
-import re
-
-from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
-from util import *
-
-def p2p_port(n):
- return 11000 + n + os.getpid()%999
-def rpc_port(n):
- return 12000 + n + os.getpid()%999
-
-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)
-
-bitcoind_processes = {}
-
-def initialize_datadir(dirname, n):
- datadir = os.path.join(dirname, "node"+str(n))
- if not os.path.isdir(datadir):
- 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(p2p_port(n))+"\n");
- f.write("rpcport="+str(rpc_port(n))+"\n");
- return datadir
-
-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")):
- devnull = open("/dev/null", "w+")
- # Create cache directories, run bitcoinds:
- for i in range(4):
- datadir=initialize_datadir("cache", i)
- args = [ os.getenv("BITCOIND", "bitcoind"), "-keypool=1", "-datadir="+datadir, "-discover=0" ]
- if i > 0:
- args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
- bitcoind_processes[i] = subprocess.Popen(args)
- subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir,
- "-rpcwait", "getblockcount"], stdout=devnull)
- devnull.close()
- rpcs = []
- for i in range(4):
- try:
- url = "http://rt:rt@127.0.0.1:%d"%(rpc_port(i),)
- rpcs.append(AuthServiceProxy(url))
- except:
- sys.stderr.write("Error connecting to "+url+"\n")
- sys.exit(1)
-
- # Create a 200-block-long chain; each of the 4 nodes
- # gets 25 mature blocks and 25 immature.
- # blocks are created with timestamps 10 minutes apart, starting
- # at 1 Jan 2014
- block_time = 1388534400
- for i in range(2):
- for peer in range(4):
- for j in range(25):
- set_node_times(rpcs, block_time)
- rpcs[peer].setgenerate(True, 1)
- block_time += 10*60
- # Must sync before next peer starts generating blocks
- sync_blocks(rpcs)
-
- # Shut them down, and clean up cache directories:
- stop_nodes(rpcs)
- wait_bitcoinds()
- for i in range(4):
- os.remove(log_filename("cache", i, "debug.log"))
- os.remove(log_filename("cache", i, "db.log"))
- os.remove(log_filename("cache", i, "peers.dat"))
- os.remove(log_filename("cache", i, "fee_estimates.dat"))
-
- 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)
- initialize_datadir(test_dir, i) # Overwrite port/rpcport in bitcoin.conf
-
-def initialize_chain_clean(test_dir, num_nodes):
- """
- Create an empty blockchain and num_nodes wallets.
- Useful if a test case wants complete control over initialization.
- """
- for i in range(num_nodes):
- datadir=initialize_datadir(test_dir, i)
-
-
-def _rpchost_to_args(rpchost):
- '''Convert optional IP:port spec to rpcconnect/rpcport args'''
- if rpchost is None:
- return []
-
- match = re.match('(\[[0-9a-fA-f:]+\]|[^:]+)(?::([0-9]+))?$', rpchost)
- if not match:
- raise ValueError('Invalid RPC host spec ' + rpchost)
-
- rpcconnect = match.group(1)
- rpcport = match.group(2)
-
- if rpcconnect.startswith('['): # remove IPv6 [...] wrapping
- rpcconnect = rpcconnect[1:-1]
-
- rv = ['-rpcconnect=' + rpcconnect]
- if rpcport:
- rv += ['-rpcport=' + rpcport]
- return rv
-
-def start_node(i, dirname, extra_args=None, rpchost=None):
- """
- Start a bitcoind and return RPC connection to it
- """
- datadir = os.path.join(dirname, "node"+str(i))
- args = [ os.getenv("BITCOIND", "bitcoind"), "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ]
- if extra_args is not None: args.extend(extra_args)
- bitcoind_processes[i] = subprocess.Popen(args)
- devnull = open("/dev/null", "w+")
- subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir] +
- _rpchost_to_args(rpchost) +
- ["-rpcwait", "getblockcount"], stdout=devnull)
- devnull.close()
- url = "http://rt:rt@%s:%d" % (rpchost or '127.0.0.1', rpc_port(i))
- proxy = AuthServiceProxy(url)
- proxy.url = url # store URL on proxy for info
- return proxy
-
-def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None):
- """
- Start multiple bitcoinds, return RPC connections to them
- """
- if extra_args is None: extra_args = [ None for i in range(num_nodes) ]
- return [ start_node(i, dirname, extra_args[i], rpchost) for i in range(num_nodes) ]
-
-def log_filename(dirname, n_node, logname):
- return os.path.join(dirname, "node"+str(n_node), "regtest", logname)
-
-def stop_node(node, i):
- node.stop()
- bitcoind_processes[i].wait()
- del bitcoind_processes[i]
-
-def stop_nodes(nodes):
- for node in nodes:
- node.stop()
- del nodes[:] # Emptying array closes connections as a side effect
-
-def set_node_times(nodes, t):
- for node in nodes:
- node.setmocktime(t)
-
-def wait_bitcoinds():
- # Wait for all bitcoinds to cleanly exit
- for bitcoind in bitcoind_processes.values():
- bitcoind.wait()
- bitcoind_processes.clear()
-
-def connect_nodes(from_connection, node_num):
- ip_port = "127.0.0.1:"+str(p2p_port(node_num))
- from_connection.addnode(ip_port, "onetry")
- # poll until version handshake complete to avoid race conditions
- # with transaction relaying
- while any(peer['version'] == 0 for peer in from_connection.getpeerinfo()):
- time.sleep(0.1)
-
-def connect_nodes_bi(nodes, a, b):
- connect_nodes(nodes[a], b)
- connect_nodes(nodes[b], a)
-
-def find_output(node, txid, amount):
- """
- Return index to output of txid with value amount
- Raises exception if there is none.
- """
- txdata = node.getrawtransaction(txid, 1)
- for i in range(len(txdata["vout"])):
- if txdata["vout"][i]["value"] == amount:
- return i
- raise RuntimeError("find_output txid %s : %s not found"%(txid,str(amount)))
-
-
-def gather_inputs(from_node, amount_needed, confirmations_required=1):
- """
- Return a random set of unspent txouts that are enough to pay amount_needed
- """
- assert(confirmations_required >=0)
- utxo = from_node.listunspent(confirmations_required)
- random.shuffle(utxo)
- inputs = []
- total_in = Decimal("0.00000000")
- while total_in < amount_needed and len(utxo) > 0:
- t = utxo.pop()
- total_in += t["amount"]
- inputs.append({ "txid" : t["txid"], "vout" : t["vout"], "address" : t["address"] } )
- if total_in < amount_needed:
- raise RuntimeError("Insufficient funds: need %d, have %d"%(amount_needed, total_in))
- return (total_in, inputs)
-
-def make_change(from_node, amount_in, amount_out, fee):
- """
- Create change output(s), return them
- """
- outputs = {}
- amount = amount_out+fee
- change = amount_in - amount
- if change > amount*2:
- # Create an extra change output to break up big inputs
- change_address = from_node.getnewaddress()
- # Split change in two, being careful of rounding:
- outputs[change_address] = Decimal(change/2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
- change = amount_in - amount - outputs[change_address]
- if change > 0:
- outputs[from_node.getnewaddress()] = change
- return outputs
-
-def send_zeropri_transaction(from_node, to_node, amount, fee):
- """
- Create&broadcast a zero-priority transaction.
- Returns (txid, hex-encoded-txdata)
- Ensures transaction is zero-priority by first creating a send-to-self,
- then using it's output
- """
-
- # Create a send-to-self with confirmed inputs:
- self_address = from_node.getnewaddress()
- (total_in, inputs) = gather_inputs(from_node, amount+fee*2)
- outputs = make_change(from_node, total_in, amount+fee, fee)
- outputs[self_address] = float(amount+fee)
-
- self_rawtx = from_node.createrawtransaction(inputs, outputs)
- self_signresult = from_node.signrawtransaction(self_rawtx)
- self_txid = from_node.sendrawtransaction(self_signresult["hex"], True)
-
- vout = find_output(from_node, self_txid, amount+fee)
- # Now immediately spend the output to create a 1-input, 1-output
- # zero-priority transaction:
- inputs = [ { "txid" : self_txid, "vout" : vout } ]
- outputs = { to_node.getnewaddress() : float(amount) }
-
- rawtx = from_node.createrawtransaction(inputs, outputs)
- signresult = from_node.signrawtransaction(rawtx)
- txid = from_node.sendrawtransaction(signresult["hex"], True)
-
- return (txid, signresult["hex"])
-
-def random_zeropri_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
- """
- Create a random zero-priority transaction.
- Returns (txid, hex-encoded-transaction-data, fee)
- """
- from_node = random.choice(nodes)
- to_node = random.choice(nodes)
- fee = min_fee + fee_increment*random.randint(0,fee_variants)
- (txid, txhex) = send_zeropri_transaction(from_node, to_node, amount, fee)
- return (txid, txhex, fee)
-
-def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
- """
- Create a random transaction.
- Returns (txid, hex-encoded-transaction-data, fee)
- """
- from_node = random.choice(nodes)
- to_node = random.choice(nodes)
- fee = min_fee + fee_increment*random.randint(0,fee_variants)
-
- (total_in, inputs) = gather_inputs(from_node, amount+fee)
- outputs = make_change(from_node, total_in, amount, fee)
- outputs[to_node.getnewaddress()] = float(amount)
-
- rawtx = from_node.createrawtransaction(inputs, outputs)
- signresult = from_node.signrawtransaction(rawtx)
- txid = from_node.sendrawtransaction(signresult["hex"], True)
-
- return (txid, signresult["hex"], fee)
-
-def assert_equal(thing1, thing2):
- if thing1 != thing2:
- raise AssertionError("%s != %s"%(str(thing1),str(thing2)))
-
-def assert_greater_than(thing1, thing2):
- if thing1 <= thing2:
- raise AssertionError("%s <= %s"%(str(thing1),str(thing2)))
-
-def assert_raises(exc, fun, *args, **kwds):
- try:
- fun(*args, **kwds)
- except exc:
- pass
- except Exception as e:
- raise AssertionError("Unexpected exception raised: "+type(e).__name__)
- else:
- raise AssertionError("No exception raised")
diff --git a/qa/rpc-tests/util.sh b/qa/rpc-tests/util.sh
deleted file mode 100644
index c2b7004308..0000000000
--- a/qa/rpc-tests/util.sh
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env bash
-# 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.
-
-# Functions used by more than one test
-
-function echoerr {
- echo "$@" 1>&2;
-}
-
-# Usage: ExtractKey <key> "<json_object_string>"
-# Warning: this will only work for the very-well-behaved
-# JSON produced by bitcoind, do NOT use it to try to
-# parse arbitrary/nested/etc JSON.
-function ExtractKey {
- echo $2 | tr -d ' "{}\n' | awk -v RS=',' -F: "\$1 ~ /$1/ { print \$2}"
-}
-
-function CreateDataDir {
- DIR=$1
- mkdir -p $DIR
- CONF=$DIR/bitcoin.conf
- echo "regtest=1" >> $CONF
- echo "keypool=2" >> $CONF
- echo "rpcuser=rt" >> $CONF
- echo "rpcpassword=rt" >> $CONF
- echo "rpcwait=1" >> $CONF
- echo "walletnotify=${SENDANDWAIT} -STOP" >> $CONF
- shift
- while (( "$#" )); do
- echo $1 >> $CONF
- shift
- done
-}
-
-function AssertEqual {
- if (( $( echo "$1 == $2" | bc ) == 0 ))
- then
- echoerr "AssertEqual: $1 != $2"
- declare -f CleanUp > /dev/null 2>&1
- if [[ $? -eq 0 ]] ; then
- CleanUp
- fi
- exit 1
- fi
-}
-
-# CheckBalance -datadir=... amount account minconf
-function CheckBalance {
- declare -i EXPECT="$2"
- B=$( $CLI $1 getbalance $3 $4 )
- if (( $( echo "$B == $EXPECT" | bc ) == 0 ))
- then
- echoerr "bad balance: $B (expected $2)"
- declare -f CleanUp > /dev/null 2>&1
- if [[ $? -eq 0 ]] ; then
- CleanUp
- fi
- exit 1
- fi
-}
-
-# Use: Address <datadir> [account]
-function Address {
- $CLI $1 getnewaddress $2
-}
-
-# Send from to amount
-function Send {
- from=$1
- to=$2
- amount=$3
- address=$(Address $to)
- txid=$( ${SENDANDWAIT} $CLI $from sendtoaddress $address $amount )
-}
-
-# Use: Unspent <datadir> <n'th-last-unspent> <var>
-function Unspent {
- local r=$( $CLI $1 listunspent | awk -F'[ |:,"]+' "\$2 ~ /$3/ { print \$3 }" | tail -n $2 | head -n 1)
- echo $r
-}
-
-# Use: CreateTxn1 <datadir> <n'th-last-unspent> <destaddress>
-# produces hex from signrawtransaction
-function CreateTxn1 {
- TXID=$(Unspent $1 $2 txid)
- AMOUNT=$(Unspent $1 $2 amount)
- VOUT=$(Unspent $1 $2 vout)
- RAWTXN=$( $CLI $1 createrawtransaction "[{\"txid\":\"$TXID\",\"vout\":$VOUT}]" "{\"$3\":$AMOUNT}")
- ExtractKey hex "$( $CLI $1 signrawtransaction $RAWTXN )"
-}
-
-# Use: SendRawTxn <datadir> <hex_txn_data>
-function SendRawTxn {
- ${SENDANDWAIT} $CLI $1 sendrawtransaction $2
-}
-
-# Use: GetBlocks <datadir>
-# returns number of blocks from getinfo
-function GetBlocks {
- $CLI $1 getblockcount
-}
diff --git a/qa/rpc-tests/wallet-hd.py b/qa/rpc-tests/wallet-hd.py
new file mode 100755
index 0000000000..c738ee2207
--- /dev/null
+++ b/qa/rpc-tests/wallet-hd.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ start_nodes,
+ start_node,
+ assert_equal,
+ connect_nodes_bi,
+)
+import os
+import shutil
+
+
+class WalletHDTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+ self.node_args = [['-usehd=0'], ['-usehd=1', '-keypool=0']]
+
+ def setup_network(self):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.node_args)
+ self.is_network_split = False
+ connect_nodes_bi(self.nodes, 0, 1)
+
+ def run_test (self):
+ tmpdir = self.options.tmpdir
+
+ # Make sure we use hd, keep masterkeyid
+ masterkeyid = self.nodes[1].getwalletinfo()['masterkeyid']
+ assert_equal(len(masterkeyid), 40)
+
+ # Import a non-HD private key in the HD wallet
+ non_hd_add = self.nodes[0].getnewaddress()
+ self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add))
+
+ # This should be enough to keep the master key and the non-HD key
+ self.nodes[1].backupwallet(tmpdir + "hd.bak")
+ #self.nodes[1].dumpwallet(tmpdir + "hd.dump")
+
+ # Derive some HD addresses and remember the last
+ # Also send funds to each add
+ self.nodes[0].generate(101)
+ hd_add = None
+ num_hd_adds = 300
+ for i in range(num_hd_adds):
+ hd_add = self.nodes[1].getnewaddress()
+ hd_info = self.nodes[1].validateaddress(hd_add)
+ assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i+1)+"'")
+ assert_equal(hd_info["hdmasterkeyid"], masterkeyid)
+ self.nodes[0].sendtoaddress(hd_add, 1)
+ self.nodes[0].generate(1)
+ self.nodes[0].sendtoaddress(non_hd_add, 1)
+ self.nodes[0].generate(1)
+
+ self.sync_all()
+ assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
+
+ print("Restore backup ...")
+ self.stop_node(1)
+ os.remove(self.options.tmpdir + "/node1/regtest/wallet.dat")
+ shutil.copyfile(tmpdir + "hd.bak", tmpdir + "/node1/regtest/wallet.dat")
+ self.nodes[1] = start_node(1, self.options.tmpdir, self.node_args[1])
+ #connect_nodes_bi(self.nodes, 0, 1)
+
+ # Assert that derivation is deterministic
+ hd_add_2 = None
+ for _ in range(num_hd_adds):
+ hd_add_2 = self.nodes[1].getnewaddress()
+ hd_info_2 = self.nodes[1].validateaddress(hd_add_2)
+ assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(_+1)+"'")
+ assert_equal(hd_info_2["hdmasterkeyid"], masterkeyid)
+ assert_equal(hd_add, hd_add_2)
+
+ # Needs rescan
+ self.stop_node(1)
+ self.nodes[1] = start_node(1, self.options.tmpdir, self.node_args[1] + ['-rescan'])
+ #connect_nodes_bi(self.nodes, 0, 1)
+ assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
+
+
+if __name__ == '__main__':
+ WalletHDTest().main ()
diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py
index bf71d5ebc5..5d96e7a6e5 100755
--- a/qa/rpc-tests/wallet.py
+++ b/qa/rpc-tests/wallet.py
@@ -1,32 +1,23 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
-# Distributed under the MIT/X11 software license, see the accompanying
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#
-# Exercise the wallet. Ported from wallet.sh.
-# Does the following:
-# a) creates 3 nodes, with an empty chain (no blocks).
-# b) node0 mines a block
-# c) node1 mines 101 blocks, so now nodes 0 and 1 have 50btc, node2 has none.
-# d) node0 sends 21 btc to node2, in two transactions (11 btc, then 10 btc).
-# e) node0 mines a block, collects the fee on the second transaction
-# f) node1 mines 100 blocks, to mature node0's just-mined block
-# g) check that node0 has 100-21, node2 has 21
-# h) node0 should now have 2 unspent outputs; send these to node2 via raw tx broadcast by node1
-# i) have node1 mine a block
-# j) check balances - node0 should have 0, node2 should have 100
-#
-
-from test_framework import BitcoinTestFramework
-from util import *
-
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
class WalletTest (BitcoinTestFramework):
- def setup_chain(self):
- print("Initializing test directory "+self.options.tmpdir)
- initialize_chain_clean(self.options.tmpdir, 3)
+ def check_fee_amount(self, curr_balance, balance_with_fee, fee_per_byte, tx_size):
+ """Return curr_balance after asserting the fee was in range"""
+ fee = balance_with_fee - curr_balance
+ assert_fee_amount(fee, tx_size, fee_per_byte * 1000)
+ return curr_balance
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 4
def setup_network(self, split=False):
self.nodes = start_nodes(3, self.options.tmpdir)
@@ -37,29 +28,55 @@ class WalletTest (BitcoinTestFramework):
self.sync_all()
def run_test (self):
- print "Mining blocks..."
- self.nodes[0].setgenerate(True, 1)
+ # Check that there's no UTXO on none of the nodes
+ assert_equal(len(self.nodes[0].listunspent()), 0)
+ assert_equal(len(self.nodes[1].listunspent()), 0)
+ assert_equal(len(self.nodes[2].listunspent()), 0)
+
+ print("Mining blocks...")
+
+ self.nodes[0].generate(1)
+
+ walletinfo = self.nodes[0].getwalletinfo()
+ assert_equal(walletinfo['immature_balance'], 50)
+ assert_equal(walletinfo['balance'], 0)
self.sync_all()
- self.nodes[1].setgenerate(True, 101)
+ self.nodes[1].generate(101)
self.sync_all()
assert_equal(self.nodes[0].getbalance(), 50)
assert_equal(self.nodes[1].getbalance(), 50)
assert_equal(self.nodes[2].getbalance(), 0)
+ # Check that only first and second nodes have UTXOs
+ assert_equal(len(self.nodes[0].listunspent()), 1)
+ assert_equal(len(self.nodes[1].listunspent()), 1)
+ assert_equal(len(self.nodes[2].listunspent()), 0)
+
# Send 21 BTC from 0 to 2 using sendtoaddress call.
- # Second transaction will be child of first, and will require a fee
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
- # Have node0 mine a block, thus he will collect his own fee.
- self.nodes[0].setgenerate(True, 1)
+ walletinfo = self.nodes[0].getwalletinfo()
+ assert_equal(walletinfo['immature_balance'], 0)
+
+ # Have node0 mine a block, thus it will collect its own fee.
+ self.nodes[0].generate(1)
self.sync_all()
+ # Exercise locking of unspent outputs
+ unspent_0 = self.nodes[2].listunspent()[0]
+ unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]}
+ self.nodes[2].lockunspent(False, [unspent_0])
+ assert_raises(JSONRPCException, self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
+ assert_equal([unspent_0], self.nodes[2].listlockunspent())
+ self.nodes[2].lockunspent(True, [unspent_0])
+ assert_equal(len(self.nodes[2].listlockunspent()), 0)
+
# Have node1 generate 100 blocks (so node0 can recover the fee)
- self.nodes[1].setgenerate(True, 100)
+ self.nodes[1].generate(100)
self.sync_all()
# node0 should end up with 100 btc in block rewards plus fees, but
@@ -68,18 +85,18 @@ class WalletTest (BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), 21)
# Node0 should have two unspent outputs.
- # Create a couple of transactions to send them to node2, submit them through
- # node1, and make sure both node0 and node2 pick them up properly:
+ # Create a couple of transactions to send them to node2, submit them through
+ # node1, and make sure both node0 and node2 pick them up properly:
node0utxos = self.nodes[0].listunspent(1)
assert_equal(len(node0utxos), 2)
# create both transactions
txns_to_send = []
- for utxo in node0utxos:
+ for utxo in node0utxos:
inputs = []
outputs = {}
inputs.append({ "txid" : utxo["txid"], "vout" : utxo["vout"]})
- outputs[self.nodes[2].getnewaddress("from1")] = utxo["amount"]
+ outputs[self.nodes[2].getnewaddress("from1")] = utxo["amount"] - 3
raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
txns_to_send.append(self.nodes[0].signrawtransaction(raw_tx))
@@ -88,13 +105,248 @@ class WalletTest (BitcoinTestFramework):
self.nodes[1].sendrawtransaction(txns_to_send[1]["hex"], True)
# Have node1 mine a block to confirm transactions:
- self.nodes[1].setgenerate(True, 1)
+ self.nodes[1].generate(1)
self.sync_all()
assert_equal(self.nodes[0].getbalance(), 0)
- assert_equal(self.nodes[2].getbalance(), 100)
- assert_equal(self.nodes[2].getbalance("from1"), 100-21)
+ assert_equal(self.nodes[2].getbalance(), 94)
+ assert_equal(self.nodes[2].getbalance("from1"), 94-21)
+
+ # Send 10 BTC normal
+ address = self.nodes[0].getnewaddress("test")
+ fee_per_byte = Decimal('0.001') / 1000
+ self.nodes[2].settxfee(fee_per_byte * 1000)
+ txid = self.nodes[2].sendtoaddress(address, 10, "", "", False)
+ self.nodes[2].generate(1)
+ self.sync_all()
+ node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
+ assert_equal(self.nodes[0].getbalance(), Decimal('10'))
+
+ # Send 10 BTC with subtract fee from amount
+ txid = self.nodes[2].sendtoaddress(address, 10, "", "", True)
+ self.nodes[2].generate(1)
+ self.sync_all()
+ node_2_bal -= Decimal('10')
+ assert_equal(self.nodes[2].getbalance(), node_2_bal)
+ node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
+
+ # Sendmany 10 BTC
+ txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [])
+ self.nodes[2].generate(1)
+ self.sync_all()
+ node_0_bal += Decimal('10')
+ node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
+ assert_equal(self.nodes[0].getbalance(), node_0_bal)
+
+ # Sendmany 10 BTC with subtract fee from amount
+ txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [address])
+ self.nodes[2].generate(1)
+ self.sync_all()
+ node_2_bal -= Decimal('10')
+ assert_equal(self.nodes[2].getbalance(), node_2_bal)
+ node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
+
+ # Test ResendWalletTransactions:
+ # Create a couple of transactions, then start up a fourth
+ # node (nodes[3]) and ask nodes[0] to rebroadcast.
+ # EXPECT: nodes[3] should have those transactions in its mempool.
+ txid1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
+ txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
+ sync_mempools(self.nodes)
+
+ self.nodes.append(start_node(3, self.options.tmpdir))
+ connect_nodes_bi(self.nodes, 0, 3)
+ sync_blocks(self.nodes)
+
+ relayed = self.nodes[0].resendwallettransactions()
+ assert_equal(set(relayed), {txid1, txid2})
+ sync_mempools(self.nodes)
+
+ assert(txid1 in self.nodes[3].getrawmempool())
+
+ # Exercise balance rpcs
+ assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], 1)
+ assert_equal(self.nodes[0].getunconfirmedbalance(), 1)
+
+ #check if we can list zero value tx as available coins
+ #1. create rawtx
+ #2. hex-changed one output to 0.0
+ #3. sign and send
+ #4. check if recipient (node0) can list the zero value tx
+ usp = self.nodes[1].listunspent()
+ inputs = [{"txid":usp[0]['txid'], "vout":usp[0]['vout']}]
+ outputs = {self.nodes[1].getnewaddress(): 49.998, self.nodes[0].getnewaddress(): 11.11}
+
+ rawTx = self.nodes[1].createrawtransaction(inputs, outputs).replace("c0833842", "00000000") #replace 11.11 with 0.0 (int32)
+ decRawTx = self.nodes[1].decoderawtransaction(rawTx)
+ signedRawTx = self.nodes[1].signrawtransaction(rawTx)
+ decRawTx = self.nodes[1].decoderawtransaction(signedRawTx['hex'])
+ zeroValueTxid= decRawTx['txid']
+ sendResp = self.nodes[1].sendrawtransaction(signedRawTx['hex'])
+
+ self.sync_all()
+ self.nodes[1].generate(1) #mine a block
+ self.sync_all()
+
+ unspentTxs = self.nodes[0].listunspent() #zero value tx must be in listunspents output
+ found = False
+ for uTx in unspentTxs:
+ if uTx['txid'] == zeroValueTxid:
+ found = True
+ assert_equal(uTx['amount'], Decimal('0'))
+ assert(found)
+
+ #do some -walletbroadcast tests
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+ self.nodes = start_nodes(3, self.options.tmpdir, [["-walletbroadcast=0"],["-walletbroadcast=0"],["-walletbroadcast=0"]])
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+ self.sync_all()
+
+ txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2)
+ txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted)
+ self.nodes[1].generate(1) #mine a block, tx should not be in there
+ self.sync_all()
+ assert_equal(self.nodes[2].getbalance(), node_2_bal) #should not be changed because tx was not broadcasted
+
+ #now broadcast from another node, mine a block, sync, and check the balance
+ self.nodes[1].sendrawtransaction(txObjNotBroadcasted['hex'])
+ self.nodes[1].generate(1)
+ self.sync_all()
+ node_2_bal += 2
+ txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted)
+ assert_equal(self.nodes[2].getbalance(), node_2_bal)
+
+ #create another tx
+ txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2)
+
+ #restart the nodes with -walletbroadcast=1
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+ self.nodes = start_nodes(3, self.options.tmpdir)
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+ sync_blocks(self.nodes)
+
+ self.nodes[0].generate(1)
+ sync_blocks(self.nodes)
+ node_2_bal += 2
+
+ #tx should be added to balance because after restarting the nodes tx should be broadcastet
+ assert_equal(self.nodes[2].getbalance(), node_2_bal)
+
+ #send a tx with value in a string (PR#6380 +)
+ txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "2")
+ txObj = self.nodes[0].gettransaction(txId)
+ assert_equal(txObj['amount'], Decimal('-2'))
+
+ txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "0.0001")
+ txObj = self.nodes[0].gettransaction(txId)
+ assert_equal(txObj['amount'], Decimal('-0.0001'))
+
+ #check if JSON parser can handle scientific notation in strings
+ txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1e-4")
+ txObj = self.nodes[0].gettransaction(txId)
+ assert_equal(txObj['amount'], Decimal('-0.0001'))
+
+ try:
+ txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4")
+ except JSONRPCException as e:
+ assert("Invalid amount" in e.error['message'])
+ else:
+ raise AssertionError("Must not parse invalid amounts")
+
+
+ try:
+ self.nodes[0].generate("2")
+ raise AssertionError("Must not accept strings as numeric")
+ except JSONRPCException as e:
+ assert("not an integer" in e.error['message'])
+
+ # Import address and private key to check correct behavior of spendable unspents
+ # 1. Send some coins to generate new UTXO
+ address_to_import = self.nodes[2].getnewaddress()
+ txid = self.nodes[0].sendtoaddress(address_to_import, 1)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # 2. Import address from node2 to node1
+ self.nodes[1].importaddress(address_to_import)
+
+ # 3. Validate that the imported address is watch-only on node1
+ assert(self.nodes[1].validateaddress(address_to_import)["iswatchonly"])
+
+ # 4. Check that the unspents after import are not spendable
+ assert_array_result(self.nodes[1].listunspent(),
+ {"address": address_to_import},
+ {"spendable": False})
+
+ # 5. Import private key of the previously imported address on node1
+ priv_key = self.nodes[2].dumpprivkey(address_to_import)
+ self.nodes[1].importprivkey(priv_key)
+
+ # 6. Check that the unspents are now spendable on node1
+ assert_array_result(self.nodes[1].listunspent(),
+ {"address": address_to_import},
+ {"spendable": True})
+
+ # Mine a block from node0 to an address from node1
+ cbAddr = self.nodes[1].getnewaddress()
+ blkHash = self.nodes[0].generatetoaddress(1, cbAddr)[0]
+ cbTxId = self.nodes[0].getblock(blkHash)['tx'][0]
+ self.sync_all()
+
+ # Check that the txid and balance is found by node1
+ self.nodes[1].gettransaction(cbTxId)
+
+ # check if wallet or blockchain maintenance changes the balance
+ self.sync_all()
+ blocks = self.nodes[0].generate(2)
+ self.sync_all()
+ balance_nodes = [self.nodes[i].getbalance() for i in range(3)]
+ block_count = self.nodes[0].getblockcount()
+
+ # Check modes:
+ # - True: unicode escaped as \u....
+ # - False: unicode directly as UTF-8
+ for mode in [True, False]:
+ self.nodes[0].ensure_ascii = mode
+ # unicode check: Basic Multilingual Plane, Supplementary Plane respectively
+ for s in [u'рыба', u'𝅘𝅥𝅯']:
+ addr = self.nodes[0].getaccountaddress(s)
+ label = self.nodes[0].getaccount(addr)
+ assert_equal(label, s)
+ assert(s in self.nodes[0].listaccounts().keys())
+ self.nodes[0].ensure_ascii = True # restore to default
+
+ # maintenance tests
+ maintenance = [
+ '-rescan',
+ '-reindex',
+ '-zapwallettxes=1',
+ '-zapwallettxes=2',
+ # disabled until issue is fixed: https://github.com/bitcoin/bitcoin/issues/7463
+ # '-salvagewallet',
+ ]
+ for m in maintenance:
+ print("check " + m)
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+ self.nodes = start_nodes(3, self.options.tmpdir, [[m]] * 3)
+ while m == '-reindex' and [block_count] * 3 != [self.nodes[i].getblockcount() for i in range(3)]:
+ # reindex will leave rpc warm up "early"; Wait for it to finish
+ time.sleep(0.1)
+ assert_equal(balance_nodes, [self.nodes[i].getbalance() for i in range(3)])
+ # Exercise listsinceblock with the last two blocks
+ coinbase_tx_1 = self.nodes[0].listsinceblock(blocks[0])
+ assert_equal(coinbase_tx_1["lastblock"], blocks[1])
+ assert_equal(len(coinbase_tx_1["transactions"]), 1)
+ assert_equal(coinbase_tx_1["transactions"][0]["blockhash"], blocks[1])
+ assert_equal(len(self.nodes[0].listsinceblock(blocks[1])["transactions"]), 0)
if __name__ == '__main__':
- WalletTest ().main ()
+ WalletTest().main()
diff --git a/qa/rpc-tests/walletbackup.py b/qa/rpc-tests/walletbackup.py
index 6a42d9dfa4..b991d5c761 100755
--- a/qa/rpc-tests/walletbackup.py
+++ b/qa/rpc-tests/walletbackup.py
@@ -1,6 +1,6 @@
-#!/usr/bin/env python2
-# Copyright (c) 2014 The Bitcoin Core developers
-# Distributed under the MIT/X11 software license, see the accompanying
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
@@ -33,23 +33,24 @@ Shutdown again, restore using importwallet,
and confirm again balances are correct.
"""
-from test_framework import BitcoinTestFramework
-from util import *
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
from random import randint
import logging
-logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
+logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO, stream=sys.stdout)
class WalletBackupTest(BitcoinTestFramework):
- def setup_chain(self):
- logging.info("Initializing test directory "+self.options.tmpdir)
- initialize_chain_clean(self.options.tmpdir, 4)
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 4
# This mirrors how the network was setup in the bash test
def setup_network(self, split=False):
# nodes 1, 2,3 are spenders, let's give them a keypool=100
extra_args = [["-keypool=100"], ["-keypool=100"], ["-keypool=100"], []]
- self.nodes = start_nodes(4, self.options.tmpdir, extra_args)
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args)
connect_nodes(self.nodes[0], 3)
connect_nodes(self.nodes[1], 3)
connect_nodes(self.nodes[2], 3)
@@ -77,7 +78,7 @@ class WalletBackupTest(BitcoinTestFramework):
# Have the miner (node3) mine a block.
# Must sync mempools before mining.
sync_mempools(self.nodes)
- self.nodes[3].setgenerate(True, 1)
+ self.nodes[3].generate(1)
# As above, this mirrors the original bash test.
def start_three(self):
@@ -101,13 +102,13 @@ class WalletBackupTest(BitcoinTestFramework):
def run_test(self):
logging.info("Generating initial blockchain")
- self.nodes[0].setgenerate(True, 1)
+ self.nodes[0].generate(1)
sync_blocks(self.nodes)
- self.nodes[1].setgenerate(True, 1)
+ self.nodes[1].generate(1)
sync_blocks(self.nodes)
- self.nodes[2].setgenerate(True, 1)
+ self.nodes[2].generate(1)
sync_blocks(self.nodes)
- self.nodes[3].setgenerate(True, 100)
+ self.nodes[3].generate(100)
sync_blocks(self.nodes)
assert_equal(self.nodes[0].getbalance(), 50)
@@ -134,7 +135,7 @@ class WalletBackupTest(BitcoinTestFramework):
self.do_one_round()
# Generate 101 more blocks, so any fees paid mature
- self.nodes[3].setgenerate(True, 101)
+ self.nodes[3].generate(101)
self.sync_all()
balance0 = self.nodes[0].getbalance()
diff --git a/qa/rpc-tests/zapwallettxes.py b/qa/rpc-tests/zapwallettxes.py
new file mode 100755
index 0000000000..17ba53a844
--- /dev/null
+++ b/qa/rpc-tests/zapwallettxes.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+
+class ZapWalletTXesTest (BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 3
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+ self.is_network_split=False
+ self.sync_all()
+
+ def run_test (self):
+ print("Mining blocks...")
+ self.nodes[0].generate(1)
+ self.sync_all()
+ self.nodes[1].generate(101)
+ self.sync_all()
+
+ assert_equal(self.nodes[0].getbalance(), 50)
+
+ txid0 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
+ txid1 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ txid2 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
+ txid3 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
+
+ tx0 = self.nodes[0].gettransaction(txid0)
+ assert_equal(tx0['txid'], txid0) #tx0 must be available (confirmed)
+
+ tx1 = self.nodes[0].gettransaction(txid1)
+ assert_equal(tx1['txid'], txid1) #tx1 must be available (confirmed)
+
+ tx2 = self.nodes[0].gettransaction(txid2)
+ assert_equal(tx2['txid'], txid2) #tx2 must be available (unconfirmed)
+
+ tx3 = self.nodes[0].gettransaction(txid3)
+ assert_equal(tx3['txid'], txid3) #tx3 must be available (unconfirmed)
+
+ #restart bitcoind
+ self.nodes[0].stop()
+ bitcoind_processes[0].wait()
+ self.nodes[0] = start_node(0,self.options.tmpdir)
+
+ tx3 = self.nodes[0].gettransaction(txid3)
+ assert_equal(tx3['txid'], txid3) #tx must be available (unconfirmed)
+
+ self.nodes[0].stop()
+ bitcoind_processes[0].wait()
+
+ #restart bitcoind with zapwallettxes
+ self.nodes[0] = start_node(0,self.options.tmpdir, ["-zapwallettxes=1"])
+
+ assert_raises(JSONRPCException, self.nodes[0].gettransaction, [txid3])
+ #there must be a expection because the unconfirmed wallettx0 must be gone by now
+
+ tx0 = self.nodes[0].gettransaction(txid0)
+ assert_equal(tx0['txid'], txid0) #tx0 (confirmed) must still be available because it was confirmed
+
+
+if __name__ == '__main__':
+ ZapWalletTXesTest ().main ()
diff --git a/qa/rpc-tests/zapwallettxes.sh b/qa/rpc-tests/zapwallettxes.sh
deleted file mode 100755
index 4312d84e94..0000000000
--- a/qa/rpc-tests/zapwallettxes.sh
+++ /dev/null
@@ -1,165 +0,0 @@
-#!/usr/bin/env bash
-# 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 -zapwallettxes=<mode>
-
-if [ $# -lt 1 ]; then
- echo "Usage: $0 path_to_binaries"
- echo "e.g. $0 ../../src"
- echo "Env vars BITCOIND and BITCOINCLI may be used to specify the exact binaries used"
- exit 1
-fi
-
-set -f
-
-BITCOIND=${BITCOIND:-${1}/bitcoind}
-CLI=${BITCOINCLI:-${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)
-
-D1=${D}/node1
-CreateDataDir "$D1" port=11000 rpcport=11001
-B1ARGS="-datadir=$D1"
-$BITCOIND $B1ARGS &
-B1PID=$!
-
-D2=${D}/node2
-CreateDataDir "$D2" port=11010 rpcport=11011
-B2ARGS="-datadir=$D2"
-$BITCOIND $B2ARGS &
-B2PID=$!
-
-function CleanUp {
-$CLI $B2ARGS stop > /dev/null 2>&1
-wait $B2PID
-$CLI $B1ARGS stop > /dev/null 2>&1
-wait $B1PID
-
-rm -rf $D
-}
-
-# 110 blocks, 10 mature == 500 XBT
-$CLI $B1ARGS setgenerate true 110
-$CLI $B2ARGS setgenerate true 110
-
-CheckBalance "$B1ARGS" 500
-CheckBalance "$B2ARGS" 500
-
-# Send 10 XBT
-TXID1_DEFAULT=$($CLI $B1ARGS sendtoaddress "mrhz5ZgSF3C1BSdyCKt3gEdhKoRL5BNfJV" 10)
-TXID2_DEFAULT=$($CLI $B2ARGS sendtoaddress "mrhz5ZgSF3C1BSdyCKt3gEdhKoRL5BNfJV" 10)
-
-CheckBalance $B1ARGS 490
-CheckBalance $B2ARGS 490
-
-# Move 10 XBT to testaccount
-TMP=$($CLI $B1ARGS move "" "testaccount" 10)
-TMP=$($CLI $B2ARGS move "" "testaccount" 10)
-
-CheckBalance $B1ARGS 10 "testaccount"
-CheckBalance $B2ARGS 10 "testaccount"
-
-# Send 1 XBT from testaccount
-TXID1_TESTACCOUNT=$($CLI $B1ARGS sendfrom "testaccount" "mrhz5ZgSF3C1BSdyCKt3gEdhKoRL5BNfJV" 1)
-TXID2_TESTACCOUNT=$($CLI $B2ARGS sendfrom "testaccount" "mrhz5ZgSF3C1BSdyCKt3gEdhKoRL5BNfJV" 1)
-
-CheckBalance $B1ARGS 9 "testaccount"
-CheckBalance $B2ARGS 9 "testaccount"
-
-CheckBalance $B1ARGS 489
-CheckBalance $B2ARGS 489
-
-# Confirm transactions
-$CLI $B1ARGS setgenerate true 1
-$CLI $B2ARGS setgenerate true 1
-
-# Create unconfirmed transaction
-TXID1_UNCONFIRMED=$($CLI $B1ARGS sendtoaddress "mrhz5ZgSF3C1BSdyCKt3gEdhKoRL5BNfJV" 1)
-TXID2_UNCONFIRMED=$($CLI $B2ARGS sendtoaddress "mrhz5ZgSF3C1BSdyCKt3gEdhKoRL5BNfJV" 1)
-
-# check balance (we created another 50 and spent 1 in the meantime)
-CheckBalance $B1ARGS 538
-CheckBalance $B2ARGS 538
-
-# Safety check, if unconfirmed transactions are there
-$CLI $B1ARGS gettransaction $TXID1_UNCONFIRMED > /dev/null 2>&1
-if [[ $? -ne 0 ]] ; then
- echoerr "gettransaction1_1: $TXID1_UNCONFIRMED failed"
- CleanUp
- exit 1
-fi
-$CLI $B2ARGS gettransaction $TXID2_UNCONFIRMED > /dev/null 2>&1
-if [[ $? -ne 0 ]] ; then
- echoerr "gettransaction2_1: $TXID2_UNCONFIRMED failed"
- CleanUp
- exit 1
-fi
-
-# stop nodes
-$CLI $B2ARGS stop > /dev/null 2>&1
-wait $B2PID
-$CLI $B1ARGS stop > /dev/null 2>&1
-wait $B1PID
-
-# restart nodes with -zapwallettxes
-$BITCOIND -zapwallettxes=1 $B1ARGS &
-B1PID=$!
-$BITCOIND -zapwallettxes=2 $B2ARGS &
-B2PID=$!
-
-# check if confirmed transactions are there
-$CLI $B1ARGS gettransaction $TXID1_DEFAULT > /dev/null 2>&1
-if [[ $? -ne 0 ]] ; then
- echoerr "check confirmed transaction 1: $TXID1_DEFAULT failed"
- CleanUp
- exit 1
-fi
-$CLI $B2ARGS gettransaction $TXID2_DEFAULT > /dev/null 2>&1
-if [[ $? -ne 0 ]] ; then
- echoerr "check confirmed transaction 2: $TXID2_DEFAULT failed"
- CleanUp
- exit 1
-fi
-$CLI $B1ARGS gettransaction $TXID1_TESTACCOUNT > /dev/null 2>&1
-if [[ $? -ne 0 ]] ; then
- echoerr "check confirmed transaction 3: $TXID1_TESTACCOUNT failed"
- CleanUp
- exit 1
-fi
-$CLI $B2ARGS gettransaction $TXID2_TESTACCOUNT > /dev/null 2>&1
-if [[ $? -ne 0 ]] ; then
- echoerr "check confirmed transaction 4: $TXID2_TESTACCOUNT failed"
- CleanUp
- exit 1
-fi
-
-# check if unconfirmed transaction is gone
-$CLI $B1ARGS gettransaction $TXID1_UNCONFIRMED > /dev/null 2>&1
-if [[ $? -eq 0 ]] ; then
- echoerr "check unconfirmed transaction 1: $TXID1_UNCONFIRMED failed"
- CleanUp
- exit 1
-fi
-$CLI $B2ARGS gettransaction $TXID2_UNCONFIRMED > /dev/null 2>&1
-if [[ $? -eq 0 ]] ; then
- echoerr "check unconfirmed transaction 2: $TXID2_UNCONFIRMED failed"
- CleanUp
- exit 1
-fi
-
-# check zapwallet mode 1, testaccount balance must be 9 (keeping transaction metadata)
-CheckBalance $B1ARGS 9 "testaccount"
-
-# check zapwallet mode 2, testaccount balance must be 10 (dropping transaction metadata)
-CheckBalance $B2ARGS 10 "testaccount"
-
-echo "Tests successful, cleaning up"
-CleanUp
-exit 0
diff --git a/qa/rpc-tests/zmq_test.py b/qa/rpc-tests/zmq_test.py
new file mode 100755
index 0000000000..3a116317fe
--- /dev/null
+++ b/qa/rpc-tests/zmq_test.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test ZMQ interface
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import zmq
+import struct
+
+import http.client
+import urllib.parse
+
+class ZMQTest (BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+
+ port = 28332
+
+ def setup_nodes(self):
+ self.zmqContext = zmq.Context()
+ self.zmqSubSocket = self.zmqContext.socket(zmq.SUB)
+ self.zmqSubSocket.setsockopt(zmq.SUBSCRIBE, b"hashblock")
+ self.zmqSubSocket.setsockopt(zmq.SUBSCRIBE, b"hashtx")
+ self.zmqSubSocket.connect("tcp://127.0.0.1:%i" % self.port)
+ return start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[
+ ['-zmqpubhashtx=tcp://127.0.0.1:'+str(self.port), '-zmqpubhashblock=tcp://127.0.0.1:'+str(self.port)],
+ [],
+ [],
+ []
+ ])
+
+ def run_test(self):
+ self.sync_all()
+
+ genhashes = self.nodes[0].generate(1)
+ self.sync_all()
+
+ print("listen...")
+ msg = self.zmqSubSocket.recv_multipart()
+ topic = msg[0]
+ assert_equal(topic, b"hashtx")
+ body = msg[1]
+ nseq = msg[2]
+ msgSequence = struct.unpack('<I', msg[-1])[-1]
+ assert_equal(msgSequence, 0) #must be sequence 0 on hashtx
+
+ msg = self.zmqSubSocket.recv_multipart()
+ topic = msg[0]
+ body = msg[1]
+ msgSequence = struct.unpack('<I', msg[-1])[-1]
+ assert_equal(msgSequence, 0) #must be sequence 0 on hashblock
+ blkhash = bytes_to_hex_str(body)
+
+ assert_equal(genhashes[0], blkhash) #blockhash from generate must be equal to the hash received over zmq
+
+ n = 10
+ genhashes = self.nodes[1].generate(n)
+ self.sync_all()
+
+ zmqHashes = []
+ blockcount = 0
+ for x in range(0,n*2):
+ msg = self.zmqSubSocket.recv_multipart()
+ topic = msg[0]
+ body = msg[1]
+ if topic == b"hashblock":
+ zmqHashes.append(bytes_to_hex_str(body))
+ msgSequence = struct.unpack('<I', msg[-1])[-1]
+ assert_equal(msgSequence, blockcount+1)
+ blockcount += 1
+
+ for x in range(0,n):
+ assert_equal(genhashes[x], zmqHashes[x]) #blockhash from generate must be equal to the hash received over zmq
+
+ #test tx from a second node
+ hashRPC = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
+ self.sync_all()
+
+ # now we should receive a zmq msg because the tx was broadcast
+ msg = self.zmqSubSocket.recv_multipart()
+ topic = msg[0]
+ body = msg[1]
+ hashZMQ = ""
+ if topic == b"hashtx":
+ hashZMQ = bytes_to_hex_str(body)
+ msgSequence = struct.unpack('<I', msg[-1])[-1]
+ assert_equal(msgSequence, blockcount+1)
+
+ assert_equal(hashRPC, hashZMQ) #blockhash from generate must be equal to the hash received over zmq
+
+
+if __name__ == '__main__':
+ ZMQTest ().main ()