diff options
Diffstat (limited to 'qa')
84 files changed, 10665 insertions, 2084 deletions
diff --git a/qa/README.md b/qa/README.md index 758d1f47e5..225207cc1c 100644 --- a/qa/README.md +++ b/qa/README.md @@ -5,18 +5,48 @@ 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>`. +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 -Or you can run any combination of tests by calling `qa/pull-tester/rpc-tests.py <testname1> <testname2> <testname3> ...` + qa/pull-tester/rpc-tests.py -extended -Run the regression test suite with `qa/pull-tester/rpc-tests.py` +By default, tests will be run in parallel. To specify how many jobs to run, +append `-parallel=n` (default n=4). -Run all possible tests with `qa/pull-tester/rpc-tests.py -extended` +If you want to create a basic coverage report for the rpc test suite, append `--coverage`. -Possible options: +Possible options, which apply to each individual test run: ``` -h, --help show this help message and exit diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index df71e44b60..4a953385e0 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.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. @@ -29,60 +29,97 @@ 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 not vars().has_key('ENABLE_WALLET'): +if 'ENABLE_WALLET' not in vars(): ENABLE_WALLET=0 -if not vars().has_key('ENABLE_BITCOIND'): +if 'ENABLE_BITCOIND' not in vars(): ENABLE_BITCOIND=0 -if not vars().has_key('ENABLE_UTILS'): +if 'ENABLE_UTILS' not in vars(): ENABLE_UTILS=0 -if not vars().has_key('ENABLE_ZMQ'): +if 'ENABLE_ZMQ' not in vars(): ENABLE_ZMQ=0 ENABLE_COVERAGE=0 -#Create a set to store arguments and create the passOn string +#Create a set to store arguments and create the passon string opts = set() -passOn = "" -p = re.compile("^--") +passon_args = [] +PASSON_REGEX = re.compile("^--") +PARALLEL_REGEX = re.compile('^-parallel=') -bold = ("","") -if (os.name == 'posix'): - bold = ('\033[0m', '\033[1m') +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 (p.match(arg) or arg == "-h"): - passOn += " " + arg + 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 -buildDir = BUILDDIR -os.environ["BITCOIND"] = buildDir + '/src/bitcoind' + EXEEXT -os.environ["BITCOINCLI"] = buildDir + '/src/bitcoin-cli' + EXEEXT +if "BITCOIND" not in os.environ: + os.environ["BITCOIND"] = BUILDDIR + '/src/bitcoind' + EXEEXT -#Disable Windows tests by default if EXEEXT == ".exe" and "-win" not in opts: - print "Win tests currently disabled. Use -win option to enable" + # 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) -#Tests +# python3-zmq may not be installed. Handle this gracefully and with some helpful info +if ENABLE_ZMQ: + try: + import zmq + except ImportError: + print("ERROR: \"import zmq\" failed. Set ENABLE_ZMQ=0 or " + "to run zmq tests, see dependency info in /qa/README.md.") + # ENABLE_ZMQ=0 + raise + 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-accounts.py', + 'wallet-hd.py', + 'wallet-dump.py', 'listtransactions.py', 'receivedby.py', 'mempool_resurrect_test.py', 'txn_doublespend.py --mineblock', + 'p2p-segwit.py', + 'segwit.py', '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', @@ -90,98 +127,157 @@ testScripts = [ 'merkle_blocks.py', 'fundrawtransaction.py', 'signrawtransactions.py', - 'walletbackup.py', 'nodehandling.py', 'reindex.py', 'decodescript.py', - 'p2p-fullblocktest.py', 'blockchain.py', 'disablewallet.py', 'sendheaders.py', 'keypool.py', + 'p2p-mempool.py', 'prioritise_transaction.py', + 'invalidblockrequest.py', + 'invalidtxrequest.py', + 'abandonconflict.py', + 'p2p-versionbits-warning.py', + 'importprunedfunds.py', + 'signmessages.py', + 'p2p-compactblocks.py', + 'nulldummy.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', - 'pruning.py', 'forknotify.py', 'invalidateblock.py', -# 'rpcbind_test.py', #temporary, bug in libevent, see #6655 + 'rpcbind_test.py', 'smartfees.py', 'maxblocksinflight.py', - 'invalidblockrequest.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 ] -#Enable ZMQ tests -if ENABLE_ZMQ == 1: - testScripts.append('zmq_test.py') - 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 + flags.append("--cachedir=%s/qa/cache" % BUILDDIR) + 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('' if passed else stdout + '\n', end='') + print('' if stderr == '' else 'stderr:\n' + stderr + '\n', end='') + 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. + """ - if(ENABLE_WALLET == 1 and ENABLE_UTILS == 1 and ENABLE_BITCOIND == 1): - rpcTestDir = buildDir + '/qa/rpc-tests/' - run_extended = '-extended' in opts - cov_flag = coverage.flag if coverage else '' - flags = " --srcdir %s/src %s %s" % (buildDir, cov_flag, passOn) - - #Run Tests - for i in range(len(testScripts)): - if (len(opts) == 0 - or (len(opts) == 1 and "-win" in opts ) - or run_extended - or testScripts[i] in opts - or re.sub(".py$", "", testScripts[i]) in opts ): - - print("Running testscript %s%s%s ..." % (bold[1], testScripts[i], bold[0])) - time0 = time.time() - subprocess.check_call( - rpcTestDir + testScripts[i] + flags, shell=True) - print("Duration: %s s\n" % (int(time.time() - time0))) - - # exit if help is called so we print just one set of - # instructions - p = re.compile(" -h| --help") - if p.match(passOn): - sys.exit(0) - - # Run Extended Tests - for i in range(len(testScriptsExt)): - if (run_extended or testScriptsExt[i] in opts - or re.sub(".py$", "", testScriptsExt[i]) in opts): - - print( - "Running 2nd level testscript " - + "%s%s%s ..." % (bold[1], testScriptsExt[i], bold[0])) - time0 = time.time() - subprocess.check_call( - rpcTestDir + testScriptsExt[i] + flags, shell=True) - print("Duration: %s s\n" % (int(time.time() - time0))) - - if coverage: - coverage.report_rpc_coverage() - - print("Cleaning up coverage data") - coverage.cleanup() - - else: - print "No rpc tests to run. Wallet, utils, and bitcoind must all be enabled" + 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)] + log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16) + log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16) + self.jobs.append((t, + time.time(), + subprocess.Popen((RPC_TESTS_DIR + t).split() + self.flags + port_seed, + universal_newlines=True, + stdout=log_stdout, + stderr=log_stderr), + log_stdout, + log_stderr)) + 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, log_out, log_err) = j + if proc.poll() is not None: + log_out.seek(0), log_err.seek(0) + [stdout, stderr] = [l.read().decode('utf-8') for l in (log_out, log_err)] + log_out.close(), log_err.close() + 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): @@ -201,7 +297,7 @@ class RPCCoverage(object): """ def __init__(self): self.dir = tempfile.mkdtemp(prefix="coverage") - self.flag = '--coveragedir %s' % self.dir + self.flag = '--coveragedir=%s' % self.dir def report_rpc_coverage(self): """ diff --git a/qa/pull-tester/tests_config.py.in b/qa/pull-tester/tests_config.py.in index 937b4231f1..a0d0a3d98a 100644 --- a/qa/pull-tester/tests_config.py.in +++ b/qa/pull-tester/tests_config.py.in @@ -1,8 +1,9 @@ -#!/usr/bin/env python2 -# 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@" @@ -11,5 +12,3 @@ EXEEXT="@EXEEXT@" @BUILD_BITCOIN_UTILS_TRUE@ENABLE_UTILS=1 @BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=1 @ENABLE_ZMQ_TRUE@ENABLE_ZMQ=1 - - diff --git a/qa/rpc-tests/README.md b/qa/rpc-tests/README.md index 898931936b..651b01f18a 100644 --- a/qa/rpc-tests/README.md +++ b/qa/rpc-tests/README.md @@ -47,10 +47,7 @@ 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. NOTE: be sure to call -```self.create_callback_map()``` in your derived classes' ```__init__``` -function, so that the correct mappings are set up between p2p messages and your -callback functions. +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. diff --git a/qa/rpc-tests/abandonconflict.py b/qa/rpc-tests/abandonconflict.py new file mode 100755 index 0000000000..874df48777 --- /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_equal(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_equal(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_equal(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_equal(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_equal(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_equal(len(self.nodes[0].getrawmempool()), 0) + assert_equal(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_equal(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_equal(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_equal(len(self.nodes[0].getrawmempool()), 0) + newbalance = self.nodes[0].getbalance() + assert_equal(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_equal(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_equal(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 index 9ca5c69f16..e903b2fbf0 100755 --- a/qa/rpc-tests/bip65-cltv-p2p.py +++ b/qa/rpc-tests/bip65-cltv-p2p.py @@ -1,17 +1,15 @@ -#!/usr/bin/env python2 -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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_NOP2, OP_DROP -from binascii import hexlify, unhexlify -import cStringIO +from test_framework.script import CScript, OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP +from io import BytesIO import time def cltv_invalidate(tx): @@ -19,7 +17,7 @@ def cltv_invalidate(tx): Prepends -1 CLTV DROP in the scriptSig itself. ''' - tx.vin[0].scriptSig = CScript([OP_1NEGATE, OP_NOP2, OP_DROP] + + tx.vin[0].scriptSig = CScript([OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig))) ''' @@ -39,11 +37,12 @@ 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(1, self.options.tmpdir, + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[['-debug', '-whitelist=127.0.0.1', '-blockversion=3']], binary=[self.options.testbinary]) @@ -60,7 +59,7 @@ class BIP65Test(ComparisonTestFramework): rawtx = node.createrawtransaction(inputs, outputs) signresult = node.signrawtransaction(rawtx) tx = CTransaction() - f = cStringIO.StringIO(unhexlify(signresult['hex'])) + f = BytesIO(hex_str_to_bytes(signresult['hex'])) tx.deserialize(f) return tx @@ -68,13 +67,13 @@ class BIP65Test(ComparisonTestFramework): 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() + "L", 0) + self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0) self.nodeaddress = self.nodes[0].getnewaddress() - self.last_block_time = time.time() + self.last_block_time = int(time.time()) - ''' 98 more version 3 blocks ''' + ''' 398 more version 3 blocks ''' test_blocks = [] - for i in xrange(98): + for i in range(398): block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 3 block.rehash() @@ -87,7 +86,7 @@ class BIP65Test(ComparisonTestFramework): ''' Mine 749 version 4 blocks ''' test_blocks = [] - for i in xrange(749): + for i in range(749): block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 4 block.rehash() @@ -119,27 +118,9 @@ class BIP65Test(ComparisonTestFramework): 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 xrange(199): + for i in range(199): block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 4 block.rehash() @@ -170,6 +151,24 @@ class BIP65Test(ComparisonTestFramework): height += 1 yield TestInstance([[block, True]]) + ''' + Check that the new CLTV rules are enforced in the 951st 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 1 old version block, should be invalid ''' block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 3 diff --git a/qa/rpc-tests/bip65-cltv.py b/qa/rpc-tests/bip65-cltv.py index e90e11e6a7..baa77b92a0 100755 --- a/qa/rpc-tests/bip65-cltv.py +++ b/qa/rpc-tests/bip65-cltv.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python2 -# Copyright (c) 2015 The Bitcoin Core developers +#!/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. @@ -9,10 +9,12 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import os -import shutil class BIP65Test(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 3 + self.setup_clean_chain = False def setup_network(self): self.nodes = [] @@ -28,13 +30,14 @@ class BIP65Test(BitcoinTestFramework): cnt = self.nodes[0].getblockcount() # Mine some old-version blocks - self.nodes[1].generate(100) + self.nodes[1].generate(200) + cnt += 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 xrange(15): + for i in range(15): self.nodes[2].generate(50) self.sync_all() if (self.nodes[0].getblockcount() != cnt + 850): @@ -46,12 +49,12 @@ class BIP65Test(BitcoinTestFramework): self.nodes[2].generate(1) self.sync_all() if (self.nodes[0].getblockcount() != cnt + 851): - raise AssertionFailure("Failed to mine a version=4 blocks") + 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 xrange(2): + for i in range(2): self.nodes[2].generate(99) self.sync_all() if (self.nodes[0].getblockcount() != cnt + 1049): 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..be6ddde112 --- /dev/null +++ b/qa/rpc-tests/bip9-softforks.py @@ -0,0 +1,239 @@ +#!/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) + 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 index ec1678cc2c..3bad5af5e6 100755 --- a/qa/rpc-tests/bipdersig-p2p.py +++ b/qa/rpc-tests/bipdersig-p2p.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python2 -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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 * @@ -10,8 +9,7 @@ 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 binascii import hexlify, unhexlify -import cStringIO +from io import BytesIO import time # A canonical signature consists of: @@ -25,7 +23,7 @@ def unDERify(tx): newscript = [] for i in scriptSig: if (len(newscript) == 0): - newscript.append(i[0:-1] + '\0' + i[-1]) + newscript.append(i[0:-1] + b'\0' + i[-1:]) else: newscript.append(i) tx.vin[0].scriptSig = CScript(newscript) @@ -47,11 +45,12 @@ 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(1, self.options.tmpdir, + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[['-debug', '-whitelist=127.0.0.1', '-blockversion=2']], binary=[self.options.testbinary]) @@ -68,7 +67,7 @@ class BIP66Test(ComparisonTestFramework): rawtx = node.createrawtransaction(inputs, outputs) signresult = node.signrawtransaction(rawtx) tx = CTransaction() - f = cStringIO.StringIO(unhexlify(signresult['hex'])) + f = BytesIO(hex_str_to_bytes(signresult['hex'])) tx.deserialize(f) return tx @@ -76,13 +75,13 @@ class BIP66Test(ComparisonTestFramework): 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() + "L", 0) + self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0) self.nodeaddress = self.nodes[0].getnewaddress() - self.last_block_time = time.time() + self.last_block_time = int(time.time()) - ''' 98 more version 2 blocks ''' + ''' 298 more version 2 blocks ''' test_blocks = [] - for i in xrange(98): + for i in range(298): block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 2 block.rehash() @@ -95,7 +94,7 @@ class BIP66Test(ComparisonTestFramework): ''' Mine 749 version 3 blocks ''' test_blocks = [] - for i in xrange(749): + for i in range(749): block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 3 block.rehash() @@ -125,29 +124,11 @@ class BIP66Test(ComparisonTestFramework): 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]]) + yield TestInstance([[block, True]]) ''' Mine 199 new version blocks on last valid tip ''' test_blocks = [] - for i in xrange(199): + for i in range(199): block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 3 block.rehash() @@ -178,6 +159,24 @@ class BIP66Test(ComparisonTestFramework): height += 1 yield TestInstance([[block, True]]) + ''' + Check that the new DERSIG rules are enforced in the 951st 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 1 old version block, should be invalid ''' block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 2 diff --git a/qa/rpc-tests/bipdersig.py b/qa/rpc-tests/bipdersig.py index 243f816f65..17c2ced79a 100755 --- a/qa/rpc-tests/bipdersig.py +++ b/qa/rpc-tests/bipdersig.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. @@ -9,10 +9,12 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import os -import shutil class BIP66Test(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 3 + self.setup_clean_chain = False def setup_network(self): self.nodes = [] @@ -34,7 +36,7 @@ class BIP66Test(BitcoinTestFramework): raise AssertionError("Failed to mine 100 version=2 blocks") # Mine 750 new-version blocks - for i in xrange(15): + for i in range(15): self.nodes[2].generate(50) self.sync_all() if (self.nodes[0].getblockcount() != cnt + 850): @@ -46,12 +48,12 @@ class BIP66Test(BitcoinTestFramework): self.nodes[2].generate(1) self.sync_all() if (self.nodes[0].getblockcount() != cnt + 851): - raise AssertionFailure("Failed to mine a version=3 blocks") + 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 xrange(2): + for i in range(2): self.nodes[2].generate(99) self.sync_all() if (self.nodes[0].getblockcount() != cnt + 1049): diff --git a/qa/rpc-tests/blockchain.py b/qa/rpc-tests/blockchain.py index b7bfe36285..410b85d15e 100755 --- a/qa/rpc-tests/blockchain.py +++ b/qa/rpc-tests/blockchain.py @@ -1,52 +1,89 @@ -#!/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 RPC calls related to blockchain state. +# Test RPC calls related to blockchain state. Tests correspond to code in +# rpc/blockchain.cpp. # -import decimal +from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework +from test_framework.authproxy import JSONRPCException from test_framework.util import ( - initialize_chain, 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 setup_chain(self): - print("Initializing test directory " + self.options.tmpdir) - initialize_chain(self.options.tmpdir) + def __init__(self): + super().__init__() + self.setup_clean_chain = False + self.num_nodes = 2 def setup_network(self, split=False): - self.nodes = start_nodes(2, self.options.tmpdir) + 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[u'total_amount'], decimal.Decimal('8725.00000000')) - assert_equal(res[u'transactions'], 200) - assert_equal(res[u'height'], 200) - assert_equal(res[u'txouts'], 200) - assert_equal(res[u'bytes_serialized'], 13924), - assert_equal(len(res[u'bestblock']), 64) - assert_equal(len(res[u'hash_serialized']), 64) + 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/create_cache.py b/qa/rpc-tests/create_cache.py new file mode 100755 index 0000000000..1ace6310d0 --- /dev/null +++ b/qa/rpc-tests/create_cache.py @@ -0,0 +1,29 @@ +#!/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 __init__(self): + super().__init__() + + # Test network and test nodes are not required: + self.num_nodes = 0 + self.nodes = [] + + def setup_network(self): + pass + + def run_test(self): + pass + +if __name__ == '__main__': + CreateCache().main() diff --git a/qa/rpc-tests/decodescript.py b/qa/rpc-tests/decodescript.py index 4bca623380..24768c2655 100755 --- a/qa/rpc-tests/decodescript.py +++ b/qa/rpc-tests/decodescript.py @@ -1,23 +1,23 @@ -#!/usr/bin/env python2 -# Copyright (c) 2015 The Bitcoin Core developers +#!/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 binascii import hexlify, unhexlify -from cStringIO import StringIO +from io import BytesIO class DecodeScriptTest(BitcoinTestFramework): """Tests decoding scripts via RPC command "decodescript".""" - def setup_chain(self): - print('Initializing test directory ' + self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 1) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 def setup_network(self, split=False): - self.nodes = start_nodes(1, self.options.tmpdir) + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir) self.is_network_split = False def decodescript_script_sig(self): @@ -102,13 +102,13 @@ class DecodeScriptTest(BitcoinTestFramework): # OP_IF # <receiver-pubkey> OP_CHECKSIGVERIFY # OP_ELSE - # <lock-until> OP_NOP2 OP_DROP + # <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_NOP2 OP_DROP OP_ENDIF ' + public_key + ' OP_CHECKSIG', rpc_result['asm']) + 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". @@ -131,7 +131,7 @@ class DecodeScriptTest(BitcoinTestFramework): 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(StringIO(unhexlify(tx))) + 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' @@ -147,7 +147,7 @@ class DecodeScriptTest(BitcoinTestFramework): # 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 = hexlify(txSave.vin[0].scriptSig)[2:(0x48*2+4)] + 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]' @@ -156,25 +156,24 @@ class DecodeScriptTest(BitcoinTestFramework): signature_2_sighash_decoded = der_signature + '[NONE|ANYONECANPAY]' # 1) P2PK scriptSig - txSave.vin[0].scriptSig = unhexlify(push_signature) - rpc_result = self.nodes[0].decoderawtransaction(hexlify(txSave.serialize())) + 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 = unhexlify(push_signature_2) - rpc_result = self.nodes[0].decoderawtransaction(hexlify(txSave.serialize())) + 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 = unhexlify('00' + push_signature + push_signature_2) - rpc_result = self.nodes[0].decoderawtransaction(hexlify(txSave.serialize())) + 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 = unhexlify('6a143011020701010101010101020601010101010101') - rpc_result = self.nodes[0].decoderawtransaction(hexlify(txSave.serialize())) - print(hexlify('636174')) + 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): diff --git a/qa/rpc-tests/disablewallet.py b/qa/rpc-tests/disablewallet.py index 4cb01575e2..36c147edad 100755 --- a/qa/rpc-tests/disablewallet.py +++ b/qa/rpc-tests/disablewallet.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python2 -# Copyright (c) 2014 The Bitcoin Core developers +#!/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. @@ -10,14 +10,16 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * + class DisableWalletTest (BitcoinTestFramework): - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 1) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 def setup_network(self, split=False): - self.nodes = start_nodes(1, self.options.tmpdir, [['-disablewallet']]) + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [['-disablewallet']]) self.is_network_split = False self.sync_all() @@ -28,5 +30,19 @@ class DisableWalletTest (BitcoinTestFramework): 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 0acef8e30b..a1901aedab 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. @@ -9,17 +9,20 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import os -import shutil 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): self.nodes = [] self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt") - with open(self.alert_filename, 'w') as f: + with open(self.alert_filename, 'w', encoding='utf8') as f: pass # Just open then close to create zero-length file self.nodes.append(start_node(0, self.options.tmpdir, ["-blockversion=2", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""])) @@ -41,7 +44,7 @@ class ForkNotifyTest(BitcoinTestFramework): self.nodes[1].generate(1) self.sync_all() - with open(self.alert_filename, 'r') as f: + with open(self.alert_filename, 'r', encoding='utf8') as f: alert_text = f.read() if len(alert_text) == 0: @@ -53,7 +56,7 @@ class ForkNotifyTest(BitcoinTestFramework): self.nodes[1].generate(1) self.sync_all() - with open(self.alert_filename, 'r') as f: + with open(self.alert_filename, 'r', encoding='utf8') as f: alert_text2 = f.read() if alert_text != alert_text2: diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index 93d13faa06..8c45578fcf 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -1,22 +1,28 @@ -#!/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.test_framework import BitcoinTestFramework from test_framework.util import * -from pprint import pprint -from time import sleep -# Create one-input, one-output, no-fee transaction: + +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 setup_chain(self): - print("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 def setup_network(self, split=False): - self.nodes = start_nodes(4, self.options.tmpdir) + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) @@ -27,9 +33,14 @@ class RawTransactionsTest(BitcoinTestFramework): self.sync_all() def run_test(self): - print "Mining blocks..." + 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 @@ -45,16 +56,15 @@ class RawTransactionsTest(BitcoinTestFramework): watchonly_address = self.nodes[0].getnewaddress() watchonly_pubkey = self.nodes[0].validateaddress(watchonly_address)["pubkey"] - watchonly_amount = 200 + 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[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].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(1) self.sync_all() @@ -68,7 +78,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - assert_equal(len(dec_tx['vin']) > 0, True) #test if we have enought inputs + assert(len(dec_tx['vin']) > 0) #test that we have enough inputs ############################## # simple test with two coins # @@ -81,7 +91,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - assert_equal(len(dec_tx['vin']) > 0, True) #test if we have enough inputs + assert(len(dec_tx['vin']) > 0) #test if we have enough inputs ############################## # simple test with two coins # @@ -94,7 +104,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - assert_equal(len(dec_tx['vin']) > 0, True) + assert(len(dec_tx['vin']) > 0) assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') @@ -113,21 +123,14 @@ class RawTransactionsTest(BitcoinTestFramework): for out in dec_tx['vout']: totalOut += out['value'] - assert_equal(len(dec_tx['vin']) > 0, True) + 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 = False - listunspent = self.nodes[2].listunspent() - for aUtx in listunspent: - if aUtx['amount'] == 5.0: - utx = aUtx - break; - - assert_equal(utx!=False, True) + utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] outputs = { self.nodes[0].getnewaddress() : 1.0 } @@ -145,18 +148,10 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee - ##################################################################### # test a fundrawtransaction with which will not get a change output # ##################################################################### - utx = False - listunspent = self.nodes[2].listunspent() - for aUtx in listunspent: - if aUtx['amount'] == 5.0: - utx = aUtx - break; - - assert_equal(utx!=False, True) + 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 } @@ -175,18 +170,70 @@ class RawTransactionsTest(BitcoinTestFramework): 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 = False - listunspent = self.nodes[2].listunspent() - for aUtx in listunspent: - if aUtx['amount'] == 1.0: - utx = aUtx - break; - - assert_equal(utx!=False, True) + utx = get_unspent(self.nodes[2].listunspent(), 1) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] outputs = { self.nodes[0].getnewaddress() : 1.0 } @@ -206,7 +253,7 @@ class RawTransactionsTest(BitcoinTestFramework): matchingOuts = 0 for i, out in enumerate(dec_tx['vout']): totalOut += out['value'] - if outputs.has_key(out['scriptPubKey']['addresses'][0]): + if out['scriptPubKey']['addresses'][0] in outputs: matchingOuts+=1 else: assert_equal(i, rawtxfund['changepos']) @@ -221,17 +268,8 @@ class RawTransactionsTest(BitcoinTestFramework): ########################################### # test a fundrawtransaction with two VINs # ########################################### - utx = False - utx2 = False - listunspent = self.nodes[2].listunspent() - for aUtx in listunspent: - if aUtx['amount'] == 1.0: - utx = aUtx - if aUtx['amount'] == 5.0: - utx2 = aUtx - - - assert_equal(utx!=False, True) + 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 } @@ -246,7 +284,7 @@ class RawTransactionsTest(BitcoinTestFramework): matchingOuts = 0 for out in dec_tx['vout']: totalOut += out['value'] - if outputs.has_key(out['scriptPubKey']['addresses'][0]): + if out['scriptPubKey']['addresses'][0] in outputs: matchingOuts+=1 assert_equal(matchingOuts, 1) @@ -263,17 +301,8 @@ class RawTransactionsTest(BitcoinTestFramework): ######################################################### # test a fundrawtransaction with two VINs and two vOUTs # ######################################################### - utx = False - utx2 = False - listunspent = self.nodes[2].listunspent() - for aUtx in listunspent: - if aUtx['amount'] == 1.0: - utx = aUtx - if aUtx['amount'] == 5.0: - utx2 = aUtx - - - assert_equal(utx!=False, True) + 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 } @@ -288,7 +317,7 @@ class RawTransactionsTest(BitcoinTestFramework): matchingOuts = 0 for out in dec_tx['vout']: totalOut += out['value'] - if outputs.has_key(out['scriptPubKey']['addresses'][0]): + if out['scriptPubKey']['addresses'][0] in outputs: matchingOuts+=1 assert_equal(matchingOuts, 2) @@ -303,14 +332,11 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawtx) - errorString = "" try: rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - except JSONRPCException,e: - errorString = e.error['message'] - - assert_equal("Insufficient" in errorString, True); - + raise AssertionError("Spent more than available") + except JSONRPCException as e: + assert("Insufficient" in e.error['message']) ############################################################ @@ -321,11 +347,11 @@ class RawTransactionsTest(BitcoinTestFramework): fundedTx = self.nodes[0].fundrawtransaction(rawTx) #create same transaction over sendtoaddress - txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1); + 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); + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert(feeDelta >= 0 and feeDelta <= feeTolerance) ############################################################ @@ -336,11 +362,11 @@ class RawTransactionsTest(BitcoinTestFramework): rawTx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawTx) #create same transaction over sendtoaddress - txId = self.nodes[0].sendmany("", outputs); + txId = self.nodes[0].sendmany("", outputs) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] #compare fee - feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert(feeDelta >= 0 and feeDelta <= feeTolerance) ############################################################ @@ -363,11 +389,11 @@ class RawTransactionsTest(BitcoinTestFramework): fundedTx = self.nodes[0].fundrawtransaction(rawTx) #create same transaction over sendtoaddress - txId = self.nodes[0].sendtoaddress(mSigObj, 1.1); + txId = self.nodes[0].sendtoaddress(mSigObj, 1.1) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] #compare fee - feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert(feeDelta >= 0 and feeDelta <= feeTolerance) ############################################################ @@ -396,11 +422,11 @@ class RawTransactionsTest(BitcoinTestFramework): fundedTx = self.nodes[0].fundrawtransaction(rawTx) #create same transaction over sendtoaddress - txId = self.nodes[0].sendtoaddress(mSigObj, 1.1); + txId = self.nodes[0].sendtoaddress(mSigObj, 1.1) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] #compare fee - feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert(feeDelta >= 0 and feeDelta <= feeTolerance) ############################################################ @@ -419,7 +445,7 @@ class RawTransactionsTest(BitcoinTestFramework): # send 1.2 BTC to msig addr - txId = self.nodes[0].sendtoaddress(mSigObj, 1.2); + txId = self.nodes[0].sendtoaddress(mSigObj, 1.2) self.sync_all() self.nodes[1].generate(1) self.sync_all() @@ -444,9 +470,12 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[1].encryptwallet("test") self.nodes.pop(1) stop_nodes(self.nodes) - wait_bitcoinds() - self.nodes = start_nodes(4, self.options.tmpdir) + 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) @@ -455,12 +484,11 @@ class RawTransactionsTest(BitcoinTestFramework): self.is_network_split=False self.sync_all() - error = False try: - self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.2); - except: - error = True - assert(error) + 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() @@ -473,7 +501,6 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[1].walletpassphrase("test", 100) signedTx = self.nodes[1].signrawtransaction(fundedTx['hex']) txId = self.nodes[1].sendrawtransaction(signedTx['hex']) - self.sync_all() self.nodes[1].generate(1) self.sync_all() @@ -481,20 +508,18 @@ class RawTransactionsTest(BitcoinTestFramework): 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.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.sync_all() + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01) self.nodes[0].generate(1) self.sync_all() @@ -505,11 +530,11 @@ class RawTransactionsTest(BitcoinTestFramework): fundedTx = self.nodes[1].fundrawtransaction(rawTx) #create same transaction over sendtoaddress - txId = self.nodes[1].sendmany("", outputs); + txId = self.nodes[1].sendmany("", outputs) signedFee = self.nodes[1].getrawmempool(True)[txId]['fee'] #compare fee - feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert(feeDelta >= 0 and feeDelta <= feeTolerance*19) #~19 inputs @@ -518,14 +543,13 @@ class RawTransactionsTest(BitcoinTestFramework): ############################################# #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.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.sync_all() + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01) self.nodes[0].generate(1) self.sync_all() @@ -568,12 +592,12 @@ class RawTransactionsTest(BitcoinTestFramework): outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) - result = self.nodes[3].fundrawtransaction(rawtx, True) + 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_equal("fee" in result.keys(), True) + assert("fee" in result.keys()) assert_greater_than(result["changepos"], -1) ############################################################### @@ -584,6 +608,7 @@ class RawTransactionsTest(BitcoinTestFramework): 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) @@ -598,7 +623,25 @@ class RawTransactionsTest(BitcoinTestFramework): 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 1ddff8a298..3cddf4046a 100755 --- a/qa/rpc-tests/getblocktemplate_longpoll.py +++ b/qa/rpc-tests/getblocktemplate_longpoll.py @@ -1,33 +1,11 @@ -#!/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.test_framework import BitcoinTestFramework from test_framework.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))) - import threading class LongpollThread(threading.Thread): @@ -48,8 +26,13 @@ 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." + 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'] diff --git a/qa/rpc-tests/getblocktemplate_proposals.py b/qa/rpc-tests/getblocktemplate_proposals.py index aca0cd7495..7a4f8f8fdc 100755 --- a/qa/rpc-tests/getblocktemplate_proposals.py +++ b/qa/rpc-tests/getblocktemplate_proposals.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. @@ -10,28 +10,6 @@ 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') @@ -68,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']) @@ -77,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'}) @@ -92,6 +70,15 @@ 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 @@ -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 6a2bcb2969..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. @@ -11,9 +11,12 @@ 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].generate(10); - self.nodes[2].generate(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 7888114c54..10bc927e1a 100755 --- a/qa/rpc-tests/httpbasics.py +++ b/qa/rpc-tests/httpbasics.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. @@ -9,106 +9,104 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import base64 -try: - import http.client as httplib -except ImportError: - import httplib -try: - import urllib.parse as urlparse -except ImportError: - import urlparse +import http.client +import urllib.parse class HTTPBasicsTest (BitcoinTestFramework): - def setup_nodes(self): - return start_nodes(4, self.options.tmpdir) + 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)} + headers = {"Authorization": "Basic " + str_to_b64str(authpair)} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"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! + 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('POST', '/', '{"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! + 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"} + headers = {"Authorization": "Basic " + str_to_b64str(authpair), "Connection": "keep-alive"} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"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! + 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('POST', '/', '{"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! + 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"} + headers = {"Authorization": "Basic " + str_to_b64str(authpair), "Connection":"close"} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"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 + 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 = urlparse.urlparse(self.nodes[1].url) + urlNode1 = urllib.parse.urlparse(self.nodes[1].url) authpair = urlNode1.username + ':' + urlNode1.password - headers = {"Authorization": "Basic " + base64.b64encode(authpair)} + headers = {"Authorization": "Basic " + str_to_b64str(authpair)} - conn = httplib.HTTPConnection(urlNode1.hostname, urlNode1.port) + conn = http.client.HTTPConnection(urlNode1.hostname, urlNode1.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - out1 = conn.getresponse().read(); - assert_equal('"error":null' in out1, True) + 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 = urlparse.urlparse(self.nodes[2].url) + urlNode2 = urllib.parse.urlparse(self.nodes[2].url) authpair = urlNode2.username + ':' + urlNode2.password - headers = {"Authorization": "Basic " + base64.b64encode(authpair)} + headers = {"Authorization": "Basic " + str_to_b64str(authpair)} - conn = httplib.HTTPConnection(urlNode2.hostname, urlNode2.port) + conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - out1 = conn.getresponse().read(); - assert_equal('"error":null' in out1, True) - assert_equal(conn.sock!=None, True) #connection must be closed because bitcoind should use keep-alive by default + 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 = httplib.HTTPConnection(urlNode2.hostname, urlNode2.port) + conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port) conn.connect() conn.request('GET', '/' + ('x'*1000), '', headers) out1 = conn.getresponse() - assert_equal(out1.status, httplib.NOT_FOUND) + assert_equal(out1.status, http.client.NOT_FOUND) - conn = httplib.HTTPConnection(urlNode2.hostname, urlNode2.port) + conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port) conn.connect() conn.request('GET', '/' + ('x'*10000), '', headers) out1 = conn.getresponse() - assert_equal(out1.status, httplib.BAD_REQUEST) + assert_equal(out1.status, http.client.BAD_REQUEST) if __name__ == '__main__': diff --git a/qa/rpc-tests/importprunedfunds.py b/qa/rpc-tests/importprunedfunds.py new file mode 100755 index 0000000000..0dee8ad4ec --- /dev/null +++ b/qa/rpc-tests/importprunedfunds.py @@ -0,0 +1,134 @@ +#!/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 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): + print("Mining blocks...") + self.nodes[0].generate(101) + + 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: + 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, "add2", False) + result2 = self.nodes[1].importprunedfunds(rawtxn2, proof2) + balance2 = self.nodes[1].getbalance("add2", 0, True) + assert_equal(balance2, Decimal('0.05')) + + #Import with private key with no rescan + self.nodes[1].importprivkey(address3_privkey, "add3", False) + result3 = self.nodes[1].importprunedfunds(rawtxn3, proof3) + balance3 = self.nodes[1].getbalance("add3", 0, False) + assert_equal(balance3, Decimal('0.025')) + balance3 = 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 = self.nodes[1].getbalance("*", 0, True) + assert_equal(balance1, Decimal('0.075')) + + self.nodes[1].removeprunedfunds(txnid2) + balance2 = self.nodes[1].getbalance("*", 0, True) + assert_equal(balance2, Decimal('0.025')) + + self.nodes[1].removeprunedfunds(txnid3) + balance3 = 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 index 2b9c8154e0..0faadd33ab 100755 --- a/qa/rpc-tests/invalidateblock.py +++ b/qa/rpc-tests/invalidateblock.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,10 +13,11 @@ from test_framework.util import * class InvalidateTest(BitcoinTestFramework): - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 3) - + 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 @@ -25,46 +26,46 @@ class InvalidateTest(BitcoinTestFramework): 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" + 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" + 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" + 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" + 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:" + 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" + 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" + 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" + 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" + print("..and then mine a block") self.nodes[2].generate(1) - print "Verify all nodes are at the right height" + print("Verify all nodes are at the right height") time.sleep(5) - for i in xrange(3): - print i,self.nodes[i].getblockcount() + 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() diff --git a/qa/rpc-tests/invalidblockrequest.py b/qa/rpc-tests/invalidblockrequest.py index 6a7980cd45..3d8107a76c 100755 --- a/qa/rpc-tests/invalidblockrequest.py +++ b/qa/rpc-tests/invalidblockrequest.py @@ -1,15 +1,12 @@ -#!/usr/bin/env python2 -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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 -from test_framework.mininode import * +from test_framework.comptool import TestManager, TestInstance, RejectResult from test_framework.blocktools import * -import logging import copy import time @@ -28,6 +25,7 @@ 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): @@ -40,7 +38,7 @@ class InvalidBlockRequestTest(ComparisonTestFramework): def get_tests(self): if self.tip is None: - self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0) + self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0) self.block_time = int(time.time())+1 ''' @@ -60,7 +58,7 @@ class InvalidBlockRequestTest(ComparisonTestFramework): Now we need that block to mature so we can spend the coinbase. ''' test = TestInstance(sync_every_block=False) - for i in xrange(100): + for i in range(100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() self.tip = block.sha256 @@ -79,9 +77,9 @@ class InvalidBlockRequestTest(ComparisonTestFramework): block2 = create_block(self.tip, create_coinbase(height), self.block_time) self.block_time += 1 - # chr(81) is OP_TRUE - tx1 = create_transaction(self.block1.vtx[0], 0, chr(81), 50*100000000) - tx2 = create_transaction(tx1, 0, chr(81), 50*100000000) + # 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() @@ -97,7 +95,7 @@ class InvalidBlockRequestTest(ComparisonTestFramework): assert(block2_orig.vtx != block2.vtx) self.tip = block2.sha256 - yield TestInstance([[block2, False], [block2_orig, True]]) + yield TestInstance([[block2, RejectResult(16, b'bad-txns-duplicate')], [block2_orig, True]]) height += 1 ''' @@ -105,14 +103,14 @@ class InvalidBlockRequestTest(ComparisonTestFramework): ''' block3 = create_block(self.tip, create_coinbase(height), self.block_time) self.block_time += 1 - block3.vtx[0].vout[0].nValue = 100*100000000 # Too high! + 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, False]]) + yield TestInstance([[block3, RejectResult(16, b'bad-cb-amount')]]) if __name__ == '__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 92d91e029a..fa39476568 100755 --- a/qa/rpc-tests/keypool.py +++ b/qa/rpc-tests/keypool.py @@ -1,41 +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. # Exercise the wallet keypool, and interaction with wallet encryption/locking -# Add python-bitcoinrpc to module search path: - from test_framework.test_framework import BitcoinTestFramework from test_framework.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))) - class KeyPoolTest(BitcoinTestFramework): def run_test(self): nodes = self.nodes + addr_before_encrypting = nodes[0].getnewaddress() + addr_before_encrypting_data = nodes[0].validateaddress(addr_before_encrypting) + wallet_info_old = nodes[0].getwalletinfo() + assert(addr_before_encrypting_data['hdmasterkeyid'] == wallet_info_old['hdmasterkeyid']) + # Encrypt wallet and wait to terminate nodes[0].encryptwallet('test') bitcoind_processes[0].wait() @@ -43,10 +24,15 @@ class KeyPoolTest(BitcoinTestFramework): nodes[0] = start_node(0, self.options.tmpdir) # Keep creating keys addr = nodes[0].getnewaddress() + addr_data = nodes[0].validateaddress(addr) + wallet_info = nodes[0].getwalletinfo() + assert(addr_before_encrypting_data['hdmasterkeyid'] != wallet_info['hdmasterkeyid']) + assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) + try: addr = nodes[0].getnewaddress() raise AssertionError('Keypool should be exhausted after one address') - except JSONRPCException,e: + except JSONRPCException as e: assert(e.error['code']==-12) # put three new keys in the keypool @@ -66,13 +52,15 @@ class KeyPoolTest(BitcoinTestFramework): try: addr = nodes[0].getrawchangeaddress() raise AssertionError('Keypool should be exhausted after three addresses') - except JSONRPCException,e: + except JSONRPCException as e: assert(e.error['code']==-12) # refill keypool with three new addresses - nodes[0].walletpassphrase('test', 12000) + nodes[0].walletpassphrase('test', 1) nodes[0].keypoolrefill(3) - nodes[0].walletlock() + # test walletpassphrase timeout + time.sleep(1.1) + assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0) # drain them by mining nodes[0].generate(1) @@ -82,15 +70,16 @@ class KeyPoolTest(BitcoinTestFramework): try: nodes[0].generate(1) raise AssertionError('Keypool should be exhausted after three addesses') - except JSONRPCException,e: + except JSONRPCException as e: assert(e.error['code']==-12) - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain(self.options.tmpdir) + def __init__(self): + super().__init__() + self.setup_clean_chain = False + self.num_nodes = 1 def setup_network(self): - self.nodes = start_nodes(1, self.options.tmpdir) + self.nodes = self.setup_nodes() if __name__ == '__main__': KeyPoolTest().main() diff --git a/qa/rpc-tests/listtransactions.py b/qa/rpc-tests/listtransactions.py index b30a6bc9d1..5ec6ce17e0 100755 --- a/qa/rpc-tests/listtransactions.py +++ b/qa/rpc-tests/listtransactions.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,52 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * +from test_framework.mininode import CTransaction, COIN +from io import BytesIO - -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 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].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")}) @@ -68,28 +63,28 @@ 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"} ) @@ -99,10 +94,111 @@ class ListTransactionsTest(BitcoinTestFramework): self.nodes[1].generate(1) self.sync_all() assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0) - check_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True), + 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 index a601147ce8..1df1c484be 100755 --- a/qa/rpc-tests/maxblocksinflight.py +++ b/qa/rpc-tests/maxblocksinflight.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python2 -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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 @@ -34,7 +33,6 @@ class TestManager(NodeConnCB): def __init__(self): NodeConnCB.__init__(self) self.log = logging.getLogger("BlockRelayTest") - self.create_callback_map() def add_new_connection(self, connection): self.connection = connection @@ -42,52 +40,49 @@ class TestManager(NodeConnCB): self.disconnectOkay = False def run(self): - try: - fail = False - 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.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)) - - # 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) - except AssertionError as e: - print "TEST FAILED: ", e.args + 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 setup_chain(self): - print "Initializing test directory "+self.options.tmpdir - initialize_chain_clean(self.options.tmpdir, 1) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 def setup_network(self): - self.nodes = start_nodes(1, self.options.tmpdir, + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[['-debug', '-whitelist=127.0.0.1']], binary=[self.options.testbinary]) diff --git a/qa/rpc-tests/maxuploadtarget.py b/qa/rpc-tests/maxuploadtarget.py index e714465db1..d0e9fe9a3f 100755 --- a/qa/rpc-tests/maxuploadtarget.py +++ b/qa/rpc-tests/maxuploadtarget.py @@ -1,13 +1,11 @@ -#!/usr/bin/env python2 -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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 * -from test_framework.comptool import wait_until import time ''' @@ -25,7 +23,6 @@ if uploadtarget has been reached. class TestNode(NodeConnCB): def __init__(self): NodeConnCB.__init__(self) - self.create_callback_map() self.connection = None self.ping_counter = 1 self.last_pong = msg_pong() @@ -78,47 +75,34 @@ class TestNode(NodeConnCB): 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) + success = wait_until(received_pong, timeout=timeout) self.ping_counter += 1 return success class MaxUploadTest(BitcoinTestFramework): - def __init__(self): - self.utxo = [] - - # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create - # So we have big transactions and full blocks to fill up our block files - # create one script_pubkey - script_pubkey = "6a4d0200" #OP_RETURN OP_PUSH2 512 bytes - for i in xrange (512): - script_pubkey = script_pubkey + "01" - # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change - self.txouts = "81" - for k in xrange(128): - # add txout value - self.txouts = self.txouts + "0000000000000000" - # add length of script_pubkey - self.txouts = self.txouts + "fd0402" - # add script_pubkey - self.txouts = self.txouts + script_pubkey def add_options(self, parser): parser.add_option("--testbinary", dest="testbinary", default=os.getenv("BITCOIND", "bitcoind"), help="bitcoind binary to test") - def setup_chain(self): - initialize_chain_clean(self.options.tmpdir, 2) + 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=200", "-blockmaxsize=999000"])) + 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 xrange(14): + for j in range(14): if len(self.utxo) < 14: self.utxo = node.listunspent() inputs=[] @@ -156,7 +140,7 @@ class MaxUploadTest(BitcoinTestFramework): test_nodes = [] connections = [] - for i in xrange(3): + 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]) @@ -191,14 +175,14 @@ class MaxUploadTest(BitcoinTestFramework): getdata_request = msg_getdata() getdata_request.inv.append(CInv(2, big_old_block)) - max_bytes_per_day = 200*1024*1024 - daily_buffer = 144 * 1000000 + 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 + success_count = max_bytes_available // old_block_size - # 144MB will be reserved for relaying new blocks, so expect this to - # succeed for ~70 tries. - for i in xrange(success_count): + # 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) @@ -206,22 +190,22 @@ class MaxUploadTest(BitcoinTestFramework): 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 xrange(3): + 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" + 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 200 times + # We'll try 800 times getdata_request.inv = [CInv(2, big_new_block)] - for i in xrange(200): + 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" + 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)] @@ -229,9 +213,9 @@ class MaxUploadTest(BitcoinTestFramework): 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("Peer 1 disconnected after trying to download old block") - print "Advancing system time on node to clear counters..." + 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. @@ -241,12 +225,12 @@ class MaxUploadTest(BitcoinTestFramework): 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" + 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" + 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"]) @@ -254,7 +238,7 @@ class MaxUploadTest(BitcoinTestFramework): test_nodes = [] connections = [] - for i in xrange(3): + 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]) @@ -264,7 +248,7 @@ class MaxUploadTest(BitcoinTestFramework): #retrieve 20 blocks which should be enough to break the 1MB limit getdata_request.inv = [CInv(2, big_new_block)] - for i in xrange(20): + 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) @@ -274,7 +258,7 @@ class MaxUploadTest(BitcoinTestFramework): 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)" + print("Peer 1 still connected after trying to download old block (whitelisted)") [c.disconnect_node() for c in connections] 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 index 34b316a6a3..45dc0e65c4 100755 --- a/qa/rpc-tests/mempool_packages.py +++ b/qa/rpc-tests/mempool_packages.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python2 -# Copyright (c) 2014-2015 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,11 +7,16 @@ 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 = [] @@ -27,7 +32,7 @@ class MempoolPackagesTest(BitcoinTestFramework): send_value = satoshi_round((value - fee)/num_outputs) inputs = [ {'txid' : parent_txid, 'vout' : vout} ] outputs = {} - for i in xrange(num_outputs): + for i in range(num_outputs): outputs[node.getnewaddress()] = send_value rawtx = node.createrawtransaction(inputs, outputs) signedtx = node.signrawtransaction(rawtx) @@ -47,7 +52,7 @@ class MempoolPackagesTest(BitcoinTestFramework): fee = Decimal("0.0001") # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] - for i in xrange(MAX_ANCESTORS): + 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) @@ -59,21 +64,81 @@ class MempoolPackagesTest(BitcoinTestFramework): descendant_count = 1 descendant_fees = 0 descendant_size = 0 - SATOSHIS = 100000000 + 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]['descendantfees'], SATOSHIS*descendant_fees) + 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" + 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 @@ -88,22 +153,22 @@ class MempoolPackagesTest(BitcoinTestFramework): # 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 xrange(10): + for i in range(10): transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value}) - for i in xrange(MAX_DESCENDANTS): + 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 xrange(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'] + print(e.error['message']) assert_equal(i, MAX_DESCENDANTS - 1) - print "tx that would create too large descendant package successfully rejected" + print("tx that would create too large descendant package successfully rejected") # TODO: check that node1's mempool is as expected @@ -138,7 +203,7 @@ class MempoolPackagesTest(BitcoinTestFramework): send_value = satoshi_round((value - fee)/2) inputs = [ {'txid' : txid, 'vout' : vout} ] outputs = {} - for i in xrange(2): + 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) @@ -152,7 +217,7 @@ class MempoolPackagesTest(BitcoinTestFramework): # Create tx2-7 vout = 1 txid = tx0_id - for i in xrange(6): + for i in range(6): (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1) vout = 0 value = sent_value diff --git a/qa/rpc-tests/mempool_reorg.py b/qa/rpc-tests/mempool_reorg.py index fdbaf689ad..301b094eb0 100755 --- a/qa/rpc-tests/mempool_reorg.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. @@ -10,11 +10,13 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import os -import shutil # 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 @@ -25,15 +27,7 @@ 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() @@ -54,12 +48,12 @@ class MempoolCoinbaseTest(BitcoinTestFramework): # and make sure the mempool code behaves correctly. 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[1], node1_address, 50) - spend_102_raw = self.create_tx(coinbase_txids[2], node0_address, 50) - spend_103_raw = self.create_tx(coinbase_txids[3], 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: 50}) + 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" @@ -73,8 +67,8 @@ class MempoolCoinbaseTest(BitcoinTestFramework): 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) @@ -87,11 +81,11 @@ class MempoolCoinbaseTest(BitcoinTestFramework): self.sync_all() - assert_equal(set(self.nodes[0].getrawmempool()), set([ spend_101_id, spend_102_1_id, timelock_tx_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()), set([ spend_101_id, spend_102_1_id, spend_103_1_id ])) + 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 19c74bb751..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. @@ -10,12 +10,15 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import os -import shutil # 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"] @@ -23,14 +26,6 @@ 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 @@ -45,13 +40,13 @@ 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].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].generate(1)) diff --git a/qa/rpc-tests/mempool_spendcoinbase.py b/qa/rpc-tests/mempool_spendcoinbase.py index fc17c50692..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. @@ -15,12 +15,15 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import os -import shutil # 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"] @@ -28,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) @@ -46,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]) diff --git a/qa/rpc-tests/merkle_blocks.py b/qa/rpc-tests/merkle_blocks.py index 72a80ce6ca..b2155d7fc3 100755 --- a/qa/rpc-tests/merkle_blocks.py +++ b/qa/rpc-tests/merkle_blocks.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. @@ -9,14 +9,13 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import os -import shutil class MerkleBlockTest(BitcoinTestFramework): - def setup_chain(self): - print("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 def setup_network(self): self.nodes = [] @@ -34,7 +33,7 @@ class MerkleBlockTest(BitcoinTestFramework): self.sync_all() def run_test(self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(105) self.sync_all() @@ -44,9 +43,9 @@ class MerkleBlockTest(BitcoinTestFramework): assert_equal(self.nodes[2].getbalance(), 0) node0utxos = self.nodes[0].listunspent(1) - tx1 = self.nodes[0].createrawtransaction([node0utxos.pop()], {self.nodes[1].getnewaddress(): 50}) + 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(): 50}) + 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]) @@ -64,7 +63,7 @@ class MerkleBlockTest(BitcoinTestFramework): 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(): 50}) + 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() @@ -72,7 +71,7 @@ class MerkleBlockTest(BitcoinTestFramework): txid_spent = txin_spent["txid"] txid_unspent = txid1 if txin_spent["txid"] != txid1 else txid2 - # We cant find the block from a fully-spent tx + # 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]) diff --git a/qa/rpc-tests/multi_rpc.py b/qa/rpc-tests/multi_rpc.py index 62071d426e..cdeb94caa0 100755 --- a/qa/rpc-tests/multi_rpc.py +++ b/qa/rpc-tests/multi_rpc.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python2 -# Copyright (c) 2015 The Bitcoin Core developers +#!/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. @@ -8,43 +8,42 @@ # from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * -import base64 - -try: - import http.client as httplib -except ImportError: - import httplib -try: - import urllib.parse as urlparse -except ImportError: - import urlparse +from test_framework.util import str_to_b64str, assert_equal + +import os +import http.client +import urllib.parse class HTTPBasicsTest (BitcoinTestFramework): - def setup_nodes(self): - return start_nodes(4, self.options.tmpdir) + + def __init__(self): + super().__init__() + self.setup_clean_chain = False + self.num_nodes = 1 def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain(self.options.tmpdir) + 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: + with open(os.path.join(self.options.tmpdir+"/node0", "bitcoin.conf"), 'a', encoding='utf8') 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 = urlparse.urlparse(self.nodes[0].url) + url = urllib.parse.urlparse(self.nodes[0].url) #Old authpair authpair = url.username + ':' + url.password - #New authpair generated via contrib/rpcuser tool + #New authpair generated via share/rpcuser tool rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144" password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" @@ -53,9 +52,9 @@ class HTTPBasicsTest (BitcoinTestFramework): password2 = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI=" authpairnew = "rt:"+password - headers = {"Authorization": "Basic " + base64.b64encode(authpair)} + headers = {"Authorization": "Basic " + str_to_b64str(authpair)} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) resp = conn.getresponse() @@ -63,9 +62,9 @@ class HTTPBasicsTest (BitcoinTestFramework): conn.close() #Use new authpair to confirm both work - headers = {"Authorization": "Basic " + base64.b64encode(authpairnew)} + headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) resp = conn.getresponse() @@ -74,9 +73,9 @@ class HTTPBasicsTest (BitcoinTestFramework): #Wrong login name with rt's password authpairnew = "rtwrong:"+password - headers = {"Authorization": "Basic " + base64.b64encode(authpairnew)} + headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) resp = conn.getresponse() @@ -85,9 +84,9 @@ class HTTPBasicsTest (BitcoinTestFramework): #Wrong password for rt authpairnew = "rt:"+password+"wrong" - headers = {"Authorization": "Basic " + base64.b64encode(authpairnew)} + headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) resp = conn.getresponse() @@ -96,9 +95,9 @@ class HTTPBasicsTest (BitcoinTestFramework): #Correct for rt2 authpairnew = "rt2:"+password2 - headers = {"Authorization": "Basic " + base64.b64encode(authpairnew)} + headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) resp = conn.getresponse() @@ -107,9 +106,9 @@ class HTTPBasicsTest (BitcoinTestFramework): #Wrong password for rt2 authpairnew = "rt2:"+password2+"wrong" - headers = {"Authorization": "Basic " + base64.b64encode(authpairnew)} + headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - conn = httplib.HTTPConnection(url.hostname, url.port) + conn = http.client.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) resp = conn.getresponse() @@ -117,6 +116,5 @@ class HTTPBasicsTest (BitcoinTestFramework): conn.close() - if __name__ == '__main__': HTTPBasicsTest ().main () diff --git a/qa/rpc-tests/nodehandling.py b/qa/rpc-tests/nodehandling.py index e383a3a12c..e9682c4908 100755 --- a/qa/rpc-tests/nodehandling.py +++ b/qa/rpc-tests/nodehandling.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. @@ -9,18 +9,17 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import base64 -try: - import http.client as httplib -except ImportError: - import httplib -try: - import urllib.parse as urlparse -except ImportError: - import urlparse +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 # @@ -54,7 +53,7 @@ class NodeHandlingTest (BitcoinTestFramework): 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(); + 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 @@ -62,7 +61,7 @@ class NodeHandlingTest (BitcoinTestFramework): stop_node(self.nodes[2], 2) self.nodes[2] = start_node(2, self.options.tmpdir) - listAfterShutdown = self.nodes[2].listbanned(); + 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) @@ -70,7 +69,7 @@ class NodeHandlingTest (BitcoinTestFramework): ########################### # RPC disconnectnode test # ########################### - url = urlparse.urlparse(self.nodes[1].url) + 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(): diff --git a/qa/rpc-tests/nulldummy.py b/qa/rpc-tests/nulldummy.py new file mode 100755 index 0000000000..54b7eac376 --- /dev/null +++ b/qa/rpc-tests/nulldummy.py @@ -0,0 +1,148 @@ +#!/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 * +from test_framework.mininode import CTransaction, NetworkThread +from test_framework.blocktools import create_coinbase, create_block, add_witness_commitment +from test_framework.script import CScript +from io import BytesIO +import time + +NULLDUMMY_ERROR = "64: non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)" + +def trueDummy(tx): + scriptSig = CScript(tx.vin[0].scriptSig) + newscript = [] + for i in scriptSig: + if (len(newscript) == 0): + assert(len(i) == 0) + newscript.append(b'\x51') + else: + newscript.append(i) + tx.vin[0].scriptSig = CScript(newscript) + tx.rehash() + +''' +This test is meant to exercise NULLDUMMY softfork. +Connect to a single node. +Generate 2 blocks (save the coinbases for later). +Generate 427 more blocks. +[Policy/Consensus] Check that NULLDUMMY compliant transactions are accepted in the 430th block. +[Policy] Check that non-NULLDUMMY transactions are rejected before activation. +[Consensus] Check that the new NULLDUMMY rules are not enforced on the 431st block. +[Policy/Consensus] Check that the new NULLDUMMY rules are enforced on the 432nd block. +''' + +class NULLDUMMYTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.num_nodes = 1 + self.setup_clean_chain = True + + 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', '-walletprematurewitness']]) + + def run_test(self): + self.address = self.nodes[0].getnewaddress() + self.ms_address = self.nodes[0].addmultisigaddress(1,[self.address]) + self.wit_address = self.nodes[0].addwitnessaddress(self.address) + self.wit_ms_address = self.nodes[0].addwitnessaddress(self.ms_address) + + NetworkThread().start() # Start up network handling in another thread + self.coinbase_blocks = self.nodes[0].generate(2) # Block 2 + coinbase_txid = [] + for i in self.coinbase_blocks: + coinbase_txid.append(self.nodes[0].getblock(i)['tx'][0]) + self.nodes[0].generate(427) # Block 429 + self.lastblockhash = self.nodes[0].getbestblockhash() + self.tip = int("0x" + self.lastblockhash, 0) + self.lastblockheight = 429 + self.lastblocktime = int(time.time()) + 429 + + print ("Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [430]") + test1txs = [self.create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, 49)] + txid1 = self.tx_submit(self.nodes[0], test1txs[0]) + test1txs.append(self.create_transaction(self.nodes[0], txid1, self.ms_address, 48)) + txid2 = self.tx_submit(self.nodes[0], test1txs[1]) + test1txs.append(self.create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, 49)) + txid3 = self.tx_submit(self.nodes[0], test1txs[2]) + self.block_submit(self.nodes[0], test1txs, False, True) + + print ("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation") + test2tx = self.create_transaction(self.nodes[0], txid2, self.ms_address, 48) + trueDummy(test2tx) + txid4 = self.tx_submit(self.nodes[0], test2tx, NULLDUMMY_ERROR) + + print ("Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [431]") + self.block_submit(self.nodes[0], [test2tx], False, True) + + print ("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation") + test4tx = self.create_transaction(self.nodes[0], txid4, self.address, 47) + test6txs=[CTransaction(test4tx)] + trueDummy(test4tx) + self.tx_submit(self.nodes[0], test4tx, NULLDUMMY_ERROR) + self.block_submit(self.nodes[0], [test4tx]) + + print ("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation") + test5tx = self.create_transaction(self.nodes[0], txid3, self.wit_address, 48) + test6txs.append(CTransaction(test5tx)) + test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01' + self.tx_submit(self.nodes[0], test5tx, NULLDUMMY_ERROR) + self.block_submit(self.nodes[0], [test5tx], True) + + print ("Test 6: NULLDUMMY compliant base/witness transactions should be accepted to mempool and in block after activation [432]") + for i in test6txs: + self.tx_submit(self.nodes[0], i) + self.block_submit(self.nodes[0], test6txs, True, True) + + + def create_transaction(self, node, txid, to_address, amount): + inputs = [{ "txid" : 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 tx_submit(self, node, tx, msg = ""): + tx.rehash() + try: + node.sendrawtransaction(bytes_to_hex_str(tx.serialize_with_witness()), True) + except JSONRPCException as exp: + assert_equal(exp.error["message"], msg) + else: + assert_equal('', msg) + return tx.hash + + + def block_submit(self, node, txs, witness = False, accept = False): + block = create_block(self.tip, create_coinbase(self.lastblockheight + 1), self.lastblocktime + 1) + block.nVersion = 4 + for tx in txs: + tx.rehash() + block.vtx.append(tx) + block.hashMerkleRoot = block.calc_merkle_root() + witness and add_witness_commitment(block) + block.rehash() + block.solve() + node.submitblock(bytes_to_hex_str(block.serialize(True))) + if (accept): + assert_equal(node.getbestblockhash(), block.hash) + self.tip = block.sha256 + self.lastblockhash = block.hash + self.lastblocktime += 1 + self.lastblockheight += 1 + else: + assert_equal(node.getbestblockhash(), self.lastblockhash) + +if __name__ == '__main__': + NULLDUMMYTest().main() diff --git a/qa/rpc-tests/p2p-acceptblock.py b/qa/rpc-tests/p2p-acceptblock.py index 700deab207..015ec34eff 100755 --- a/qa/rpc-tests/p2p-acceptblock.py +++ b/qa/rpc-tests/p2p-acceptblock.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python2 -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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 @@ -62,7 +61,6 @@ The test: class TestNode(NodeConnCB): def __init__(self): NodeConnCB.__init__(self) - self.create_callback_map() self.connection = None self.ping_counter = 1 self.last_pong = msg_pong() @@ -113,8 +111,10 @@ class AcceptBlockTest(BitcoinTestFramework): default=os.getenv("BITCOIND", "bitcoind"), help="bitcoind binary to test") - def setup_chain(self): - initialize_chain_clean(self.options.tmpdir, 2) + 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 @@ -146,13 +146,13 @@ class AcceptBlockTest(BitcoinTestFramework): # 1. Have both nodes mine a block (leave IBD) [ n.generate(1) for n in self.nodes ] - tips = [ int ("0x" + n.getbestblockhash() + "L", 0) 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 = time.time() + 1 - for i in xrange(2): + 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 @@ -162,11 +162,11 @@ class AcceptBlockTest(BitcoinTestFramework): [ 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" + 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 xrange(2): + 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])) @@ -181,11 +181,11 @@ class AcceptBlockTest(BitcoinTestFramework): if x['hash'] == blocks_h2f[1].hash: assert_equal(x['status'], "valid-headers") - print "Second height 2 block accepted only from whitelisted peer" + 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 xrange(2): + 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])) @@ -201,13 +201,13 @@ class AcceptBlockTest(BitcoinTestFramework): # 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" + 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" + 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 @@ -215,8 +215,8 @@ class AcceptBlockTest(BitcoinTestFramework): tips = blocks_h3 headers_message = msg_headers() all_blocks = [] # node0's blocks - for j in xrange(2): - for i in xrange(288): + 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: @@ -234,7 +234,7 @@ class AcceptBlockTest(BitcoinTestFramework): 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" + print("Unrequested block too far-ahead not processed") else: raise AssertionError("Unrequested block with more work should have been accepted") @@ -244,7 +244,7 @@ class AcceptBlockTest(BitcoinTestFramework): try: white_node.sync_with_ping() self.nodes[1].getblock(tips[1].hash) - print "Unrequested block far ahead of tip accepted from whitelisted peer" + print("Unrequested block far ahead of tip accepted from whitelisted peer") except: raise AssertionError("Unrequested block from whitelisted peer not accepted") @@ -260,7 +260,7 @@ class AcceptBlockTest(BitcoinTestFramework): # 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" + 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 @@ -276,14 +276,14 @@ class AcceptBlockTest(BitcoinTestFramework): # 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" + 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" + print("Successfully reorged to longer chain from non-whitelisted peer") [ c.disconnect_node() for c in connections ] diff --git a/qa/rpc-tests/p2p-compactblocks.py b/qa/rpc-tests/p2p-compactblocks.py new file mode 100755 index 0000000000..131654c333 --- /dev/null +++ b/qa/rpc-tests/p2p-compactblocks.py @@ -0,0 +1,852 @@ +#!/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.blocktools import create_block, create_coinbase, add_witness_commitment +from test_framework.siphash import siphash256 +from test_framework.script import CScript, OP_TRUE + +''' +CompactBlocksTest -- test compact blocks (BIP 152) + +Version 1 compact blocks are pre-segwit (txids) +Version 2 compact blocks are post-segwit (wtxids) +''' + +# TestNode: A peer we use to send messages to bitcoind, and store responses. +class TestNode(SingleNodeConnCB): + def __init__(self): + SingleNodeConnCB.__init__(self) + self.last_sendcmpct = [] + self.last_headers = None + self.last_inv = None + self.last_cmpctblock = None + self.block_announced = False + self.last_getdata = None + self.last_getblocktxn = None + self.last_block = None + self.last_blocktxn = None + # Store the hashes of blocks we've seen announced. + # This is for synchronizing the p2p message traffic, + # so we can eg wait until a particular block is announced. + self.set_announced_blockhashes = set() + + def on_sendcmpct(self, conn, message): + self.last_sendcmpct.append(message) + + def on_block(self, conn, message): + self.last_block = message + + def on_cmpctblock(self, conn, message): + self.last_cmpctblock = message + self.block_announced = True + self.last_cmpctblock.header_and_shortids.header.calc_sha256() + self.set_announced_blockhashes.add(self.last_cmpctblock.header_and_shortids.header.sha256) + + def on_headers(self, conn, message): + self.last_headers = message + self.block_announced = True + for x in self.last_headers.headers: + x.calc_sha256() + self.set_announced_blockhashes.add(x.sha256) + + def on_inv(self, conn, message): + self.last_inv = message + for x in self.last_inv.inv: + if x.type == 2: + self.block_announced = True + self.set_announced_blockhashes.add(x.hash) + + def on_getdata(self, conn, message): + self.last_getdata = message + + def on_getblocktxn(self, conn, message): + self.last_getblocktxn = message + + def on_blocktxn(self, conn, message): + self.last_blocktxn = message + + # Requires caller to hold mininode_lock + def received_block_announcement(self): + return self.block_announced + + def clear_block_announcement(self): + with mininode_lock: + self.block_announced = False + self.last_inv = None + self.last_headers = None + self.last_cmpctblock = None + + def get_headers(self, locator, hashstop): + msg = msg_getheaders() + msg.locator.vHave = locator + msg.hashstop = hashstop + self.connection.send_message(msg) + + 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 request_headers_and_sync(self, locator, hashstop=0): + self.clear_block_announcement() + self.get_headers(locator, hashstop) + assert(wait_until(self.received_block_announcement, timeout=30)) + assert(self.received_block_announcement()) + self.clear_block_announcement() + + # Block until a block announcement for a particular block hash is + # received. + def wait_for_block_announcement(self, block_hash, timeout=30): + def received_hash(): + return (block_hash in self.set_announced_blockhashes) + return wait_until(received_hash, timeout=timeout) + +class CompactBlocksTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.setup_clean_chain = True + # Node0 = pre-segwit, node1 = segwit-aware + self.num_nodes = 2 + self.utxos = [] + + def setup_network(self): + self.nodes = [] + + # Start up node0 to be a version 1, pre-segwit node. + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, + [["-debug", "-logtimemicros=1", "-bip9params=segwit:0:0"], + ["-debug", "-logtimemicros", "-txindex"]]) + connect_nodes(self.nodes[0], 1) + + def build_block_on_tip(self, node, segwit=False): + height = node.getblockcount() + tip = node.getbestblockhash() + mtp = node.getblockheader(tip)['mediantime'] + block = create_block(int(tip, 16), create_coinbase(height + 1), mtp + 1) + block.nVersion = 4 + if segwit: + add_witness_commitment(block) + block.solve() + return block + + # Create 10 more anyone-can-spend utxo's for testing. + def make_utxos(self): + # Doesn't matter which node we use, just use node0. + block = self.build_block_on_tip(self.nodes[0]) + self.test_node.send_and_ping(msg_block(block)) + assert(int(self.nodes[0].getbestblockhash(), 16) == block.sha256) + self.nodes[0].generate(100) + + total_value = block.vtx[0].vout[0].nValue + out_value = total_value // 10 + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(block.vtx[0].sha256, 0), b'')) + for i in range(10): + tx.vout.append(CTxOut(out_value, CScript([OP_TRUE]))) + tx.rehash() + + block2 = self.build_block_on_tip(self.nodes[0]) + block2.vtx.append(tx) + block2.hashMerkleRoot = block2.calc_merkle_root() + block2.solve() + self.test_node.send_and_ping(msg_block(block2)) + assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256) + self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)]) + return + + # Test "sendcmpct" (between peers preferring the same version): + # - No compact block announcements unless sendcmpct is sent. + # - If sendcmpct is sent with version > preferred_version, the message is ignored. + # - If sendcmpct is sent with boolean 0, then block announcements are not + # made with compact blocks. + # - If sendcmpct is then sent with boolean 1, then new block announcements + # are made with compact blocks. + # If old_node is passed in, request compact blocks with version=preferred-1 + # and verify that it receives block announcements via compact block. + def test_sendcmpct(self, node, test_node, preferred_version, old_node=None): + # Make sure we get a SENDCMPCT message from our peer + def received_sendcmpct(): + return (len(test_node.last_sendcmpct) > 0) + got_message = wait_until(received_sendcmpct, timeout=30) + assert(received_sendcmpct()) + assert(got_message) + with mininode_lock: + # Check that the first version received is the preferred one + assert_equal(test_node.last_sendcmpct[0].version, preferred_version) + # And that we receive versions down to 1. + assert_equal(test_node.last_sendcmpct[-1].version, 1) + test_node.last_sendcmpct = [] + + tip = int(node.getbestblockhash(), 16) + + def check_announcement_of_new_block(node, peer, predicate): + peer.clear_block_announcement() + node.generate(1) + got_message = wait_until(lambda: peer.block_announced, timeout=30) + assert(peer.block_announced) + assert(got_message) + with mininode_lock: + assert(predicate(peer)) + + # We shouldn't get any block announcements via cmpctblock yet. + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None) + + # Try one more time, this time after requesting headers. + test_node.request_headers_and_sync(locator=[tip]) + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None and p.last_inv is not None) + + # Test a few ways of using sendcmpct that should NOT + # result in compact block announcements. + # Before each test, sync the headers chain. + test_node.request_headers_and_sync(locator=[tip]) + + # Now try a SENDCMPCT message with too-high version + sendcmpct = msg_sendcmpct() + sendcmpct.version = preferred_version+1 + sendcmpct.announce = True + test_node.send_and_ping(sendcmpct) + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None) + + # Headers sync before next test. + test_node.request_headers_and_sync(locator=[tip]) + + # Now try a SENDCMPCT message with valid version, but announce=False + sendcmpct.version = preferred_version + sendcmpct.announce = False + test_node.send_and_ping(sendcmpct) + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None) + + # Headers sync before next test. + test_node.request_headers_and_sync(locator=[tip]) + + # Finally, try a SENDCMPCT message with announce=True + sendcmpct.version = preferred_version + sendcmpct.announce = True + test_node.send_and_ping(sendcmpct) + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is not None) + + # Try one more time (no headers sync should be needed!) + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is not None) + + # Try one more time, after turning on sendheaders + test_node.send_and_ping(msg_sendheaders()) + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is not None) + + # Try one more time, after sending a version-1, announce=false message. + sendcmpct.version = preferred_version-1 + sendcmpct.announce = False + test_node.send_and_ping(sendcmpct) + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is not None) + + # Now turn off announcements + sendcmpct.version = preferred_version + sendcmpct.announce = False + test_node.send_and_ping(sendcmpct) + check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None and p.last_headers is not None) + + if old_node is not None: + # Verify that a peer using an older protocol version can receive + # announcements from this node. + sendcmpct.version = preferred_version-1 + sendcmpct.announce = True + old_node.send_and_ping(sendcmpct) + # Header sync + old_node.request_headers_and_sync(locator=[tip]) + check_announcement_of_new_block(node, old_node, lambda p: p.last_cmpctblock is not None) + + # This test actually causes bitcoind to (reasonably!) disconnect us, so do this last. + def test_invalid_cmpctblock_message(self): + self.nodes[0].generate(101) + block = self.build_block_on_tip(self.nodes[0]) + + cmpct_block = P2PHeaderAndShortIDs() + cmpct_block.header = CBlockHeader(block) + cmpct_block.prefilled_txn_length = 1 + # This index will be too high + prefilled_txn = PrefilledTransaction(1, block.vtx[0]) + cmpct_block.prefilled_txn = [prefilled_txn] + self.test_node.send_and_ping(msg_cmpctblock(cmpct_block)) + assert(int(self.nodes[0].getbestblockhash(), 16) == block.hashPrevBlock) + + # Compare the generated shortids to what we expect based on BIP 152, given + # bitcoind's choice of nonce. + def test_compactblock_construction(self, node, test_node, version, use_witness_address): + # Generate a bunch of transactions. + node.generate(101) + num_transactions = 25 + address = node.getnewaddress() + if use_witness_address: + # Want at least one segwit spend, so move all funds to + # a witness address. + address = node.addwitnessaddress(address) + value_to_send = node.getbalance() + node.sendtoaddress(address, satoshi_round(value_to_send-Decimal(0.1))) + node.generate(1) + + segwit_tx_generated = False + for i in range(num_transactions): + txid = node.sendtoaddress(address, 0.1) + hex_tx = node.gettransaction(txid)["hex"] + tx = FromHex(CTransaction(), hex_tx) + if not tx.wit.is_null(): + segwit_tx_generated = True + + if use_witness_address: + assert(segwit_tx_generated) # check that our test is not broken + + # Wait until we've seen the block announcement for the resulting tip + tip = int(self.nodes[0].getbestblockhash(), 16) + assert(self.test_node.wait_for_block_announcement(tip)) + + # Now mine a block, and look at the resulting compact block. + test_node.clear_block_announcement() + block_hash = int(node.generate(1)[0], 16) + + # Store the raw block in our internal format. + block = FromHex(CBlock(), node.getblock("%02x" % block_hash, False)) + [tx.calc_sha256() for tx in block.vtx] + block.rehash() + + # Don't care which type of announcement came back for this test; just + # request the compact block if we didn't get one yet. + wait_until(test_node.received_block_announcement, timeout=30) + assert(test_node.received_block_announcement()) + + with mininode_lock: + if test_node.last_cmpctblock is None: + test_node.clear_block_announcement() + inv = CInv(4, block_hash) # 4 == "CompactBlock" + test_node.send_message(msg_getdata([inv])) + + wait_until(test_node.received_block_announcement, timeout=30) + assert(test_node.received_block_announcement()) + + # Now we should have the compactblock + header_and_shortids = None + with mininode_lock: + assert(test_node.last_cmpctblock is not None) + # Convert the on-the-wire representation to absolute indexes + header_and_shortids = HeaderAndShortIDs(test_node.last_cmpctblock.header_and_shortids) + + # Check that we got the right block! + header_and_shortids.header.calc_sha256() + assert_equal(header_and_shortids.header.sha256, block_hash) + + # Make sure the prefilled_txn appears to have included the coinbase + assert(len(header_and_shortids.prefilled_txn) >= 1) + assert_equal(header_and_shortids.prefilled_txn[0].index, 0) + + # Check that all prefilled_txn entries match what's in the block. + for entry in header_and_shortids.prefilled_txn: + entry.tx.calc_sha256() + # This checks the non-witness parts of the tx agree + assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256) + + # And this checks the witness + wtxid = entry.tx.calc_sha256(True) + if version == 2: + assert_equal(wtxid, block.vtx[entry.index].calc_sha256(True)) + else: + # Shouldn't have received a witness + assert(entry.tx.wit.is_null()) + + # Check that the cmpctblock message announced all the transactions. + assert_equal(len(header_and_shortids.prefilled_txn) + len(header_and_shortids.shortids), len(block.vtx)) + + # And now check that all the shortids are as expected as well. + # Determine the siphash keys to use. + [k0, k1] = header_and_shortids.get_siphash_keys() + + index = 0 + while index < len(block.vtx): + if (len(header_and_shortids.prefilled_txn) > 0 and + header_and_shortids.prefilled_txn[0].index == index): + # Already checked prefilled transactions above + header_and_shortids.prefilled_txn.pop(0) + else: + tx_hash = block.vtx[index].sha256 + if version == 2: + tx_hash = block.vtx[index].calc_sha256(True) + shortid = calculate_shortid(k0, k1, tx_hash) + assert_equal(shortid, header_and_shortids.shortids[0]) + header_and_shortids.shortids.pop(0) + index += 1 + + # Test that bitcoind requests compact blocks when we announce new blocks + # via header or inv, and that responding to getblocktxn causes the block + # to be successfully reconstructed. + # Post-segwit: upgraded nodes would only make this request of cb-version-2, + # NODE_WITNESS peers. Unupgraded nodes would still make this request of + # any cb-version-1-supporting peer. + def test_compactblock_requests(self, node, test_node, version, segwit): + # Try announcing a block with an inv or header, expect a compactblock + # request + for announce in ["inv", "header"]: + block = self.build_block_on_tip(node, segwit=segwit) + with mininode_lock: + test_node.last_getdata = None + + if announce == "inv": + test_node.send_message(msg_inv([CInv(2, block.sha256)])) + else: + test_node.send_header_for_blocks([block]) + success = wait_until(lambda: test_node.last_getdata is not None, timeout=30) + assert(success) + assert_equal(len(test_node.last_getdata.inv), 1) + assert_equal(test_node.last_getdata.inv[0].type, 4) + assert_equal(test_node.last_getdata.inv[0].hash, block.sha256) + + # Send back a compactblock message that omits the coinbase + comp_block = HeaderAndShortIDs() + comp_block.header = CBlockHeader(block) + comp_block.nonce = 0 + [k0, k1] = comp_block.get_siphash_keys() + coinbase_hash = block.vtx[0].sha256 + if version == 2: + coinbase_hash = block.vtx[0].calc_sha256(True) + comp_block.shortids = [ + calculate_shortid(k0, k1, coinbase_hash) ] + test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) + assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) + # Expect a getblocktxn message. + with mininode_lock: + assert(test_node.last_getblocktxn is not None) + absolute_indexes = test_node.last_getblocktxn.block_txn_request.to_absolute() + assert_equal(absolute_indexes, [0]) # should be a coinbase request + + # Send the coinbase, and verify that the tip advances. + if version == 2: + msg = msg_witness_blocktxn() + else: + msg = msg_blocktxn() + msg.block_transactions.blockhash = block.sha256 + msg.block_transactions.transactions = [block.vtx[0]] + test_node.send_and_ping(msg) + assert_equal(int(node.getbestblockhash(), 16), block.sha256) + + # Create a chain of transactions from given utxo, and add to a new block. + def build_block_with_transactions(self, node, utxo, num_transactions): + block = self.build_block_on_tip(node) + + for i in range(num_transactions): + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(utxo[0], utxo[1]), b'')) + tx.vout.append(CTxOut(utxo[2] - 1000, CScript([OP_TRUE]))) + tx.rehash() + utxo = [tx.sha256, 0, tx.vout[0].nValue] + block.vtx.append(tx) + + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + return block + + # Test that we only receive getblocktxn requests for transactions that the + # node needs, and that responding to them causes the block to be + # reconstructed. + def test_getblocktxn_requests(self, node, test_node, version): + with_witness = (version==2) + + def test_getblocktxn_response(compact_block, peer, expected_result): + msg = msg_cmpctblock(compact_block.to_p2p()) + peer.send_and_ping(msg) + with mininode_lock: + assert(peer.last_getblocktxn is not None) + absolute_indexes = peer.last_getblocktxn.block_txn_request.to_absolute() + assert_equal(absolute_indexes, expected_result) + + def test_tip_after_message(node, peer, msg, tip): + peer.send_and_ping(msg) + assert_equal(int(node.getbestblockhash(), 16), tip) + + # First try announcing compactblocks that won't reconstruct, and verify + # that we receive getblocktxn messages back. + utxo = self.utxos.pop(0) + + block = self.build_block_with_transactions(node, utxo, 5) + self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) + comp_block = HeaderAndShortIDs() + comp_block.initialize_from_block(block, use_witness=with_witness) + + test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5]) + + msg_bt = msg_blocktxn() + if with_witness: + msg_bt = msg_witness_blocktxn() # serialize with witnesses + msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:]) + test_tip_after_message(node, test_node, msg_bt, block.sha256) + + utxo = self.utxos.pop(0) + block = self.build_block_with_transactions(node, utxo, 5) + self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) + + # Now try interspersing the prefilled transactions + comp_block.initialize_from_block(block, prefill_list=[0, 1, 5], use_witness=with_witness) + test_getblocktxn_response(comp_block, test_node, [2, 3, 4]) + msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[2:5]) + test_tip_after_message(node, test_node, msg_bt, block.sha256) + + # Now try giving one transaction ahead of time. + utxo = self.utxos.pop(0) + block = self.build_block_with_transactions(node, utxo, 5) + self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) + test_node.send_and_ping(msg_tx(block.vtx[1])) + assert(block.vtx[1].hash in node.getrawmempool()) + + # Prefill 4 out of the 6 transactions, and verify that only the one + # that was not in the mempool is requested. + comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4], use_witness=with_witness) + test_getblocktxn_response(comp_block, test_node, [5]) + + msg_bt.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]]) + test_tip_after_message(node, test_node, msg_bt, block.sha256) + + # Now provide all transactions to the node before the block is + # announced and verify reconstruction happens immediately. + utxo = self.utxos.pop(0) + block = self.build_block_with_transactions(node, utxo, 10) + self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) + for tx in block.vtx[1:]: + test_node.send_message(msg_tx(tx)) + test_node.sync_with_ping() + # Make sure all transactions were accepted. + mempool = node.getrawmempool() + for tx in block.vtx[1:]: + assert(tx.hash in mempool) + + # Clear out last request. + with mininode_lock: + test_node.last_getblocktxn = None + + # Send compact block + comp_block.initialize_from_block(block, prefill_list=[0], use_witness=with_witness) + test_tip_after_message(node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256) + with mininode_lock: + # Shouldn't have gotten a request for any transaction + assert(test_node.last_getblocktxn is None) + + # Incorrectly responding to a getblocktxn shouldn't cause the block to be + # permanently failed. + def test_incorrect_blocktxn_response(self, node, test_node, version): + if (len(self.utxos) == 0): + self.make_utxos() + utxo = self.utxos.pop(0) + + block = self.build_block_with_transactions(node, utxo, 10) + self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) + # Relay the first 5 transactions from the block in advance + for tx in block.vtx[1:6]: + test_node.send_message(msg_tx(tx)) + test_node.sync_with_ping() + # Make sure all transactions were accepted. + mempool = node.getrawmempool() + for tx in block.vtx[1:6]: + assert(tx.hash in mempool) + + # Send compact block + comp_block = HeaderAndShortIDs() + comp_block.initialize_from_block(block, prefill_list=[0], use_witness=(version == 2)) + test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) + absolute_indexes = [] + with mininode_lock: + assert(test_node.last_getblocktxn is not None) + absolute_indexes = test_node.last_getblocktxn.block_txn_request.to_absolute() + assert_equal(absolute_indexes, [6, 7, 8, 9, 10]) + + # Now give an incorrect response. + # Note that it's possible for bitcoind to be smart enough to know we're + # lying, since it could check to see if the shortid matches what we're + # sending, and eg disconnect us for misbehavior. If that behavior + # change were made, we could just modify this test by having a + # different peer provide the block further down, so that we're still + # verifying that the block isn't marked bad permanently. This is good + # enough for now. + msg = msg_blocktxn() + if version==2: + msg = msg_witness_blocktxn() + msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:]) + test_node.send_and_ping(msg) + + # Tip should not have updated + assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) + + # We should receive a getdata request + success = wait_until(lambda: test_node.last_getdata is not None, timeout=10) + assert(success) + assert_equal(len(test_node.last_getdata.inv), 1) + assert(test_node.last_getdata.inv[0].type == 2 or test_node.last_getdata.inv[0].type == 2|MSG_WITNESS_FLAG) + assert_equal(test_node.last_getdata.inv[0].hash, block.sha256) + + # Deliver the block + if version==2: + test_node.send_and_ping(msg_witness_block(block)) + else: + test_node.send_and_ping(msg_block(block)) + assert_equal(int(node.getbestblockhash(), 16), block.sha256) + + def test_getblocktxn_handler(self, node, test_node, version): + # bitcoind won't respond for blocks whose height is more than 15 blocks + # deep. + MAX_GETBLOCKTXN_DEPTH = 15 + chain_height = node.getblockcount() + current_height = chain_height + while (current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH): + block_hash = node.getblockhash(current_height) + block = FromHex(CBlock(), node.getblock(block_hash, False)) + + msg = msg_getblocktxn() + msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), []) + num_to_request = random.randint(1, len(block.vtx)) + msg.block_txn_request.from_absolute(sorted(random.sample(range(len(block.vtx)), num_to_request))) + test_node.send_message(msg) + success = wait_until(lambda: test_node.last_blocktxn is not None, timeout=10) + assert(success) + + [tx.calc_sha256() for tx in block.vtx] + with mininode_lock: + assert_equal(test_node.last_blocktxn.block_transactions.blockhash, int(block_hash, 16)) + all_indices = msg.block_txn_request.to_absolute() + for index in all_indices: + tx = test_node.last_blocktxn.block_transactions.transactions.pop(0) + tx.calc_sha256() + assert_equal(tx.sha256, block.vtx[index].sha256) + if version == 1: + # Witnesses should have been stripped + assert(tx.wit.is_null()) + else: + # Check that the witness matches + assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True)) + test_node.last_blocktxn = None + current_height -= 1 + + # Next request should be ignored, as we're past the allowed depth. + block_hash = node.getblockhash(current_height) + msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [0]) + test_node.send_and_ping(msg) + with mininode_lock: + assert_equal(test_node.last_blocktxn, None) + + def test_compactblocks_not_at_tip(self, node, test_node): + # Test that requesting old compactblocks doesn't work. + MAX_CMPCTBLOCK_DEPTH = 11 + new_blocks = [] + for i in range(MAX_CMPCTBLOCK_DEPTH): + test_node.clear_block_announcement() + new_blocks.append(node.generate(1)[0]) + wait_until(test_node.received_block_announcement, timeout=30) + + test_node.clear_block_announcement() + test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) + success = wait_until(lambda: test_node.last_cmpctblock is not None, timeout=30) + assert(success) + + test_node.clear_block_announcement() + node.generate(1) + wait_until(test_node.received_block_announcement, timeout=30) + test_node.clear_block_announcement() + test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) + success = wait_until(lambda: test_node.last_block is not None, timeout=30) + assert(success) + with mininode_lock: + test_node.last_block.block.calc_sha256() + assert_equal(test_node.last_block.block.sha256, int(new_blocks[0], 16)) + + # Generate an old compactblock, and verify that it's not accepted. + cur_height = node.getblockcount() + hashPrevBlock = int(node.getblockhash(cur_height-5), 16) + block = self.build_block_on_tip(node) + block.hashPrevBlock = hashPrevBlock + block.solve() + + comp_block = HeaderAndShortIDs() + comp_block.initialize_from_block(block) + test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) + + tips = node.getchaintips() + found = False + for x in tips: + if x["hash"] == block.hash: + assert_equal(x["status"], "headers-only") + found = True + break + assert(found) + + # Requesting this block via getblocktxn should silently fail + # (to avoid fingerprinting attacks). + msg = msg_getblocktxn() + msg.block_txn_request = BlockTransactionsRequest(block.sha256, [0]) + with mininode_lock: + test_node.last_blocktxn = None + test_node.send_and_ping(msg) + with mininode_lock: + assert(test_node.last_blocktxn is None) + + def activate_segwit(self, node): + node.generate(144*3) + assert_equal(get_bip9_status(node, "segwit")["status"], 'active') + + def test_end_to_end_block_relay(self, node, listeners): + utxo = self.utxos.pop(0) + + block = self.build_block_with_transactions(node, utxo, 10) + + [l.clear_block_announcement() for l in listeners] + + # ToHex() won't serialize with witness, but this block has no witnesses + # anyway. TODO: repeat this test with witness tx's to a segwit node. + node.submitblock(ToHex(block)) + + for l in listeners: + wait_until(lambda: l.received_block_announcement(), timeout=30) + with mininode_lock: + for l in listeners: + assert(l.last_cmpctblock is not None) + l.last_cmpctblock.header_and_shortids.header.calc_sha256() + assert_equal(l.last_cmpctblock.header_and_shortids.header.sha256, block.sha256) + + # Helper for enabling cb announcements + # Send the sendcmpct request and sync headers + def request_cb_announcements(self, peer, node, version): + tip = node.getbestblockhash() + peer.get_headers(locator=[int(tip, 16)], hashstop=0) + + msg = msg_sendcmpct() + msg.version = version + msg.announce = True + peer.send_and_ping(msg) + + + def run_test(self): + # Setup the p2p connections and start up the network thread. + self.test_node = TestNode() + self.segwit_node = TestNode() + self.old_node = TestNode() # version 1 peer <--> segwit node + + connections = [] + connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.test_node)) + connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], + self.segwit_node, services=NODE_NETWORK|NODE_WITNESS)) + connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], + self.old_node, services=NODE_NETWORK)) + self.test_node.add_connection(connections[0]) + self.segwit_node.add_connection(connections[1]) + self.old_node.add_connection(connections[2]) + + NetworkThread().start() # Start up network handling in another thread + + # Test logic begins here + self.test_node.wait_for_verack() + + # We will need UTXOs to construct transactions in later tests. + self.make_utxos() + + print("Running tests, pre-segwit activation:") + + print("\tTesting SENDCMPCT p2p message... ") + self.test_sendcmpct(self.nodes[0], self.test_node, 1) + sync_blocks(self.nodes) + self.test_sendcmpct(self.nodes[1], self.segwit_node, 2, old_node=self.old_node) + sync_blocks(self.nodes) + + print("\tTesting compactblock construction...") + self.test_compactblock_construction(self.nodes[0], self.test_node, 1, False) + sync_blocks(self.nodes) + self.test_compactblock_construction(self.nodes[1], self.segwit_node, 2, False) + sync_blocks(self.nodes) + + print("\tTesting compactblock requests... ") + self.test_compactblock_requests(self.nodes[0], self.test_node, 1, False) + sync_blocks(self.nodes) + self.test_compactblock_requests(self.nodes[1], self.segwit_node, 2, False) + sync_blocks(self.nodes) + + print("\tTesting getblocktxn requests...") + self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1) + sync_blocks(self.nodes) + self.test_getblocktxn_requests(self.nodes[1], self.segwit_node, 2) + sync_blocks(self.nodes) + + print("\tTesting getblocktxn handler...") + self.test_getblocktxn_handler(self.nodes[0], self.test_node, 1) + sync_blocks(self.nodes) + self.test_getblocktxn_handler(self.nodes[1], self.segwit_node, 2) + self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1) + sync_blocks(self.nodes) + + print("\tTesting compactblock requests/announcements not at chain tip...") + self.test_compactblocks_not_at_tip(self.nodes[0], self.test_node) + sync_blocks(self.nodes) + self.test_compactblocks_not_at_tip(self.nodes[1], self.segwit_node) + self.test_compactblocks_not_at_tip(self.nodes[1], self.old_node) + sync_blocks(self.nodes) + + print("\tTesting handling of incorrect blocktxn responses...") + self.test_incorrect_blocktxn_response(self.nodes[0], self.test_node, 1) + sync_blocks(self.nodes) + self.test_incorrect_blocktxn_response(self.nodes[1], self.segwit_node, 2) + sync_blocks(self.nodes) + + # End-to-end block relay tests + print("\tTesting end-to-end block relay...") + self.request_cb_announcements(self.test_node, self.nodes[0], 1) + self.request_cb_announcements(self.old_node, self.nodes[1], 1) + self.request_cb_announcements(self.segwit_node, self.nodes[1], 2) + self.test_end_to_end_block_relay(self.nodes[0], [self.segwit_node, self.test_node, self.old_node]) + self.test_end_to_end_block_relay(self.nodes[1], [self.segwit_node, self.test_node, self.old_node]) + + # Advance to segwit activation + print ("\nAdvancing to segwit activation\n") + self.activate_segwit(self.nodes[1]) + print ("Running tests, post-segwit activation...") + + print("\tTesting compactblock construction...") + self.test_compactblock_construction(self.nodes[1], self.old_node, 1, True) + self.test_compactblock_construction(self.nodes[1], self.segwit_node, 2, True) + sync_blocks(self.nodes) + + print("\tTesting compactblock requests (unupgraded node)... ") + self.test_compactblock_requests(self.nodes[0], self.test_node, 1, True) + + print("\tTesting getblocktxn requests (unupgraded node)...") + self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1) + + # Need to manually sync node0 and node1, because post-segwit activation, + # node1 will not download blocks from node0. + print("\tSyncing nodes...") + assert(self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash()) + while (self.nodes[0].getblockcount() > self.nodes[1].getblockcount()): + block_hash = self.nodes[0].getblockhash(self.nodes[1].getblockcount()+1) + self.nodes[1].submitblock(self.nodes[0].getblock(block_hash, False)) + assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash()) + + print("\tTesting compactblock requests (segwit node)... ") + self.test_compactblock_requests(self.nodes[1], self.segwit_node, 2, True) + + print("\tTesting getblocktxn requests (segwit node)...") + self.test_getblocktxn_requests(self.nodes[1], self.segwit_node, 2) + sync_blocks(self.nodes) + + print("\tTesting getblocktxn handler (segwit node should return witnesses)...") + self.test_getblocktxn_handler(self.nodes[1], self.segwit_node, 2) + self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1) + + # Test that if we submitblock to node1, we'll get a compact block + # announcement to all peers. + # (Post-segwit activation, blocks won't propagate from node0 to node1 + # automatically, so don't bother testing a block announced to node0.) + print("\tTesting end-to-end block relay...") + self.request_cb_announcements(self.test_node, self.nodes[0], 1) + self.request_cb_announcements(self.old_node, self.nodes[1], 1) + self.request_cb_announcements(self.segwit_node, self.nodes[1], 2) + self.test_end_to_end_block_relay(self.nodes[1], [self.segwit_node, self.test_node, self.old_node]) + + print("\tTesting invalid index in cmpctblock message...") + self.test_invalid_cmpctblock_message() + + +if __name__ == '__main__': + CompactBlocksTest().main() diff --git a/qa/rpc-tests/p2p-feefilter.py b/qa/rpc-tests/p2p-feefilter.py new file mode 100755 index 0000000000..96d99d38a7 --- /dev/null +++ b/qa/rpc-tests/p2p-feefilter.py @@ -0,0 +1,115 @@ +#!/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] + node0 = self.nodes[0] + # 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 + + # Send one transaction from node0 that should be received, so that we + # we can sync the test on receipt (if node1's txs were relayed, they'd + # be received by the time this node0 tx is received). This is + # unfortunately reliant on the current relay behavior where we batch up + # to 35 entries in an inv, which means that when this next transaction + # is eligible for relay, the prior transactions from node1 are eligible + # as well. + node0.settxfee(Decimal("0.00020000")) + txids = [node0.sendtoaddress(node0.getnewaddress(), 1)] + assert(allInvsMatch(txids, 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 index 9555940cec..9aee81164f 100755 --- a/qa/rpc-tests/p2p-fullblocktest.py +++ b/qa/rpc-tests/p2p-fullblocktest.py @@ -1,21 +1,16 @@ -#!/usr/bin/env python2 - -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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 -from test_framework.mininode import * +from test_framework.comptool import TestManager, TestInstance, RejectResult from test_framework.blocktools import * -import logging -import copy import time -import numbers from test_framework.key import CECKey -from test_framework.script import CScript, CScriptOp, SignatureHash, SIGHASH_ALL, OP_TRUE +from test_framework.script import * +import struct class PreviousSpendableOutput(object): def __init__(self, tx = CTransaction(), n = -1): @@ -30,76 +25,104 @@ We use the testing framework in which we expect a particular answer from each test. ''' +# 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. ''' + # 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(bytes("horsebattery")) + self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() - self.block_time = int(time.time())+1 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): - test = TestManager(self, self.options.tmpdir) - test.add_all_connections(self.nodes) + self.test = TestManager(self, self.options.tmpdir) + self.test.add_all_connections(self.nodes) NetworkThread().start() # Start up network handling in another thread - test.run() + self.test.run() def add_transactions_to_block(self, block, tx_list): [ tx.rehash() for tx in tx_list ] block.vtx.extend(tx_list) - block.hashMerkleRoot = block.calc_merkle_root() - block.rehash() - return block - - # Create a block on top of self.tip, and advance self.tip to point to the new block - # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output, - # and rest will go to fees. - def next_block(self, number, spend=None, additional_coinbase_value=0, script=None): + + # 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 - if (spend != None): - 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, self.block_time) - if (spend != None): - tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), "", 0xffffffff)) # no signature yet - # This copies the java comparison tool testing behavior: the first - # txout has a garbage scriptPubKey, "to make sure we're not - # pre-verifying too much" (?) - tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255]))) - if script == None: - tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) - else: - tx.vout.append(CTxOut(1, script)) - # Now sign it if necessary - scriptSig = "" - scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) - if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend - scriptSig = CScript([OP_TRUE]) - else: - # We have to actually sign it - (sighash, err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL) - scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) - tx.vin[0].scriptSig = scriptSig - # Now add the transaction to the block - block = self.add_transactions_to_block(block, [tx]) - block.solve() + 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 - self.block_time += 1 assert number not in self.blocks self.blocks[number] = block return block @@ -113,7 +136,7 @@ class FullBlockTest(ComparisonTestFramework): def save_spendable_output(): spendable_outputs.append(self.tip) - # get an output that we previous marked as spendable + # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) @@ -122,15 +145,38 @@ class FullBlockTest(ComparisonTestFramework): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected - def rejected(): - return TestInstance([[self.tip, False]]) - + 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] - # creates a new block and advances the tip to that block + # 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 @@ -141,44 +187,45 @@ class FullBlockTest(ComparisonTestFramework): # 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(1000 + i) + 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 bulding a couple of blocks on top (which output is spent is in parentheses): + # Start by building a couple of blocks on top (which output is spent is + # in parentheses): # genesis -> b1 (0) -> b2 (1) - out0 = get_spendable_output() - block(1, spend=out0) + block(1, spend=out[0]) save_spendable_output() yield accepted() - out1 = get_spendable_output() - block(2, spend=out1) - # Inv again, then deliver twice (shouldn't break anything). + 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) - block(3, spend=out1) - # Deliver twice (should still not break anything) + 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) - out2 = get_spendable_output() - block(4, spend=out2) + block(4, spend=out[2]) yield accepted() @@ -186,47 +233,42 @@ class FullBlockTest(ComparisonTestFramework): # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b3 (1) -> b4 (2) tip(2) - block(5, spend=out2) + block(5, spend=out[2]) save_spendable_output() yield rejected() - out3 = get_spendable_output() - block(6, spend=out3) + 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=out2) + block(7, spend=out[2]) yield rejected() - out4 = get_spendable_output() - block(8, spend=out4) + 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=out4, additional_coinbase_value=1) - yield rejected() + 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=out3) + block(10, spend=out[3]) yield rejected() - block(11, spend=out4, additional_coinbase_value=1) - 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 @@ -235,36 +277,1012 @@ class FullBlockTest(ComparisonTestFramework): # (b12 added last) # \-> b3 (1) -> b4 (2) tip(5) - b12 = block(12, spend=out3) + b12 = block(12, spend=out[3]) save_spendable_output() - #yield TestInstance([[b12, False]]) - b13 = block(13, spend=out4) + 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() - out5 = get_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=out5, additional_coinbase_value=1) + 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] * (1000000 / 50 - 1)) + lots_of_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS - 1)) tip(13) - block(15, spend=out5, script=lots_of_checksigs) + block(15, spend=out[5], script=lots_of_checksigs) yield accepted() + save_spendable_output() # Test that a block with too many checksigs is rejected - out6 = get_spendable_output() - too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50)) - block(16, spend=out6, script=too_many_checksigs) + 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 diff --git a/qa/rpc-tests/p2p-mempool.py b/qa/rpc-tests/p2p-mempool.py new file mode 100755 index 0000000000..382d7f1e82 --- /dev/null +++ b/qa/rpc-tests/p2p-mempool.py @@ -0,0 +1,102 @@ +#!/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=timeout) + self.ping_counter += 1 + return success + + def send_mempool(self): + self.lastInv = [] + self.send_message(msg_mempool()) + +class P2PMempoolTests(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 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..09ab1b80fc --- /dev/null +++ b/qa/rpc-tests/p2p-segwit.py @@ -0,0 +1,2001 @@ +#!/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 + +# Helper for getting the script associated with a P2PKH +def GetP2PKHScript(pubkeyhash): + return CScript([CScriptOp(OP_DUP), CScriptOp(OP_HASH160), pubkeyhash, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_CHECKSIG)]) + +# 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() + + +class SegWitTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 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 non-standard, to test that violating policy with a witness before + # segwit activation doesn't blind a node to a transaction. Transactions + # rejected for having a witness before segwit activation shouldn't be added + # to the rejection cache. + 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() + # Note that this should be rejected for the premature witness reason, + # rather than a policy check, since segwit hasn't activated yet. + 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) + + # 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. + self.test_node.announce_tx_and_wait_for_getdata(tx) + 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.wit.vtxinwit.append(CTxInWitness()) + + # Add too-large for IsStandard witness and check that it does not enter reject filter + p2sh_program = CScript([OP_TRUE]) + p2sh_pubkey = hash160(p2sh_program) + witness_program2 = CScript([b'a'*400000]) + tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]))) + tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program2] + tx3.rehash() + + # Node will not be blinded to the transaction + self.std_node.announce_tx_and_wait_for_getdata(tx3) + self.std_node.test_transaction_acceptance(tx3, True, False, b'tx-size') + self.std_node.announce_tx_and_wait_for_getdata(tx3) + self.std_node.test_transaction_acceptance(tx3, True, False, b'tx-size') + + # Remove witness stuffing, instead add extra witness push on stack + tx3.vout[0] = CTxOut(tx2.vout[0].nValue-1000, CScript([OP_TRUE])) + 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 + + # 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, weight + 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))) + weight = 3*len(block.serialize(False)) + len(block.serialize(True)) + assert_equal(rpc_details["weight"], weight) + + # 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) + + # V0 segwit outputs should be standard after activation, but not before. + def test_standardness_v0(self, segwit_activated): + print("\tTesting standardness of v0 outputs (%s activation)" % ("after" if segwit_activated else "before")) + assert(len(self.utxo)) + + witness_program = CScript([OP_TRUE]) + witness_hash = sha256(witness_program) + scriptPubKey = CScript([OP_0, witness_hash]) + + p2sh_pubkey = hash160(witness_program) + p2sh_scriptPubKey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL]) + + # First prepare a p2sh output (so that spending it will pass standardness) + p2sh_tx = CTransaction() + p2sh_tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")] + p2sh_tx.vout = [CTxOut(self.utxo[0].nValue-1000, p2sh_scriptPubKey)] + p2sh_tx.rehash() + + # Mine it on test_node to create the confirmed output. + self.test_node.test_transaction_acceptance(p2sh_tx, with_witness=True, accepted=True) + self.nodes[0].generate(1) + sync_blocks(self.nodes) + + # Now test standardness of v0 P2WSH outputs. + # Start by creating a transaction with two outputs. + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))] + tx.vout = [CTxOut(p2sh_tx.vout[0].nValue-10000, scriptPubKey)] + tx.vout.append(CTxOut(8000, scriptPubKey)) # Might burn this later + tx.rehash() + + self.std_node.test_transaction_acceptance(tx, with_witness=True, accepted=segwit_activated) + + # Now create something that looks like a P2PKH output. This won't be spendable. + scriptPubKey = CScript([OP_0, hash160(witness_hash)]) + tx2 = CTransaction() + if segwit_activated: + # if tx was accepted, then we spend the second output. + tx2.vin = [CTxIn(COutPoint(tx.sha256, 1), b"")] + tx2.vout = [CTxOut(7000, scriptPubKey)] + tx2.wit.vtxinwit.append(CTxInWitness()) + tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program] + else: + # if tx wasn't accepted, we just re-spend the p2sh output we started with. + tx2.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))] + tx2.vout = [CTxOut(p2sh_tx.vout[0].nValue-1000, scriptPubKey)] + tx2.rehash() + + self.std_node.test_transaction_acceptance(tx2, with_witness=True, accepted=segwit_activated) + + # Now update self.utxo for later tests. + tx3 = CTransaction() + if segwit_activated: + # tx and tx2 were both accepted. Don't bother trying to reclaim the + # P2PKH output; just send tx's first output back to an anyone-can-spend. + sync_mempools(self.nodes) + tx3.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")] + tx3.vout = [CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE]))] + tx3.wit.vtxinwit.append(CTxInWitness()) + tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program] + tx3.rehash() + self.test_node.test_transaction_acceptance(tx3, with_witness=True, accepted=True) + else: + # tx and tx2 didn't go anywhere; just clean up the p2sh_tx output. + tx3.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))] + tx3.vout = [CTxOut(p2sh_tx.vout[0].nValue-1000, witness_program)] + tx3.rehash() + self.test_node.test_transaction_acceptance(tx3, with_witness=True, accepted=True) + + self.nodes[0].generate(1) + sync_blocks(self.nodes) + self.utxo.pop(0) + self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) + assert_equal(len(self.nodes[1].getrawmempool()), 0) + + + # 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) + + # 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): + # Ping regularly to keep the connection alive + if (not i % 100): + self.test_node.sync_with_ping() + # 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 = GetP2PKHScript(pubkeyhash) + 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 + + # Uncompressed pubkeys are no longer supported in default relay policy, + # but (for now) are still valid in blocks. + def test_uncompressed_pubkey(self): + print("\tTesting uncompressed pubkeys") + # Segwit transactions using uncompressed pubkeys are not accepted + # under default policy, but should still pass consensus. + key = CECKey() + key.set_secretbytes(b"9") + key.set_compressed(False) + pubkey = CPubKey(key.get_pubkey()) + assert_equal(len(pubkey), 65) # This should be an uncompressed pubkey + + assert(len(self.utxo) > 0) + utxo = self.utxo.pop(0) + + # Test 1: P2WPKH + # First create a P2WPKH output that uses an uncompressed pubkey + pubkeyhash = hash160(pubkey) + scriptPKH = CScript([OP_0, pubkeyhash]) + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(utxo.sha256, utxo.n), b"")) + tx.vout.append(CTxOut(utxo.nValue-1000, scriptPKH)) + tx.rehash() + + # Confirm it in a block. + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx]) + self.test_node.test_witness_block(block, accepted=True) + + # Now try to spend it. Send it to a P2WSH output, which we'll + # use in the next test. + witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) + witness_hash = sha256(witness_program) + scriptWSH = CScript([OP_0, witness_hash]) + + tx2 = CTransaction() + tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b"")) + tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, scriptWSH)) + script = GetP2PKHScript(pubkeyhash) + sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) + signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + tx2.wit.vtxinwit.append(CTxInWitness()) + tx2.wit.vtxinwit[0].scriptWitness.stack = [ signature, pubkey ] + tx2.rehash() + + # Should fail policy test. + self.test_node.test_transaction_acceptance(tx2, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') + # But passes consensus. + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx2]) + self.test_node.test_witness_block(block, accepted=True) + + # Test 2: P2WSH + # Try to spend the P2WSH output created in last test. + # Send it to a P2SH(P2WSH) output, which we'll use in the next test. + p2sh_witness_hash = hash160(scriptWSH) + scriptP2SH = CScript([OP_HASH160, p2sh_witness_hash, OP_EQUAL]) + scriptSig = CScript([scriptWSH]) + + tx3 = CTransaction() + tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), b"")) + tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, scriptP2SH)) + tx3.wit.vtxinwit.append(CTxInWitness()) + sign_P2PK_witness_input(witness_program, tx3, 0, SIGHASH_ALL, tx2.vout[0].nValue, key) + + # Should fail policy test. + self.test_node.test_transaction_acceptance(tx3, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') + # But passes consensus. + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx3]) + self.test_node.test_witness_block(block, accepted=True) + + # Test 3: P2SH(P2WSH) + # Try to spend the P2SH output created in the last test. + # Send it to a P2PKH output, which we'll use in the next test. + scriptPubKey = GetP2PKHScript(pubkeyhash) + tx4 = CTransaction() + tx4.vin.append(CTxIn(COutPoint(tx3.sha256, 0), scriptSig)) + tx4.vout.append(CTxOut(tx3.vout[0].nValue-1000, scriptPubKey)) + tx4.wit.vtxinwit.append(CTxInWitness()) + sign_P2PK_witness_input(witness_program, tx4, 0, SIGHASH_ALL, tx3.vout[0].nValue, key) + + # Should fail policy test. + self.test_node.test_transaction_acceptance(tx4, True, False, b'non-mandatory-script-verify-flag (Using non-compressed keys in segwit)') + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx4]) + self.test_node.test_witness_block(block, accepted=True) + + # Test 4: Uncompressed pubkeys should still be valid in non-segwit + # transactions. + tx5 = CTransaction() + tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b"")) + tx5.vout.append(CTxOut(tx4.vout[0].nValue-1000, CScript([OP_TRUE]))) + (sig_hash, err) = SignatureHash(scriptPubKey, tx5, 0, SIGHASH_ALL) + signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + tx5.vin[0].scriptSig = CScript([signature, pubkey]) + tx5.rehash() + # Should pass policy and consensus. + self.test_node.test_transaction_acceptance(tx5, True, True) + block = self.build_next_block() + self.update_witness_block_with_transactions(block, [tx5]) + self.test_node.test_witness_block(block, accepted=True) + self.utxo.append(UTXO(tx5.sha256, 0, tx5.vout[0].nValue)) + + def test_non_standard_witness(self): + print("\tTesting detection of non-standard P2WSH witness") + pad = chr(1).encode('latin-1') + + # Create scripts for tests + scripts = [] + scripts.append(CScript([OP_DROP] * 100)) + scripts.append(CScript([OP_DROP] * 99)) + scripts.append(CScript([pad * 59] * 59 + [OP_DROP] * 60)) + scripts.append(CScript([pad * 59] * 59 + [OP_DROP] * 61)) + + p2wsh_scripts = [] + + assert(len(self.utxo)) + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")) + + # For each script, generate a pair of P2WSH and P2SH-P2WSH output. + outputvalue = (self.utxo[0].nValue - 1000) // (len(scripts) * 2) + for i in scripts: + p2wsh = CScript([OP_0, sha256(i)]) + p2sh = hash160(p2wsh) + p2wsh_scripts.append(p2wsh) + tx.vout.append(CTxOut(outputvalue, p2wsh)) + tx.vout.append(CTxOut(outputvalue, CScript([OP_HASH160, p2sh, OP_EQUAL]))) + tx.rehash() + txid = tx.sha256 + self.test_node.test_transaction_acceptance(tx, with_witness=False, accepted=True) + + self.nodes[0].generate(1) + sync_blocks(self.nodes) + + # Creating transactions for tests + p2wsh_txs = [] + p2sh_txs = [] + for i in range(len(scripts)): + p2wsh_tx = CTransaction() + p2wsh_tx.vin.append(CTxIn(COutPoint(txid,i*2))) + p2wsh_tx.vout.append(CTxOut(outputvalue - 5000, CScript([OP_0, hash160(hex_str_to_bytes(""))]))) + p2wsh_tx.wit.vtxinwit.append(CTxInWitness()) + p2wsh_tx.rehash() + p2wsh_txs.append(p2wsh_tx) + p2sh_tx = CTransaction() + p2sh_tx.vin.append(CTxIn(COutPoint(txid,i*2+1), CScript([p2wsh_scripts[i]]))) + p2sh_tx.vout.append(CTxOut(outputvalue - 5000, CScript([OP_0, hash160(hex_str_to_bytes(""))]))) + p2sh_tx.wit.vtxinwit.append(CTxInWitness()) + p2sh_tx.rehash() + p2sh_txs.append(p2sh_tx) + + # Testing native P2WSH + # Witness stack size, excluding witnessScript, over 100 is non-standard + p2wsh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]] + self.std_node.test_transaction_acceptance(p2wsh_txs[0], True, False, b'bad-witness-nonstandard') + # Non-standard nodes should accept + self.test_node.test_transaction_acceptance(p2wsh_txs[0], True, True) + + # Stack element size over 80 bytes is non-standard + p2wsh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]] + self.std_node.test_transaction_acceptance(p2wsh_txs[1], True, False, b'bad-witness-nonstandard') + # Non-standard nodes should accept + self.test_node.test_transaction_acceptance(p2wsh_txs[1], True, True) + # Standard nodes should accept if element size is not over 80 bytes + p2wsh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 80] * 100 + [scripts[1]] + self.std_node.test_transaction_acceptance(p2wsh_txs[1], True, True) + + # witnessScript size at 3600 bytes is standard + p2wsh_txs[2].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, scripts[2]] + self.test_node.test_transaction_acceptance(p2wsh_txs[2], True, True) + self.std_node.test_transaction_acceptance(p2wsh_txs[2], True, True) + + # witnessScript size at 3601 bytes is non-standard + p2wsh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]] + self.std_node.test_transaction_acceptance(p2wsh_txs[3], True, False, b'bad-witness-nonstandard') + # Non-standard nodes should accept + self.test_node.test_transaction_acceptance(p2wsh_txs[3], True, True) + + # Repeating the same tests with P2SH-P2WSH + p2sh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]] + self.std_node.test_transaction_acceptance(p2sh_txs[0], True, False, b'bad-witness-nonstandard') + self.test_node.test_transaction_acceptance(p2sh_txs[0], True, True) + p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]] + self.std_node.test_transaction_acceptance(p2sh_txs[1], True, False, b'bad-witness-nonstandard') + self.test_node.test_transaction_acceptance(p2sh_txs[1], True, True) + p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 80] * 100 + [scripts[1]] + self.std_node.test_transaction_acceptance(p2sh_txs[1], True, True) + p2sh_txs[2].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, scripts[2]] + self.test_node.test_transaction_acceptance(p2sh_txs[2], True, True) + self.std_node.test_transaction_acceptance(p2sh_txs[2], True, True) + p2sh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]] + self.std_node.test_transaction_acceptance(p2sh_txs[3], True, False, b'bad-witness-nonstandard') + self.test_node.test_transaction_acceptance(p2sh_txs[3], True, True) + + self.nodes[0].generate(1) # Mine and clean up the mempool of non-standard node + # Valid but non-standard transactions in a block should be accepted by standard node + sync_blocks(self.nodes) + assert_equal(len(self.nodes[0].getrawmempool()), 0) + assert_equal(len(self.nodes[1].getrawmempool()), 0) + + self.utxo.pop(0) + + + 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) + self.test_standardness_v0(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_standardness_v0(segwit_activated=True) + self.test_segwit_versions() + self.test_premature_coinbase_witness_spend() + self.test_uncompressed_pubkey() + self.test_signature_version_1() + self.test_non_standard_witness() + 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..fc3eddddee --- /dev/null +++ b/qa/rpc-tests/p2p-versionbits-warning.py @@ -0,0 +1,165 @@ +#!/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 re +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 + +WARN_UNKNOWN_RULES_MINED = "Unknown block versions being mined! It's possible unknown rules are in effect" +WARN_UNKNOWN_RULES_ACTIVE = "unknown new rules activated (versionbit {})".format(VB_UNKNOWN_BIT) +VB_PATTERN = re.compile("^Warning.*versionbit") + +# 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.alert_filename = os.path.join(self.options.tmpdir, "alert.txt") + # Open and close to create zero-length file + with open(self.alert_filename, 'w', encoding='utf8') as _: + pass + self.extra_args = [["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""]] + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) + + # 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 _ 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', encoding='utf8') as f: + alert_text = f.read() + assert(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 + # get*info() + assert(not VB_PATTERN.match(self.nodes[0].getinfo()["errors"])) + assert(not VB_PATTERN.match(self.nodes[0].getmininginfo()["errors"])) + assert(not VB_PATTERN.match(self.nodes[0].getnetworkinfo()["warnings"])) + + # 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 get*info() shows some kind of error. + assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getinfo()["errors"]) + assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getmininginfo()["errors"]) + assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getnetworkinfo()["warnings"]) + + # 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_nodes(self.nodes) + # Empty out the alert file + with open(self.alert_filename, 'w', encoding='utf8') as _: + pass + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) + + # Connecting one block should be enough to generate an error. + self.nodes[0].generate(1) + assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getinfo()["errors"]) + assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getmininginfo()["errors"]) + assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getnetworkinfo()["warnings"]) + stop_nodes(self.nodes) + self.test_versionbits_in_alert_file() + + # Test framework expects the node to still be running... + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) + +if __name__ == '__main__': + VersionBitsWarningTest().main() diff --git a/qa/rpc-tests/prioritise_transaction.py b/qa/rpc-tests/prioritise_transaction.py index f376ceee5e..e1771231c0 100755 --- a/qa/rpc-tests/prioritise_transaction.py +++ b/qa/rpc-tests/prioritise_transaction.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python2 -# Copyright (c) 2015 The Bitcoin Core developers +#!/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. @@ -9,31 +9,16 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * - -COIN = 100000000 +from test_framework.mininode import COIN, MAX_BLOCK_SIZE class PrioritiseTransactionTest(BitcoinTestFramework): def __init__(self): - # 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 xrange (512): - script_pubkey = script_pubkey + "01" - # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change - self.txouts = "81" - for k in xrange(128): - # add txout value - self.txouts = self.txouts + "0000000000000000" - # add length of script_pubkey - self.txouts = self.txouts + "fd0402" - # add script_pubkey - self.txouts = self.txouts + script_pubkey - - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 1) + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 + + self.txouts = gen_return_txouts() def setup_network(self): self.nodes = [] @@ -42,62 +27,30 @@ class PrioritiseTransactionTest(BitcoinTestFramework): self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-printpriority=1"])) self.relayfee = self.nodes[0].getnetworkinfo()['relayfee'] - def create_confirmed_utxos(self, count): - self.nodes[0].generate(int(0.5*count)+101) - utxos = self.nodes[0].listunspent() - iterations = count - len(utxos) - addr1 = self.nodes[0].getnewaddress() - addr2 = self.nodes[0].getnewaddress() - if iterations <= 0: - return utxos - for i in xrange(iterations): - t = utxos.pop() - fee = self.relayfee - 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 = self.nodes[0].createrawtransaction(inputs, outputs) - signed_tx = self.nodes[0].signrawtransaction(raw_tx)["hex"] - txid = self.nodes[0].sendrawtransaction(signed_tx) - - while (self.nodes[0].getmempoolinfo()['size'] > 0): - self.nodes[0].generate(1) - - utxos = self.nodes[0].listunspent() - assert(len(utxos) >= count) - return utxos - - def create_lots_of_big_transactions(self, utxos, fee): - addr = self.nodes[0].getnewaddress() - txids = [] - for i in xrange(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 = self.nodes[0].createrawtransaction(inputs, outputs) - newtx = rawtx[0:92] - newtx = newtx + self.txouts - newtx = newtx + rawtx[94:] - signresult = self.nodes[0].signrawtransaction(newtx, None, None, "NONE") - txid = self.nodes[0].sendrawtransaction(signresult["hex"], True) - txids.append(txid) - return txids - def run_test(self): - utxos = self.create_confirmed_utxos(90) + 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 - for i in xrange(3): + range_size = utxo_count // 3 + for i in range(3): txids.append([]) - txids[i] = self.create_lots_of_big_transactions(utxos[30*i:30*i+30], (i+1)*base_fee) + 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 @@ -108,7 +61,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework): self.nodes[0].generate(1) mempool = self.nodes[0].getrawmempool() - print "Assert that prioritised transasction was mined" + print("Assert that prioritised transaction was mined") assert(txids[0][0] not in mempool) assert(txids[0][1] in mempool) @@ -121,7 +74,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework): assert(high_fee_tx != None) # Add a prioritisation before a tx is in the mempool (de-prioritising a - # high-fee transaction). + # 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 @@ -131,17 +84,60 @@ class PrioritiseTransactionTest(BitcoinTestFramework): mempool = self.nodes[0].getrawmempool() assert(high_fee_tx in mempool) - # Now verify the high feerate transaction isn't mined. - self.nodes[0].generate(5) + # 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" + 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 index 3623c16162..27160cae07 100755 --- a/qa/rpc-tests/proxy_test.py +++ b/qa/rpc-tests/proxy_test.py @@ -1,15 +1,14 @@ -#!/usr/bin/env python2 -# Copyright (c) 2015 The Bitcoin Core developers +#!/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 -import traceback, sys -from binascii import hexlify -import time, os 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 @@ -34,8 +33,14 @@ addnode connect to onion addnode connect to generic DNS name ''' -class ProxyTest(BitcoinTestFramework): + +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() @@ -47,29 +52,36 @@ class ProxyTest(BitcoinTestFramework): self.conf2.addr = ('127.0.0.1', 14000 + (os.getpid() % 1000)) self.conf2.unauth = True self.conf2.auth = True - # ... 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 + 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() - self.serv3 = Socks5Server(self.conf3) - self.serv3.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 - return start_nodes(4, self.options.tmpdir, extra_args=[ + 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'], - ['-listen', '-debug=net', '-debug=proxy', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion'] - ]) + [] + ] + 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 = [] @@ -79,25 +91,26 @@ class ProxyTest(BitcoinTestFramework): 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, "15.61.23.23") + 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) - # 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, "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 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 @@ -105,7 +118,7 @@ class ProxyTest(BitcoinTestFramework): cmd = proxies[2].queue.get() assert(isinstance(cmd, Socks5Command)) assert_equal(cmd.atyp, AddressType.DOMAINNAME) - assert_equal(cmd.addr, "bitcoinostk4e4re.onion") + assert_equal(cmd.addr, b"bitcoinostk4e4re.onion") assert_equal(cmd.port, 8333) if not auth: assert_equal(cmd.username, None) @@ -117,7 +130,7 @@ class ProxyTest(BitcoinTestFramework): cmd = proxies[3].queue.get() assert(isinstance(cmd, Socks5Command)) assert_equal(cmd.atyp, AddressType.DOMAINNAME) - assert_equal(cmd.addr, "node.noumenon") + assert_equal(cmd.addr, b"node.noumenon") assert_equal(cmd.port, 8333) if not auth: assert_equal(cmd.username, None) @@ -137,10 +150,11 @@ class ProxyTest(BitcoinTestFramework): 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), 4) + assert_equal(len(credentials), len(rv)) - # proxy on IPv6 localhost - self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False, False) + 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 = {} @@ -169,11 +183,12 @@ class ProxyTest(BitcoinTestFramework): assert_equal(n2[net]['proxy_randomize_credentials'], True) assert_equal(n2['onion']['reachable'], True) - 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 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 index 21f8d69382..287dbc776e 100755 --- a/qa/rpc-tests/pruning.py +++ b/qa/rpc-tests/pruning.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,38 +13,20 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import os.path 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) + 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 = ["",""] - - # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create - # So we have big transactions and full blocks to fill up our block files - - # create one script_pubkey - script_pubkey = "6a4d0200" #OP_RETURN OP_PUSH2 512 bytes - for i in xrange (512): - script_pubkey = script_pubkey + "01" - # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change - self.txouts = "81" - for k in xrange(128): - # add txout value - self.txouts = self.txouts + "0000000000000000" - # add length of script_pubkey - self.txouts = self.txouts + "fd0402" - # add script_pubkey - self.txouts = self.txouts + script_pubkey - - - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 3) + self.txouts = gen_return_txouts() def setup_network(self): self.nodes = [] @@ -74,8 +56,8 @@ class PruneTest(BitcoinTestFramework): 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 550MB of data - for i in xrange(645): + # 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]) @@ -83,30 +65,30 @@ class PruneTest(BitcoinTestFramework): 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 550MB, current usage:", calc_usage(self.prunedir) - print "Mining 25 more blocks should cause the first block file to be pruned" + 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 xrange(25): + 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 > 10: + if time.time() - waitstart > 30: raise AssertionError("blk00000.dat not pruned when it should be") - print "Success" + print("Success") usage = calc_usage(self.prunedir) - print "Usage should be below target:", usage + 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" + print("Mine 24 (stale) blocks on Node 1, followed by 25 (main chain) block reorg from Node 0, for 12 rounds") - for j in xrange(12): + 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 @@ -114,7 +96,7 @@ class PruneTest(BitcoinTestFramework): 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 xrange(24): + for i in range(24): if j == 0: self.mine_full_block(self.nodes[1],self.address[1]) else: @@ -122,7 +104,7 @@ class PruneTest(BitcoinTestFramework): # Reorg back with 25 block chain from node 0 self.utxo = self.nodes[0].listunspent() - for i in xrange(25): + 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 @@ -130,7 +112,7 @@ class PruneTest(BitcoinTestFramework): 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) + 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 @@ -141,11 +123,11 @@ class PruneTest(BitcoinTestFramework): 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 + print("Current block height:", height) invalidheight = height-287 badhash = self.nodes[1].getblockhash(invalidheight) - print "Invalidating block at height:",invalidheight,badhash + 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 @@ -157,29 +139,32 @@ class PruneTest(BitcoinTestFramework): curhash = self.nodes[1].getblockhash(invalidheight - 1) assert(self.nodes[1].getblockcount() == invalidheight - 1) - print "New best height", self.nodes[1].getblockcount() + 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" + print("Generating new longer chain of 300 more blocks") self.nodes[1].generate(300) - print "Reconnect nodes" + print("Reconnect nodes") connect_nodes(self.nodes[0], 1) connect_nodes(self.nodes[2], 1) - sync_blocks(self.nodes[0:3]) + 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("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]) + print("Mine 220 more blocks so we have requisite history (some blocks will be big and cause pruning of previous chain)") + for i in range(22): + # This can be slow, so do this in multiple RPC calls to avoid + # RPC timeouts. + self.nodes[0].generate(10) #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 + print("Usage should be below target:", usage) if (usage > 550): raise AssertionError("Pruning target not being met") @@ -191,7 +176,7 @@ class PruneTest(BitcoinTestFramework): 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 + 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 @@ -215,14 +200,14 @@ class PruneTest(BitcoinTestFramework): # 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 + 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" + 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) @@ -235,7 +220,7 @@ class PruneTest(BitcoinTestFramework): 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 xrange(14): + for j in range(14): if len(self.utxo) < 14: self.utxo = node.listunspent() inputs=[] @@ -259,8 +244,8 @@ class PruneTest(BitcoinTestFramework): 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" + 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 @@ -271,12 +256,12 @@ class PruneTest(BitcoinTestFramework): # 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" + 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" + 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 @@ -300,7 +285,7 @@ class PruneTest(BitcoinTestFramework): 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" + 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 @@ -329,11 +314,11 @@ class PruneTest(BitcoinTestFramework): # \ \ # ++...++(1044) .. # - # N0 ********************(1032) @@...@@@(1552) + # N0 ********************(1032) @@...@@@(1552) # \ # *...**(1320) - print "Test that we can rerequest a block we previously pruned if needed for a reorg" + 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 @@ -353,7 +338,7 @@ class PruneTest(BitcoinTestFramework): # # N1 doesn't change because 1033 on main chain (*) is invalid - print "Done" + print("Done") if __name__ == '__main__': PruneTest().main() diff --git a/qa/rpc-tests/rawtransactions.py b/qa/rpc-tests/rawtransactions.py index 173faf736e..ab6d2e8def 100755 --- a/qa/rpc-tests/rawtransactions.py +++ b/qa/rpc-tests/rawtransactions.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. @@ -10,18 +10,17 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -from pprint import pprint -from time import sleep # Create one-input, one-output, no-fee transaction: class RawTransactionsTest(BitcoinTestFramework): - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 3) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 3 def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir) + 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) @@ -43,9 +42,9 @@ class RawTransactionsTest(BitcoinTestFramework): 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.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() @@ -58,13 +57,13 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[2].createrawtransaction(inputs, outputs) rawtx = self.nodes[2].signrawtransaction(rawtx) - errorString = "" try: rawtx = self.nodes[2].sendrawtransaction(rawtx['hex']) - except JSONRPCException,e: - errorString = e.error['message'] + except JSONRPCException as e: + assert("Missing inputs" in e.error['message']) + else: + assert(False) - assert_equal("Missing inputs" in errorString, True); ######################### # RAW TX MULTISIG TESTS # @@ -83,15 +82,13 @@ class RawTransactionsTest(BitcoinTestFramework): bal = self.nodes[2].getbalance() # send 1.2 BTC to msig adr - txId = self.nodes[0].sendtoaddress(mSigObj, 1.2); + 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() @@ -105,7 +102,7 @@ class RawTransactionsTest(BitcoinTestFramework): 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); + 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'] @@ -123,7 +120,7 @@ class RawTransactionsTest(BitcoinTestFramework): for outpoint in rawTx['vout']: if outpoint['value'] == Decimal('2.20000000'): vout = outpoint - break; + break bal = self.nodes[0].getbalance() inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}] @@ -141,5 +138,25 @@ class RawTransactionsTest(BitcoinTestFramework): 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 16d6bd4cf1..4f17b661cb 100755 --- a/qa/rpc-tests/receivedby.py +++ b/qa/rpc-tests/receivedby.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. @@ -25,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 @@ -63,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].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":[]}) @@ -126,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) @@ -138,7 +122,7 @@ class ReceivedByTest(BitcoinTestFramework): 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 index f2e3f248ea..25cf4c1679 100755 --- a/qa/rpc-tests/reindex.py +++ b/qa/rpc-tests/reindex.py @@ -1,33 +1,45 @@ -#!/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 -reindex with CheckBlockIndex +# Test -reindex and -reindex-chainstate with CheckBlockIndex # from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * -import os.path +from test_framework.util import ( + start_nodes, + stop_nodes, + assert_equal, +) +import time class ReindexTest(BitcoinTestFramework): - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 1) + 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)) + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir) - def run_test(self): + def reindex(self, justchainstate=False): self.nodes[0].generate(3) - stop_node(self.nodes[0], 0) - wait_bitcoinds() - self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug", "-reindex", "-checkblockindex=1"]) - assert_equal(self.nodes[0].getblockcount(), 3) - print "Success" + blockcount = self.nodes[0].getblockcount() + stop_nodes(self.nodes) + extra_args = [["-debug", "-reindex-chainstate" if justchainstate else "-reindex", "-checkblockindex=1"]] + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args) + 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 index 6e9e0b304c..34c0f9d795 100755 --- a/qa/rpc-tests/replace-by-fee.py +++ b/qa/rpc-tests/replace-by-fee.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python2 -# Copyright (c) 2014-2015 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. @@ -11,16 +11,11 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * from test_framework.script import * from test_framework.mininode import * -import binascii -COIN = 100000000 MAX_REPLACEMENT_LIMIT = 100 -def satoshi_round(amount): - return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) - def txToHex(tx): - return binascii.hexlify(tx.serialize()).decode('utf-8') + 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 @@ -54,25 +49,33 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])): tx2.vout = [CTxOut(amount, scriptPubKey)] tx2.rehash() - tx2_hex = binascii.hexlify(tx2.serialize()).decode('utf-8') - #print tx2_hex - - signed_tx = node.signrawtransaction(binascii.hexlify(tx2.serialize()).decode('utf-8')) + signed_tx = node.signrawtransaction(txToHex(tx2)) txid = node.sendrawtransaction(signed_tx['hex'], True) # If requested, ensure txouts are confirmed. if confirmed: - while len(node.getrawmempool()): + 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", + self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000", "-debug", "-relaypriority=0", "-whitelist=127.0.0.1", "-limitancestorcount=50", "-limitancestorsize=101", @@ -84,35 +87,38 @@ class ReplaceByFeeTest(BitcoinTestFramework): def run_test(self): make_utxo(self.nodes[0], 1*COIN) - print "Running test simple doublespend..." + print("Running test simple doublespend...") self.test_simple_doublespend() - print "Running test doublespend chain..." + print("Running test doublespend chain...") self.test_doublespend_chain() - print "Running test doublespend tree..." + print("Running test doublespend tree...") self.test_doublespend_tree() - print "Running test replacement feeperkb..." + print("Running test replacement feeperkb...") self.test_replacement_feeperkb() - print "Running test spends of conflicting outputs..." + print("Running test spends of conflicting outputs...") self.test_spends_of_conflicting_outputs() - print "Running test new unconfirmed inputs..." + print("Running test new unconfirmed inputs...") self.test_new_unconfirmed_inputs() - print "Running test too many replacements..." + print("Running test too many replacements...") self.test_too_many_replacements() - print "Running test opt-in..." + print("Running test opt-in...") self.test_opt_in() - print "Passed\n" + 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], 1.1*COIN) + tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] @@ -136,7 +142,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Extra 0.1 BTC fee tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(0.9*COIN, CScript([b'b']))] + tx1b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))] tx1b_hex = txToHex(tx1b) tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True) @@ -228,7 +234,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): _total_txs=_total_txs): yield x - fee = 0.0001*COIN + 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) @@ -261,7 +267,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # 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 = 0.0001*COIN + 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) @@ -284,7 +290,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_replacement_feeperkb(self): """Replacement requires fee-per-KB to be higher""" - tx0_outpoint = make_utxo(self.nodes[0], 1.1*COIN) + tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] @@ -296,7 +302,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # rejected. tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(0.001*COIN, CScript([b'a'*999000]))] + tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*999000]))] tx1b_hex = txToHex(tx1b) try: @@ -308,12 +314,12 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_spends_of_conflicting_outputs(self): """Replacements that spend conflicting tx outputs are rejected""" - utxo1 = make_utxo(self.nodes[0], 1.2*COIN) - utxo2 = make_utxo(self.nodes[0], 3.0*COIN) + 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(1.1*COIN, CScript([b'a']))] + tx1a.vout = [CTxOut(int(1.1*COIN), CScript([b'a']))] tx1a_hex = txToHex(tx1a) tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True) @@ -336,7 +342,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Spend tx1a's output to test the indirect case. tx1b = CTransaction() tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)] - tx1b.vout = [CTxOut(1.0*COIN, CScript([b'a']))] + 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) @@ -356,12 +362,12 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_new_unconfirmed_inputs(self): """Replacements that add new unconfirmed inputs are rejected""" - confirmed_utxo = make_utxo(self.nodes[0], 1.1*COIN) - unconfirmed_utxo = make_utxo(self.nodes[0], 0.1*COIN, False) + 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.0*COIN, CScript([b'a']))] + tx1.vout = [CTxOut(1*COIN, CScript([b'a']))] tx1_hex = txToHex(tx1) tx1_txid = self.nodes[0].sendrawtransaction(tx1_hex, True) @@ -385,7 +391,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Start by creating a single transaction with many outputs initial_nValue = 10*COIN utxo = make_utxo(self.nodes[0], initial_nValue) - fee = 0.0001*COIN + 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) @@ -438,7 +444,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_opt_in(self): """ Replacing should only work if orig tx opted in """ - tx0_outpoint = make_utxo(self.nodes[0], 1.1*COIN) + tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) # Create a non-opting in transaction tx1a = CTransaction() @@ -450,7 +456,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Shouldn't be able to double-spend tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(0.9*COIN, CScript([b'b']))] + tx1b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))] tx1b_hex = txToHex(tx1b) try: @@ -458,10 +464,10 @@ class ReplaceByFeeTest(BitcoinTestFramework): except JSONRPCException as exp: assert_equal(exp.error['code'], -26) else: - print tx1b_txid + print(tx1b_txid) assert(False) - tx1_outpoint = make_utxo(self.nodes[0], 1.1*COIN) + tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) # Create a different non-opting in transaction tx2a = CTransaction() @@ -473,7 +479,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Still shouldn't be able to double-spend tx2b = CTransaction() tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] - tx2b.vout = [CTxOut(0.9*COIN, CScript([b'b']))] + tx2b.vout = [CTxOut(int(0.9*COIN), CScript([b'b']))] tx2b_hex = txToHex(tx2b) try: @@ -493,19 +499,19 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx3a = CTransaction() tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff), CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)] - tx3a.vout = [CTxOut(0.9*COIN, CScript([b'c'])), CTxOut(0.9*COIN, CScript([b'd']))] + 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(0.5*COIN, CScript([b'e']))] + 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(0.5*COIN, CScript([b'f']))] + tx3c.vout = [CTxOut(int(0.5*COIN), CScript([b'f']))] tx3c_hex = txToHex(tx3c) self.nodes[0].sendrawtransaction(tx3b_hex, True) @@ -513,5 +519,72 @@ class ReplaceByFeeTest(BitcoinTestFramework): # 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 e084ad55ab..b769cd71f2 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. @@ -11,19 +11,11 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * from struct import * -import binascii -import json -import StringIO -import decimal - -try: - import http.client as httplib -except ImportError: - import httplib -try: - import urllib.parse as urlparse -except ImportError: - import urlparse +from io import BytesIO +from codecs import encode + +import http.client +import urllib.parse def deser_uint256(f): r = 0 @@ -34,17 +26,17 @@ def deser_uint256(f): #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 = httplib.HTTPConnection(host, port) + conn = http.client.HTTPConnection(host, port) conn.request('POST', path, requestdata) if response_object: @@ -55,12 +47,13 @@ def http_post_call(host, port, path, requestdata = '', response_object = 0): class RESTTest (BitcoinTestFramework): FORMAT_SEPARATOR = "." - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 3) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 3 def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir) + 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) @@ -68,8 +61,8 @@ class RESTTest (BitcoinTestFramework): self.sync_all() def run_test(self): - url = urlparse.urlparse(self.nodes[0].url) - print "Mining blocks..." + url = urllib.parse.urlparse(self.nodes[0].url) + print("Mining blocks...") self.nodes[0].generate(1) self.sync_all() @@ -142,17 +135,17 @@ class RESTTest (BitcoinTestFramework): bb_hash = self.nodes[0].getbestblockhash() binaryRequest = b'\x01\x02' - binaryRequest += binascii.unhexlify(txid) - binaryRequest += pack("i", n); - binaryRequest += binascii.unhexlify(vintx); - binaryRequest += pack("i", 0); + 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 = StringIO.StringIO() + output = BytesIO() output.write(bin_response) output.seek(0) chainHeight = unpack("i", output.read(4))[0] - hashFromBinResponse = hex(deser_uint256(output))[2:].zfill(65).rstrip("L") + 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 @@ -186,14 +179,14 @@ class RESTTest (BitcoinTestFramework): #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 + assert_equal(response.status, 400) #must be a 400 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 + assert_equal(response.status, 400) #must be a 400 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 + assert_equal(response.status, 400) #must be a 400 because we send a invalid bin request #test limits json_request = '/checkmempool/' @@ -201,14 +194,14 @@ class RESTTest (BitcoinTestFramework): 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 + assert_equal(response.status, 400) #must be a 400 because we exceeding the limits json_request = '/checkmempool/' for x in range(0, 15): json_request += txid+'-'+str(n)+'/' - json_request = json_request.rstrip("/"); + 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 + assert_equal(response.status, 200) #must be a 200 because we are within the limits self.nodes[0].generate(1) #generate block to not affect upcoming tests self.sync_all() @@ -235,7 +228,7 @@ class RESTTest (BitcoinTestFramework): assert_equal(response_hex.status, 200) assert_greater_than(int(response_hex.getheader('content-length')), 160) response_hex_str = response_hex.read() - assert_equal(response_str.encode("hex")[0:160], response_hex_str[0:160]) + 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) @@ -243,7 +236,7 @@ class RESTTest (BitcoinTestFramework): 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(response_header_str.encode("hex")[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 block_json_string = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+'json') @@ -253,8 +246,8 @@ class RESTTest (BitcoinTestFramework): # 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() - json_obj = json.loads(response_header_json_str, parse_float=decimal.Decimal) + 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 @@ -277,12 +270,12 @@ class RESTTest (BitcoinTestFramework): 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() + 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 = block_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) @@ -293,7 +286,6 @@ class RESTTest (BitcoinTestFramework): 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 = [] diff --git a/qa/rpc-tests/rpcbind_test.py b/qa/rpc-tests/rpcbind_test.py index 7a9da66787..d78d0b884e 100755 --- a/qa/rpc-tests/rpcbind_test.py +++ b/qa/rpc-tests/rpcbind_test.py @@ -1,152 +1,108 @@ -#!/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 - -import json -import shutil -import subprocess -import tempfile -import traceback - +from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * from test_framework.netutil import * -def run_bind_test(tmpdir, allow_ips, connect_to, addresses, expected): - ''' - Start a node with requested rpcallowip and rpcbind parameters, - then try to connect, and check if the set of bound addresses - matches the expected set. - ''' - expected = [(addr_to_hex(addr), port) for (addr, port) in expected] - base_args = ['-disablewallet', '-nolisten'] - 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) - try: - pid = bitcoind_processes[0].pid - assert_equal(set(get_bind_addrs(pid)), set(expected)) - finally: - stop_nodes(nodes) - wait_bitcoinds() - -def run_allowip_test(tmpdir, allow_ips, rpchost, rpcport): - ''' - Start a node with rpcwallow IP, and request getinfo - at a non-localhost IP. - ''' - base_args = ['-disablewallet', '-nolisten'] + ['-rpcallowip='+x for x in allow_ips] - nodes = start_nodes(1, tmpdir, [base_args]) - try: - # connect to node through non-loopback interface - url = "http://rt:rt@%s:%d" % (rpchost, rpcport,) - node = get_rpc_proxy(url, 1) - node.getinfo() - finally: - node = None # make sure connection will be garbage collected and closed - stop_nodes(nodes) - wait_bitcoinds() - -def run_test(tmpdir): - assert(sys.platform == 'linux2') # 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(): - if ip != '127.0.0.1': - non_loopback_ip = ip - break - if non_loopback_ip is None: - assert(not 'This test requires at least one non-loopback IPv4 interface') - print("Using interface %s for testing" % non_loopback_ip) +class RPCBindTest(BitcoinTestFramework): - defaultport = rpc_port(0) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 - # check default without rpcallowip (IPv4 and IPv6 localhost) - run_bind_test(tmpdir, None, '127.0.0.1', [], - [('127.0.0.1', defaultport), ('::1', defaultport)]) - # check default with rpcallowip (IPv6 any) - run_bind_test(tmpdir, ['127.0.0.1'], '127.0.0.1', [], - [('::0', defaultport)]) - # check only IPv4 localhost (explicit) - run_bind_test(tmpdir, ['127.0.0.1'], '127.0.0.1', ['127.0.0.1'], - [('127.0.0.1', defaultport)]) - # check only IPv4 localhost (explicit) with alternative port - run_bind_test(tmpdir, ['127.0.0.1'], '127.0.0.1:32171', ['127.0.0.1:32171'], - [('127.0.0.1', 32171)]) - # check only IPv4 localhost (explicit) with multiple alternative ports on same host - run_bind_test(tmpdir, ['127.0.0.1'], '127.0.0.1:32171', ['127.0.0.1:32171', '127.0.0.1:32172'], - [('127.0.0.1', 32171), ('127.0.0.1', 32172)]) - # check only IPv6 localhost (explicit) - run_bind_test(tmpdir, ['[::1]'], '[::1]', ['[::1]'], - [('::1', defaultport)]) - # check both IPv4 and IPv6 localhost (explicit) - run_bind_test(tmpdir, ['127.0.0.1'], '127.0.0.1', ['127.0.0.1', '[::1]'], - [('127.0.0.1', defaultport), ('::1', defaultport)]) - # check only non-loopback interface - run_bind_test(tmpdir, [non_loopback_ip], non_loopback_ip, [non_loopback_ip], - [(non_loopback_ip, defaultport)]) - - # Check that with invalid rpcallowip, we are denied - run_allowip_test(tmpdir, [non_loopback_ip], non_loopback_ip, defaultport) - try: - run_allowip_test(tmpdir, ['1.1.1.1'], non_loopback_ip, defaultport) - assert(not 'Connection not denied by rpcallowip as expected') - except ValueError: + def setup_network(self): pass -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) - - run_test(options.tmpdir) - - success = True - - except AssertionError as e: - print("Assertion failed: "+e.message) - except Exception as e: - print("Unexpected exception caught during testing: "+str(e)) - traceback.print_tb(sys.exc_info()[2]) - - if not options.nocleanup: - print("Cleaning up") - wait_bitcoinds() - shutil.rmtree(options.tmpdir) + def setup_nodes(self): + pass - if success: - print("Tests successful") - sys.exit(0) - else: - print("Failed") - sys.exit(1) + def run_bind_test(self, allow_ips, connect_to, addresses, expected): + ''' + Start a node with requested rpcallowip and rpcbind parameters, + then try to connect, and check if the set of bound addresses + matches the expected set. + ''' + expected = [(addr_to_hex(addr), port) for (addr, port) in expected] + base_args = ['-disablewallet', '-nolisten'] + if allow_ips: + base_args += ['-rpcallowip=' + x for x in allow_ips] + binds = ['-rpcbind='+addr for addr in addresses] + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [base_args + binds], connect_to) + try: + pid = bitcoind_processes[0].pid + assert_equal(set(get_bind_addrs(pid)), set(expected)) + finally: + stop_nodes(self.nodes) + + def run_allowip_test(self, allow_ips, rpchost, rpcport): + ''' + Start a node with rpcallow IP, and request getnetworkinfo + at a non-localhost IP. + ''' + base_args = ['-disablewallet', '-nolisten'] + ['-rpcallowip='+x for x in allow_ips] + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [base_args]) + try: + # connect to node through non-loopback interface + node = get_rpc_proxy(rpc_url(0, "%s:%d" % (rpchost, rpcport)), 0) + node.getnetworkinfo() + finally: + node = None # make sure connection will be garbage collected and closed + stop_nodes(self.nodes) + + def run_test(self): + # due to OS-specific network stats queries, this test works only on Linux + assert(sys.platform.startswith('linux')) + # find the first non-loopback interface for testing + non_loopback_ip = None + for name,ip in all_interfaces(): + if ip != '127.0.0.1': + non_loopback_ip = ip + break + if non_loopback_ip is None: + assert(not 'This test requires at least one non-loopback IPv4 interface') + print("Using interface %s for testing" % non_loopback_ip) + + defaultport = rpc_port(0) + + # check default without rpcallowip (IPv4 and IPv6 localhost) + self.run_bind_test(None, '127.0.0.1', [], + [('127.0.0.1', defaultport), ('::1', defaultport)]) + # check default with rpcallowip (IPv6 any) + self.run_bind_test(['127.0.0.1'], '127.0.0.1', [], + [('::0', defaultport)]) + # check only IPv4 localhost (explicit) + self.run_bind_test(['127.0.0.1'], '127.0.0.1', ['127.0.0.1'], + [('127.0.0.1', defaultport)]) + # check only IPv4 localhost (explicit) with alternative port + self.run_bind_test(['127.0.0.1'], '127.0.0.1:32171', ['127.0.0.1:32171'], + [('127.0.0.1', 32171)]) + # check only IPv4 localhost (explicit) with multiple alternative ports on same host + self.run_bind_test(['127.0.0.1'], '127.0.0.1:32171', ['127.0.0.1:32171', '127.0.0.1:32172'], + [('127.0.0.1', 32171), ('127.0.0.1', 32172)]) + # check only IPv6 localhost (explicit) + self.run_bind_test(['[::1]'], '[::1]', ['[::1]'], + [('::1', defaultport)]) + # check both IPv4 and IPv6 localhost (explicit) + self.run_bind_test(['127.0.0.1'], '127.0.0.1', ['127.0.0.1', '[::1]'], + [('127.0.0.1', defaultport), ('::1', defaultport)]) + # check only non-loopback interface + self.run_bind_test([non_loopback_ip], non_loopback_ip, [non_loopback_ip], + [(non_loopback_ip, defaultport)]) + + # Check that with invalid rpcallowip, we are denied + self.run_allowip_test([non_loopback_ip], non_loopback_ip, defaultport) + try: + self.run_allowip_test(['1.1.1.1'], non_loopback_ip, defaultport) + assert(not 'Connection not denied by rpcallowip as expected') + except JSONRPCException: + pass if __name__ == '__main__': - main() + RPCBindTest().main() diff --git a/qa/rpc-tests/segwit.py b/qa/rpc-tests/segwit.py new file mode 100755 index 0000000000..41a1b3b20f --- /dev/null +++ b/qa/rpc-tests/segwit.py @@ -0,0 +1,604 @@ +#!/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, CTransaction, CTxIn, COutPoint, CTxOut +from test_framework.address import script_to_p2sh, key_to_p2pkh +from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_1, OP_2, OP_CHECKMULTISIG +from io import BytesIO + +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 + +def find_unspent(node, min_value): + for utxo in node.listunspent(): + if utxo['amount'] >= min_value: + return utxo + +class SegWitTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 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(161) #block 161 + + print("Verify sigops are counted in GBT with pre-BIP141 rules before the fork") + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + tmpl = self.nodes[0].getblocktemplate({}) + assert(tmpl['sigoplimit'] == 20000) + assert(tmpl['transactions'][0]['hash'] == txid) + assert(tmpl['transactions'][0]['sigops'] == 2) + tmpl = self.nodes[0].getblocktemplate({'rules':['segwit']}) + assert(tmpl['sigoplimit'] == 20000) + assert(tmpl['transactions'][0]['hash'] == txid) + assert(tmpl['transactions'][0]['sigops'] == 2) + self.nodes[0].generate(1) #block 162 + + balance_presetup = self.nodes[0].getbalance() + 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], find_unspent(self.nodes[0], 50), self.pubkey[n], False, Decimal("49.999"))) + p2sh_ids[n][v].append(send_to_witness(v, self.nodes[0], find_unspent(self.nodes[0], 50), self.pubkey[n], True, Decimal("49.999"))) + + self.nodes[0].generate(1) #block 163 + sync_blocks(self.nodes) + + # Make sure all nodes recognize the transactions as theirs + assert_equal(self.nodes[0].getbalance(), balance_presetup - 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(260) #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 + + print("Verify sigops are counted in GBT with BIP141 rules after the fork") + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + tmpl = self.nodes[0].getblocktemplate({'rules':['segwit']}) + assert(tmpl['sigoplimit'] == 80000) + assert(tmpl['transactions'][0]['txid'] == txid) + assert(tmpl['transactions'][0]['sigops'] == 8) + + print("Verify non-segwit miners get a valid GBT response after the fork") + send_to_witness(1, self.nodes[0], find_unspent(self.nodes[0], 50), self.pubkey[0], False, Decimal("49.998")) + try: + tmpl = self.nodes[0].getblocktemplate({}) + assert(len(tmpl['transactions']) == 1) # Doesn't include witness tx + assert(tmpl['sigoplimit'] == 20000) + assert(tmpl['transactions'][0]['hash'] == txid) + assert(tmpl['transactions'][0]['sigops'] == 2) + assert(('!segwit' in tmpl['rules']) or ('segwit' not in tmpl['rules'])) + except JSONRPCException: + # This is an acceptable outcome + pass + + print("Verify behaviour of importaddress, addwitnessaddress and listunspent") + + # Some public keys to be used later + pubkeys = [ + "0363D44AABD0F1699138239DF2F042C3282C0671CC7A76826A55C8203D90E39242", # cPiM8Ub4heR9NBYmgVzJQiUH1if44GSBGiqaeJySuL2BKxubvgwb + "02D3E626B3E616FC8662B489C123349FECBFC611E778E5BE739B257EAE4721E5BF", # cPpAdHaD6VoYbW78kveN2bsvb45Q7G5PhaPApVUGwvF8VQ9brD97 + "04A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538A62F5BD8EC85C2477F39650BD391EA6250207065B2A81DA8B009FC891E898F0E", # 91zqCU5B9sdWxzMt1ca3VzbtVm2YM6Hi5Rxn4UDtxEaN9C9nzXV + "02A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538", # cPQFjcVRpAUBG8BA9hzr2yEzHwKoMgLkJZBBtK9vJnvGJgMjzTbd + "036722F784214129FEB9E8129D626324F3F6716555B603FFE8300BBCB882151228", # cQGtcm34xiLjB1v7bkRa4V3aAc9tS2UTuBZ1UnZGeSeNy627fN66 + "0266A8396EE936BF6D99D17920DB21C6C7B1AB14C639D5CD72B300297E416FD2EC", # cTW5mR5M45vHxXkeChZdtSPozrFwFgmEvTNnanCW6wrqwaCZ1X7K + "0450A38BD7F0AC212FEBA77354A9B036A32E0F7C81FC4E0C5ADCA7C549C4505D2522458C2D9AE3CEFD684E039194B72C8A10F9CB9D4764AB26FCC2718D421D3B84", # 92h2XPssjBpsJN5CqSP7v9a7cf2kgDunBC6PDFwJHMACM1rrVBJ + ] + + # Import a compressed key and an uncompressed key, generate some multisig addresses + self.nodes[0].importprivkey("92e6XLo5jVAVwrQKPNTs93oQco8f8sDNBcpv73Dsrs397fQtFQn") + uncompressed_spendable_address = ["mvozP4UwyGD2mGZU4D2eMvMLPB9WkMmMQu"] + self.nodes[0].importprivkey("cNC8eQ5dg3mFAVePDX4ddmPYpPbw41r9bm2jd1nLJT77e6RrzTRR") + compressed_spendable_address = ["mmWQubrDomqpgSYekvsU7HWEVjLFHAakLe"] + assert ((self.nodes[0].validateaddress(uncompressed_spendable_address[0])['iscompressed'] == False)) + assert ((self.nodes[0].validateaddress(compressed_spendable_address[0])['iscompressed'] == True)) + + self.nodes[0].importpubkey(pubkeys[0]) + compressed_solvable_address = [key_to_p2pkh(pubkeys[0])] + self.nodes[0].importpubkey(pubkeys[1]) + compressed_solvable_address.append(key_to_p2pkh(pubkeys[1])) + self.nodes[0].importpubkey(pubkeys[2]) + uncompressed_solvable_address = [key_to_p2pkh(pubkeys[2])] + + spendable_anytime = [] # These outputs should be seen anytime after importprivkey and addmultisigaddress + spendable_after_importaddress = [] # These outputs should be seen after importaddress + solvable_after_importaddress = [] # These outputs should be seen after importaddress but not spendable + unsolvable_after_importaddress = [] # These outputs should be unsolvable after importaddress + solvable_anytime = [] # These outputs should be solvable after importpubkey + unseen_anytime = [] # These outputs should never be seen + + uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]])) + uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]])) + compressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_spendable_address[0]])) + uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], uncompressed_solvable_address[0]])) + compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]])) + compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], compressed_solvable_address[1]])) + unknown_address = ["mtKKyoHabkk6e4ppT7NaM7THqPUt7AzPrT", "2NDP3jLWAFT8NDAiUa9qiE6oBt2awmMq7Dx"] + + # Test multisig_without_privkey + # We have 2 public keys without private keys, use addmultisigaddress to add to wallet. + # Money sent to P2SH of multisig of this should only be seen after importaddress with the BASE58 P2SH address. + + multisig_without_privkey_address = self.nodes[0].addmultisigaddress(2, [pubkeys[3], pubkeys[4]]) + script = CScript([OP_2, hex_str_to_bytes(pubkeys[3]), hex_str_to_bytes(pubkeys[4]), OP_2, OP_CHECKMULTISIG]) + solvable_after_importaddress.append(CScript([OP_HASH160, hash160(script), OP_EQUAL])) + + for i in compressed_spendable_address: + v = self.nodes[0].validateaddress(i) + if (v['isscript']): + [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) + # bare and p2sh multisig with compressed keys should always be spendable + spendable_anytime.extend([bare, p2sh]) + # P2WSH and P2SH(P2WSH) multisig with compressed keys are spendable after direct importaddress + spendable_after_importaddress.extend([p2wsh, p2sh_p2wsh]) + else: + [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) + # normal P2PKH and P2PK with compressed keys should always be spendable + spendable_anytime.extend([p2pkh, p2pk]) + # P2SH_P2PK, P2SH_P2PKH, and witness with compressed keys are spendable after direct importaddress + spendable_after_importaddress.extend([p2wpkh, p2sh_p2wpkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]) + + for i in uncompressed_spendable_address: + v = self.nodes[0].validateaddress(i) + if (v['isscript']): + [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) + # bare and p2sh multisig with uncompressed keys should always be spendable + spendable_anytime.extend([bare, p2sh]) + # P2WSH and P2SH(P2WSH) multisig with uncompressed keys are never seen + unseen_anytime.extend([p2wsh, p2sh_p2wsh]) + else: + [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) + # normal P2PKH and P2PK with uncompressed keys should always be spendable + spendable_anytime.extend([p2pkh, p2pk]) + # P2SH_P2PK and P2SH_P2PKH are spendable after direct importaddress + spendable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh]) + # witness with uncompressed keys are never seen + unseen_anytime.extend([p2wpkh, p2sh_p2wpkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]) + + for i in compressed_solvable_address: + v = self.nodes[0].validateaddress(i) + if (v['isscript']): + # Multisig without private is not seen after addmultisigaddress, but seen after importaddress + [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) + solvable_after_importaddress.extend([bare, p2sh, p2wsh, p2sh_p2wsh]) + else: + [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) + # normal P2PKH and P2PK with compressed keys should always be seen + solvable_anytime.extend([p2pkh, p2pk]) + # P2SH_P2PK, P2SH_P2PKH, and witness with compressed keys are seen after direct importaddress + solvable_after_importaddress.extend([p2wpkh, p2sh_p2wpkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]) + + for i in uncompressed_solvable_address: + v = self.nodes[0].validateaddress(i) + if (v['isscript']): + [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) + # Base uncompressed multisig without private is not seen after addmultisigaddress, but seen after importaddress + solvable_after_importaddress.extend([bare, p2sh]) + # P2WSH and P2SH(P2WSH) multisig with uncompressed keys are never seen + unseen_anytime.extend([p2wsh, p2sh_p2wsh]) + else: + [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) + # normal P2PKH and P2PK with uncompressed keys should always be seen + solvable_anytime.extend([p2pkh, p2pk]) + # P2SH_P2PK, P2SH_P2PKH with uncompressed keys are seen after direct importaddress + solvable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh]) + # witness with uncompressed keys are never seen + unseen_anytime.extend([p2wpkh, p2sh_p2wpkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]) + + op1 = CScript([OP_1]) + op0 = CScript([OP_0]) + # 2N7MGY19ti4KDMSzRfPAssP6Pxyuxoi6jLe is the P2SH(P2PKH) version of mjoE3sSrb8ByYEvgnC3Aox86u1CHnfJA4V + unsolvable_address = ["mjoE3sSrb8ByYEvgnC3Aox86u1CHnfJA4V", "2N7MGY19ti4KDMSzRfPAssP6Pxyuxoi6jLe", script_to_p2sh(op1), script_to_p2sh(op0)] + unsolvable_address_key = hex_str_to_bytes("02341AEC7587A51CDE5279E0630A531AEA2615A9F80B17E8D9376327BAEAA59E3D") + unsolvablep2pkh = CScript([OP_DUP, OP_HASH160, hash160(unsolvable_address_key), OP_EQUALVERIFY, OP_CHECKSIG]) + unsolvablep2wshp2pkh = CScript([OP_0, sha256(unsolvablep2pkh)]) + p2shop0 = CScript([OP_HASH160, hash160(op0), OP_EQUAL]) + p2wshop1 = CScript([OP_0, sha256(op1)]) + unsolvable_after_importaddress.append(unsolvablep2pkh) + unsolvable_after_importaddress.append(unsolvablep2wshp2pkh) + unsolvable_after_importaddress.append(op1) # OP_1 will be imported as script + unsolvable_after_importaddress.append(p2wshop1) + unseen_anytime.append(op0) # OP_0 will be imported as P2SH address with no script provided + unsolvable_after_importaddress.append(p2shop0) + + spendable_txid = [] + solvable_txid = [] + spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime, 2)) + solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime, 1)) + self.mine_and_test_listunspent(spendable_after_importaddress + solvable_after_importaddress + unseen_anytime + unsolvable_after_importaddress, 0) + + importlist = [] + for i in compressed_spendable_address + uncompressed_spendable_address + compressed_solvable_address + uncompressed_solvable_address: + v = self.nodes[0].validateaddress(i) + if (v['isscript']): + bare = hex_str_to_bytes(v['hex']) + importlist.append(bytes_to_hex_str(bare)) + importlist.append(bytes_to_hex_str(CScript([OP_0, sha256(bare)]))) + else: + pubkey = hex_str_to_bytes(v['pubkey']) + p2pk = CScript([pubkey, OP_CHECKSIG]) + p2pkh = CScript([OP_DUP, OP_HASH160, hash160(pubkey), OP_EQUALVERIFY, OP_CHECKSIG]) + importlist.append(bytes_to_hex_str(p2pk)) + importlist.append(bytes_to_hex_str(p2pkh)) + importlist.append(bytes_to_hex_str(CScript([OP_0, hash160(pubkey)]))) + importlist.append(bytes_to_hex_str(CScript([OP_0, sha256(p2pk)]))) + importlist.append(bytes_to_hex_str(CScript([OP_0, sha256(p2pkh)]))) + + importlist.append(bytes_to_hex_str(unsolvablep2pkh)) + importlist.append(bytes_to_hex_str(unsolvablep2wshp2pkh)) + importlist.append(bytes_to_hex_str(op1)) + importlist.append(bytes_to_hex_str(p2wshop1)) + + for i in importlist: + try: + self.nodes[0].importaddress(i,"",False,True) + except JSONRPCException as exp: + assert_equal(exp.error["message"], "The wallet already contains the private key for this address or script") + + self.nodes[0].importaddress(script_to_p2sh(op0)) # import OP_0 as address only + self.nodes[0].importaddress(multisig_without_privkey_address) # Test multisig_without_privkey + + spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime + spendable_after_importaddress, 2)) + solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime + solvable_after_importaddress, 1)) + self.mine_and_test_listunspent(unsolvable_after_importaddress, 1) + self.mine_and_test_listunspent(unseen_anytime, 0) + + # addwitnessaddress should refuse to return a witness address if an uncompressed key is used or the address is + # not in the wallet + # note that no witness address should be returned by unsolvable addresses + # the multisig_without_privkey_address will fail because its keys were not added with importpubkey + for i in uncompressed_spendable_address + uncompressed_solvable_address + unknown_address + unsolvable_address + [multisig_without_privkey_address]: + try: + self.nodes[0].addwitnessaddress(i) + except JSONRPCException as exp: + assert_equal(exp.error["message"], "Public key or redeemscript not known to wallet, or the key is uncompressed") + else: + assert(False) + + for i in compressed_spendable_address + compressed_solvable_address: + witaddress = self.nodes[0].addwitnessaddress(i) + # addwitnessaddress should return the same address if it is a known P2SH-witness address + assert_equal(witaddress, self.nodes[0].addwitnessaddress(witaddress)) + + spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime + spendable_after_importaddress, 2)) + solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime + solvable_after_importaddress, 1)) + self.mine_and_test_listunspent(unsolvable_after_importaddress, 1) + self.mine_and_test_listunspent(unseen_anytime, 0) + + # Repeat some tests. This time we don't add witness scripts with importaddress + # Import a compressed key and an uncompressed key, generate some multisig addresses + self.nodes[0].importprivkey("927pw6RW8ZekycnXqBQ2JS5nPyo1yRfGNN8oq74HeddWSpafDJH") + uncompressed_spendable_address = ["mguN2vNSCEUh6rJaXoAVwY3YZwZvEmf5xi"] + self.nodes[0].importprivkey("cMcrXaaUC48ZKpcyydfFo8PxHAjpsYLhdsp6nmtB3E2ER9UUHWnw") + compressed_spendable_address = ["n1UNmpmbVUJ9ytXYXiurmGPQ3TRrXqPWKL"] + + self.nodes[0].importpubkey(pubkeys[5]) + compressed_solvable_address = [key_to_p2pkh(pubkeys[5])] + self.nodes[0].importpubkey(pubkeys[6]) + uncompressed_solvable_address = [key_to_p2pkh(pubkeys[6])] + + spendable_after_addwitnessaddress = [] # These outputs should be seen after importaddress + solvable_after_addwitnessaddress=[] # These outputs should be seen after importaddress but not spendable + unseen_anytime = [] # These outputs should never be seen + + uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]])) + uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]])) + compressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_spendable_address[0]])) + uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], uncompressed_solvable_address[0]])) + compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]])) + + premature_witaddress = [] + + for i in compressed_spendable_address: + v = self.nodes[0].validateaddress(i) + if (v['isscript']): + [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) + # P2WSH and P2SH(P2WSH) multisig with compressed keys are spendable after addwitnessaddress + spendable_after_addwitnessaddress.extend([p2wsh, p2sh_p2wsh]) + premature_witaddress.append(script_to_p2sh(p2wsh)) + else: + [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) + # P2WPKH, P2SH_P2WPKH are spendable after addwitnessaddress + spendable_after_addwitnessaddress.extend([p2wpkh, p2sh_p2wpkh]) + premature_witaddress.append(script_to_p2sh(p2wpkh)) + + for i in uncompressed_spendable_address + uncompressed_solvable_address: + v = self.nodes[0].validateaddress(i) + if (v['isscript']): + [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) + # P2WSH and P2SH(P2WSH) multisig with uncompressed keys are never seen + unseen_anytime.extend([p2wsh, p2sh_p2wsh]) + else: + [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) + # P2WPKH, P2SH_P2WPKH with uncompressed keys are never seen + unseen_anytime.extend([p2wpkh, p2sh_p2wpkh]) + + for i in compressed_solvable_address: + v = self.nodes[0].validateaddress(i) + if (v['isscript']): + # P2WSH multisig without private key are seen after addwitnessaddress + [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) + solvable_after_addwitnessaddress.extend([p2wsh, p2sh_p2wsh]) + premature_witaddress.append(script_to_p2sh(p2wsh)) + else: + [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) + # P2SH_P2PK, P2SH_P2PKH with compressed keys are seen after addwitnessaddress + solvable_after_addwitnessaddress.extend([p2wpkh, p2sh_p2wpkh]) + premature_witaddress.append(script_to_p2sh(p2wpkh)) + + self.mine_and_test_listunspent(spendable_after_addwitnessaddress + solvable_after_addwitnessaddress + unseen_anytime, 0) + + # addwitnessaddress should refuse to return a witness address if an uncompressed key is used + # note that a multisig address returned by addmultisigaddress is not solvable until it is added with importaddress + # premature_witaddress are not accepted until the script is added with addwitnessaddress first + for i in uncompressed_spendable_address + uncompressed_solvable_address + premature_witaddress + [compressed_solvable_address[1]]: + try: + self.nodes[0].addwitnessaddress(i) + except JSONRPCException as exp: + assert_equal(exp.error["message"], "Public key or redeemscript not known to wallet, or the key is uncompressed") + else: + assert(False) + + # after importaddress it should pass addwitnessaddress + v = self.nodes[0].validateaddress(compressed_solvable_address[1]) + self.nodes[0].importaddress(v['hex'],"",False,True) + for i in compressed_spendable_address + compressed_solvable_address + premature_witaddress: + witaddress = self.nodes[0].addwitnessaddress(i) + assert_equal(witaddress, self.nodes[0].addwitnessaddress(witaddress)) + + spendable_txid.append(self.mine_and_test_listunspent(spendable_after_addwitnessaddress, 2)) + solvable_txid.append(self.mine_and_test_listunspent(solvable_after_addwitnessaddress, 1)) + self.mine_and_test_listunspent(unseen_anytime, 0) + + # Check that spendable outputs are really spendable + self.create_and_mine_tx_from_txids(spendable_txid) + + # import all the private keys so solvable addresses become spendable + self.nodes[0].importprivkey("cPiM8Ub4heR9NBYmgVzJQiUH1if44GSBGiqaeJySuL2BKxubvgwb") + self.nodes[0].importprivkey("cPpAdHaD6VoYbW78kveN2bsvb45Q7G5PhaPApVUGwvF8VQ9brD97") + self.nodes[0].importprivkey("91zqCU5B9sdWxzMt1ca3VzbtVm2YM6Hi5Rxn4UDtxEaN9C9nzXV") + self.nodes[0].importprivkey("cPQFjcVRpAUBG8BA9hzr2yEzHwKoMgLkJZBBtK9vJnvGJgMjzTbd") + self.nodes[0].importprivkey("cQGtcm34xiLjB1v7bkRa4V3aAc9tS2UTuBZ1UnZGeSeNy627fN66") + self.nodes[0].importprivkey("cTW5mR5M45vHxXkeChZdtSPozrFwFgmEvTNnanCW6wrqwaCZ1X7K") + self.create_and_mine_tx_from_txids(solvable_txid) + + def mine_and_test_listunspent(self, script_list, ismine): + utxo = find_unspent(self.nodes[0], 50) + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(int('0x'+utxo['txid'],0), utxo['vout']))) + for i in script_list: + tx.vout.append(CTxOut(10000000, i)) + tx.rehash() + signresults = self.nodes[0].signrawtransaction(bytes_to_hex_str(tx.serialize_without_witness()))['hex'] + txid = self.nodes[0].sendrawtransaction(signresults, True) + self.nodes[0].generate(1) + sync_blocks(self.nodes) + watchcount = 0 + spendcount = 0 + for i in self.nodes[0].listunspent(): + if (i['txid'] == txid): + watchcount += 1 + if (i['spendable'] == True): + spendcount += 1 + if (ismine == 2): + assert_equal(spendcount, len(script_list)) + elif (ismine == 1): + assert_equal(watchcount, len(script_list)) + assert_equal(spendcount, 0) + else: + assert_equal(watchcount, 0) + return txid + + def p2sh_address_to_script(self,v): + bare = CScript(hex_str_to_bytes(v['hex'])) + p2sh = CScript(hex_str_to_bytes(v['scriptPubKey'])) + p2wsh = CScript([OP_0, sha256(bare)]) + p2sh_p2wsh = CScript([OP_HASH160, hash160(p2wsh), OP_EQUAL]) + return([bare, p2sh, p2wsh, p2sh_p2wsh]) + + def p2pkh_address_to_script(self,v): + pubkey = hex_str_to_bytes(v['pubkey']) + p2wpkh = CScript([OP_0, hash160(pubkey)]) + p2sh_p2wpkh = CScript([OP_HASH160, hash160(p2wpkh), OP_EQUAL]) + p2pk = CScript([pubkey, OP_CHECKSIG]) + p2pkh = CScript(hex_str_to_bytes(v['scriptPubKey'])) + p2sh_p2pk = CScript([OP_HASH160, hash160(p2pk), OP_EQUAL]) + p2sh_p2pkh = CScript([OP_HASH160, hash160(p2pkh), OP_EQUAL]) + p2wsh_p2pk = CScript([OP_0, sha256(p2pk)]) + p2wsh_p2pkh = CScript([OP_0, sha256(p2pkh)]) + p2sh_p2wsh_p2pk = CScript([OP_HASH160, hash160(p2wsh_p2pk), OP_EQUAL]) + p2sh_p2wsh_p2pkh = CScript([OP_HASH160, hash160(p2wsh_p2pkh), OP_EQUAL]) + return [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] + + def create_and_mine_tx_from_txids(self, txids, success = True): + tx = CTransaction() + for i in txids: + txtmp = CTransaction() + txraw = self.nodes[0].getrawtransaction(i) + f = BytesIO(hex_str_to_bytes(txraw)) + txtmp.deserialize(f) + for j in range(len(txtmp.vout)): + tx.vin.append(CTxIn(COutPoint(int('0x'+i,0), j))) + tx.vout.append(CTxOut(0, CScript())) + tx.rehash() + signresults = self.nodes[0].signrawtransaction(bytes_to_hex_str(tx.serialize_without_witness()))['hex'] + self.nodes[0].sendrawtransaction(signresults, True) + self.nodes[0].generate(1) + sync_blocks(self.nodes) + + +if __name__ == '__main__': + SegWitTest().main() diff --git a/qa/rpc-tests/sendheaders.py b/qa/rpc-tests/sendheaders.py index d7f4292090..81b2442e6a 100755 --- a/qa/rpc-tests/sendheaders.py +++ b/qa/rpc-tests/sendheaders.py @@ -1,13 +1,11 @@ -#!/usr/bin/env python2 -# -# 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. -# 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 ''' @@ -65,21 +63,36 @@ 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): +direct_fetch_response_time = 0.05 + +class BaseNode(SingleNodeConnCB): def __init__(self): - NodeConnCB.__init__(self) - self.create_callback_map() - self.connection = None + SingleNodeConnCB.__init__(self) 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 + self.last_blockhash_announced = None def clear_last_announcement(self): with mininode_lock: @@ -87,9 +100,6 @@ class BaseNode(NodeConnCB): 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() @@ -108,17 +118,17 @@ class BaseNode(NodeConnCB): 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 + self.last_blockhash_announced = message.inv[-1].hash def on_headers(self, conn, message): self.last_headers = message - self.block_announced = True + if len(message.headers): + self.block_announced = True + message.headers[-1].calc_sha256() + self.last_blockhash_announced = message.headers[-1].sha256 def on_block(self, conn, message): self.last_block = message.block @@ -127,8 +137,11 @@ class BaseNode(NodeConnCB): 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 @@ -137,7 +150,7 @@ class BaseNode(NodeConnCB): expect_headers = headers if headers != None else [] expect_inv = inv if inv != None else [] test_function = lambda: self.block_announced - self.sync(test_function) + assert(wait_until(test_function, timeout=60)) with mininode_lock: self.block_announced = False @@ -160,25 +173,14 @@ class BaseNode(NodeConnCB): 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) + assert(wait_until(test_function, timeout=timeout)) + return + + def wait_for_getheaders(self, timeout=60): + test_function = lambda: self.last_getheaders != None + assert(wait_until(test_function, timeout=timeout)) return def wait_for_getdata(self, hash_list, timeout=60): @@ -186,7 +188,17 @@ class BaseNode(NodeConnCB): 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) + assert(wait_until(test_function, timeout=timeout)) + return + + def wait_for_disconnect(self, timeout=60): + test_function = lambda: self.disconnected + assert(wait_until(test_function, timeout=timeout)) + return + + def wait_for_block_announcement(self, block_hash, timeout=60): + test_function = lambda: self.last_blockhash_announced == block_hash + assert(wait_until(test_function, timeout=timeout)) return def send_header_for_blocks(self, new_blocks): @@ -211,28 +223,34 @@ class TestNode(BaseNode): BaseNode.__init__(self) class SendHeadersTest(BitcoinTestFramework): - def setup_chain(self): - initialize_chain_clean(self.options.tmpdir, 2) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 2 def setup_network(self): self.nodes = [] - self.nodes = start_nodes(2, self.options.tmpdir, [["-debug", "-logtimemicros=1"]]*2) + 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). - # peers is the p2p nodes we're using; we clear their state after the + # 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, peers): + 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 peers] + for x in self.p2p_connections: + x.wait_for_block_announcement(int(self.nodes[0].getbestblockhash(), 16)) + x.clear_last_announcement() tip_height = self.nodes[1].getblockcount() hash_to_invalidate = self.nodes[1].getblockhash(tip_height-(length-1)) @@ -246,6 +264,8 @@ class SendHeadersTest(BitcoinTestFramework): 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 @@ -264,8 +284,8 @@ class SendHeadersTest(BitcoinTestFramework): # PART 1 # 1. Mine a block; expect inv announcements each time - print "Part 1: headers don't start before sendheaders message..." - for i in xrange(4): + 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) @@ -295,16 +315,15 @@ class SendHeadersTest(BitcoinTestFramework): inv_node.clear_last_announcement() test_node.clear_last_announcement() - print "Part 1: success!" - print "Part 2: announce blocks with headers after sendheaders message..." + 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=0L) + test_node.get_headers(locator=[prev_tip], hashstop=0) test_node.sync_with_ping() - test_node.clear_last_announcement() # Clear out empty headers response # Now that we've synced headers, headers announcements should work tip = self.mine_blocks(1) @@ -313,14 +332,14 @@ class SendHeadersTest(BitcoinTestFramework): height = self.nodes[0].getblockcount()+1 block_time += 10 # Advance far enough ahead - for i in xrange(10): + 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 xrange(2): + for j in range(2): blocks = [] - for b in xrange(i+1): + for b in range(i+1): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 @@ -353,23 +372,21 @@ class SendHeadersTest(BitcoinTestFramework): # broadcast it) assert_equal(inv_node.last_inv, None) assert_equal(inv_node.last_headers, None) - inv_node.clear_last_announcement() - test_node.clear_last_announcement() 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 2: success!") - print "Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer..." + 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 xrange(2): + for j in range(2): # First try mining a reorg that can propagate with header announcement - new_block_hashes = self.mine_reorg(length=7, peers=[test_node, inv_node]) + 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) @@ -377,7 +394,7 @@ class SendHeadersTest(BitcoinTestFramework): block_time += 8 # Mine a too-large reorg, which should be announced with a single inv - new_block_hashes = self.mine_reorg(length=8, peers=[test_node, inv_node]) + 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) @@ -389,11 +406,11 @@ class SendHeadersTest(BitcoinTestFramework): # Use getblocks/getdata test_node.send_getblocks(locator = [fork_point]) - assert_equal(test_node.check_last_announcement(inv=new_block_hashes[0:-1]), True) + 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 xrange(3): + 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) @@ -408,7 +425,6 @@ class SendHeadersTest(BitcoinTestFramework): test_node.get_headers(locator=[fork_point], hashstop=new_block_hashes[1]) test_node.get_data([tip]) test_node.wait_for_block(tip) - test_node.clear_last_announcement() elif i == 2: test_node.get_data([tip]) test_node.wait_for_block(tip) @@ -416,7 +432,7 @@ class SendHeadersTest(BitcoinTestFramework): # 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=0L) + test_node.get_headers(locator=[tip], hashstop=0) test_node.sync_with_ping() else: test_node.send_block_inv(tip) @@ -426,9 +442,9 @@ class SendHeadersTest(BitcoinTestFramework): 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 3: success!") - print "Part 4: Testing direct fetch behavior..." + 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'] @@ -436,7 +452,7 @@ class SendHeadersTest(BitcoinTestFramework): # Create 2 blocks. Send the blocks, then send the headers. blocks = [] - for b in xrange(2): + for b in range(2): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 @@ -446,7 +462,7 @@ class SendHeadersTest(BitcoinTestFramework): inv_node.sync_with_ping() # Make sure blocks are processed test_node.last_getdata = None - test_node.send_header_for_blocks(blocks); + test_node.send_header_for_blocks(blocks) test_node.sync_with_ping() # should not have received any getdata messages with mininode_lock: @@ -454,7 +470,7 @@ class SendHeadersTest(BitcoinTestFramework): # This time, direct fetch should work blocks = [] - for b in xrange(3): + for b in range(3): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 @@ -463,7 +479,7 @@ class SendHeadersTest(BitcoinTestFramework): 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.wait_for_getdata([x.sha256 for x in blocks], timeout=direct_fetch_response_time) [ test_node.send_message(msg_block(x)) for x in blocks ] @@ -475,7 +491,7 @@ class SendHeadersTest(BitcoinTestFramework): blocks = [] # Create extra blocks for later - for b in xrange(20): + for b in range(20): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 @@ -494,13 +510,13 @@ class SendHeadersTest(BitcoinTestFramework): # 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) + test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=direct_fetch_response_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) + test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=direct_fetch_response_time) # Announcing 1 more header should not trigger any response test_node.last_getdata = None @@ -509,7 +525,79 @@ class SendHeadersTest(BitcoinTestFramework): with mininode_lock: assert_equal(test_node.last_getdata, None) - print "Part 4: success!" + 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 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 index d51d6ee610..c61a280616 100755 --- a/qa/rpc-tests/signrawtransactions.py +++ b/qa/rpc-tests/signrawtransactions.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python2 -# Copyright (c) 2015 The Bitcoin Core developers +#!/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. @@ -10,12 +10,13 @@ from test_framework.util import * class SignRawTransactionsTest(BitcoinTestFramework): """Tests transaction signing via RPC command "signrawtransaction".""" - def setup_chain(self): - print('Initializing test directory ' + self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 1) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 def setup_network(self, split=False): - self.nodes = start_nodes(1, self.options.tmpdir) + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir) self.is_network_split = False def successful_signing_test(self): diff --git a/qa/rpc-tests/smartfees.py b/qa/rpc-tests/smartfees.py index b209ae0c16..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-2015 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,6 +7,7 @@ # Test fee estimation code # +from collections import OrderedDict from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * @@ -22,7 +23,7 @@ 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 trival P2SH script, and assumes that its inputs + 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. @@ -49,10 +50,10 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee if total_in <= amount + fee: raise RuntimeError("Insufficient funds: need %d, have %d"%(amount+fee, total_in)) outputs = {} - outputs[P2SH_1] = total_in - amount - fee - outputs[P2SH_2] = amount + outputs = OrderedDict([(P2SH_1, total_in - amount - fee), + (P2SH_2, amount)]) rawtx = from_node.createrawtransaction(inputs, outputs) - # Createrawtransaction constructions a transaction that is ready to be signed + # 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] @@ -78,12 +79,10 @@ def split_inputs(from_node, txins, txouts, initial_split = False): ''' prevtxout = txins.pop() inputs = [] - outputs = {} 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[P2SH_1] = half_change - outputs[P2SH_2] = rem_change + 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 @@ -105,7 +104,7 @@ def check_estimates(node, fees_seen, max_invalid, print_estimates = True): 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 filter(lambda x: x >= 0, all_estimates): + 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)" @@ -146,6 +145,11 @@ def check_estimates(node, fees_seen, max_invalid, print_estimates = True): 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. @@ -219,12 +223,12 @@ class EstimateFeeTest(BitcoinTestFramework): 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 + 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 + # update which txouts are confirmed newmem = [] for utx in self.memutxo: if utx["txid"] in mined: @@ -239,7 +243,7 @@ class EstimateFeeTest(BitcoinTestFramework): 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") - for i in xrange(2): + 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]) diff --git a/qa/rpc-tests/test_framework/address.py b/qa/rpc-tests/test_framework/address.py new file mode 100644 index 0000000000..50b999be61 --- /dev/null +++ b/qa/rpc-tests/test_framework/address.py @@ -0,0 +1,74 @@ +#!/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. + +# +# address.py +# +# This file encodes and decodes BASE58 P2PKH and P2SH addresses +# + +from .script import hash256, hash160, sha256, CScript, OP_0 +from .util import bytes_to_hex_str, hex_str_to_bytes + +chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + +def byte_to_base58(b, version): + result = '' + str = bytes_to_hex_str(b) + str = bytes_to_hex_str(chr(version).encode('latin-1')) + str + checksum = bytes_to_hex_str(hash256(hex_str_to_bytes(str))) + str += checksum[:8] + value = int('0x'+str,0) + while value > 0: + result = chars[value % 58] + result + value //= 58 + while (str[:2] == '00'): + result = chars[0] + result + str = str[2:] + return result + +# TODO: def base58_decode + +def keyhash_to_p2pkh(hash, main = False): + assert (len(hash) == 20) + version = 0 if main else 111 + return byte_to_base58(hash, version) + +def scripthash_to_p2sh(hash, main = False): + assert (len(hash) == 20) + version = 5 if main else 196 + return byte_to_base58(hash, version) + +def key_to_p2pkh(key, main = False): + key = check_key(key) + return keyhash_to_p2pkh(hash160(key), main) + +def script_to_p2sh(script, main = False): + script = check_script(script) + return scripthash_to_p2sh(hash160(script), main) + +def key_to_p2sh_p2wpkh(key, main = False): + key = check_key(key) + p2shscript = CScript([OP_0, hash160(key)]) + return script_to_p2sh(p2shscript, main) + +def script_to_p2sh_p2wsh(script, main = False): + script = check_script(script) + p2shscript = CScript([OP_0, sha256(script)]) + return script_to_p2sh(p2shscript, main) + +def check_key(key): + if (type(key) is str): + key = hex_str_to_bytes(key) # Assuming this is hex string + if (type(key) is bytes and (len(key) == 33 or len(key) == 65)): + return key + assert(False) + +def check_script(script): + if (type(script) is str): + script = hex_str_to_bytes(script) # Assuming this is hex string + if (type(script) is bytes or type(script) is CScript): + return script + assert(False) diff --git a/qa/rpc-tests/test_framework/authproxy.py b/qa/rpc-tests/test_framework/authproxy.py index fba469a0dd..fd7f32b5c6 100644 --- a/qa/rpc-tests/test_framework/authproxy.py +++ b/qa/rpc-tests/test_framework/authproxy.py @@ -55,21 +55,27 @@ log = logging.getLogger("BitcoinRPC") class JSONRPCException(Exception): def __init__(self, rpc_error): - Exception.__init__(self) + try: + errmsg = '%(message)s (%(code)i)' % rpc_error + except (KeyError, TypeError): + errmsg = '' + Exception.__init__(self, errmsg) self.error = rpc_error 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.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,11 +98,10 @@ 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('__'): @@ -125,17 +130,23 @@ class AuthServiceProxy(object): return self._get_response() else: raise + except (BrokenPipeError,ConnectionResetError): + # Python 3.5+ raises BrokenPipeError instead of BadStatusLine when the connection was reset + # ConnectionResetError happens on FreeBSD with Python 3.4 + 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))) + json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii))) postdata = json.dumps({'version': '1.1', 'method': self._service_name, 'params': args, - 'id': AuthServiceProxy.__id_count}, default=EncodeDecimal) - response = self._request('POST', self.__url.path, postdata) + '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: @@ -145,9 +156,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) - return self._request('POST', self.__url.path, postdata) + return self._request('POST', self.__url.path, postdata.encode('utf-8')) def _get_response(self): http_response = self.__conn.getresponse() @@ -155,10 +166,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 index b0c58ccd47..ef800e4d57 100644 --- a/qa/rpc-tests/test_framework/bignum.py +++ b/qa/rpc-tests/test_framework/bignum.py @@ -1,16 +1,15 @@ -# +#!/usr/bin/env python3 # # bignum.py # # This file is copied from python-bitcoinlib. # -# Distributed under the MIT/X11 software license, see the accompanying +# Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # """Bignum routines""" -from __future__ import absolute_import, division, print_function, unicode_literals import struct diff --git a/qa/rpc-tests/test_framework/blockstore.py b/qa/rpc-tests/test_framework/blockstore.py index b9775b477c..28a6b92b81 100644 --- a/qa/rpc-tests/test_framework/blockstore.py +++ b/qa/rpc-tests/test_framework/blockstore.py @@ -1,30 +1,46 @@ +#!/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. # 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 * -import dbm +from .mininode import * +from io import BytesIO +import dbm.dumb as dbmd class BlockStore(object): def __init__(self, datadir): - self.blockDB = dbm.open(datadir + "/blocks", 'c') - self.currentBlock = 0L + self.blockDB = dbmd.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): - serialized_block = None + value = None try: - serialized_block = self.blockDB[repr(blockhash)] + value = self.blockDB[repr(blockhash)] except KeyError: return None - f = cStringIO.StringIO(serialized_block) - ret = CBlock() - ret.deserialize(f) - ret.calc_sha256() + 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): @@ -66,20 +82,23 @@ class BlockStore(object): try: self.blockDB[repr(block.sha256)] = bytes(block.serialize()) except TypeError as e: - print "Unexpected error: ", sys.exc_info()[0], e.args + 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 - block = self.get(i.hash) - if block is not None: - responses.append(msg_block(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): @@ -88,11 +107,11 @@ class BlockStore(object): r = [] counter = 0 step = 1 - lastBlock = self.get(current_tip) + lastBlock = self.get_block(current_tip) while lastBlock is not None: r.append(lastBlock.hashPrevBlock) for i in range(step): - lastBlock = self.get(lastBlock.hashPrevBlock) + lastBlock = self.get_block(lastBlock.hashPrevBlock) if lastBlock is None: break counter += 1 @@ -104,21 +123,28 @@ class BlockStore(object): class TxStore(object): def __init__(self, datadir): - self.txDB = dbm.open(datadir + "/transactions", 'c') + self.txDB = dbmd.open(datadir + "/transactions", 'c') def close(self): self.txDB.close() + # lookup an entry and return the item as raw bytes def get(self, txhash): - serialized_tx = None + value = None try: - serialized_tx = self.txDB[repr(txhash)] + value = self.txDB[repr(txhash)] except KeyError: return None - f = cStringIO.StringIO(serialized_tx) - ret = CTransaction() - ret.deserialize(f) - ret.calc_sha256() + 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): @@ -126,7 +152,7 @@ class TxStore(object): try: self.txDB[repr(tx.sha256)] = bytes(tx.serialize()) except TypeError as e: - print "Unexpected error: ", sys.exc_info()[0], e.args + print("Unexpected error: ", sys.exc_info()[0], e.args) def get_transactions(self, inv): responses = [] @@ -134,5 +160,5 @@ class TxStore(object): if (i.type == 1): # MSG_TX tx = self.get(i.hash) if tx is not None: - responses.append(msg_tx(tx)) + 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 index 59aa8c15cc..f69958823c 100644 --- a/qa/rpc-tests/test_framework/blocktools.py +++ b/qa/rpc-tests/test_framework/blocktools.py @@ -1,11 +1,11 @@ +#!/usr/bin/env python3 # blocktools.py - utilities for manipulating blocks and transactions -# -# Distributed under the MIT/X11 software license, see the accompanying +# 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, CScriptOp, OP_TRUE, OP_CHECKSIG +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): @@ -22,6 +22,29 @@ def create_block(hashprev, coinbase, nTime=None): 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: @@ -29,7 +52,7 @@ def serialize_script_num(value): neg = value < 0 absvalue = -value if neg else value while (absvalue): - r.append(chr(absvalue & 0xff)) + r.append(int(absvalue & 0xff)) absvalue >>= 8 if r[-1] & 0x80: r.append(0x80 if neg else 0) @@ -45,7 +68,7 @@ def create_coinbase(height, pubkey = None): coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), ser_string(serialize_script_num(height)), 0xffffffff)) coinbaseoutput = CTxOut() - coinbaseoutput.nValue = 50*100000000 + coinbaseoutput.nValue = 50 * COIN halvings = int(height/150) # regtest coinbaseoutput.nValue >>= halvings if (pubkey != None): @@ -56,12 +79,27 @@ def create_coinbase(height, pubkey = None): coinbase.calc_sha256() return coinbase -# Create a transaction with an anyone-can-spend output, that spends the -# nth output of prevtx. -def create_transaction(prevtx, n, sig, value): +# 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, "")) + 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 index e0b3ce040d..7c92d3f828 100755 --- a/qa/rpc-tests/test_framework/comptool.py +++ b/qa/rpc-tests/test_framework/comptool.py @@ -1,12 +1,11 @@ -#!/usr/bin/env python2 -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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 +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 @@ -27,31 +26,32 @@ generator that returns TestInstance objects. See below for definition. global mininode_lock -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 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.create_callback_map() 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 @@ -95,6 +95,12 @@ class TestNode(NodeConnCB): 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)])) @@ -172,6 +178,10 @@ class TestManager(object): # 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) @@ -244,6 +254,15 @@ class TestManager(object): 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 @@ -263,6 +282,15 @@ class TestManager(object): 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 @@ -364,7 +392,7 @@ class TestManager(object): 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 ] + 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 ] diff --git a/qa/rpc-tests/test_framework/coverage.py b/qa/rpc-tests/test_framework/coverage.py index 50f066a850..13b33869f5 100644 --- a/qa/rpc-tests/test_framework/coverage.py +++ b/qa/rpc-tests/test_framework/coverage.py @@ -1,3 +1,8 @@ +#!/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. @@ -45,7 +50,7 @@ class AuthServiceProxyWrapper(object): rpc_method = self.auth_service_proxy_instance._service_name if self.coverage_logfile: - with open(self.coverage_logfile, 'a+') as f: + with open(self.coverage_logfile, 'a+', encoding='utf8') as f: f.write("%s\n" % rpc_method) return return_val @@ -95,7 +100,7 @@ def write_all_rpc_commands(dirname, node): if line and not line.startswith('='): commands.add("%s\n" % line.split()[0]) - with open(filename, 'w') as f: + with open(filename, 'w', encoding='utf8') 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 index ba3038fe04..c63a15c1e0 100644 --- a/qa/rpc-tests/test_framework/key.py +++ b/qa/rpc-tests/test_framework/key.py @@ -75,6 +75,9 @@ ssl.EC_POINT_mul.argtypes = [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 +SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 + # Thx to Sam Devlin for the ctypes magic 64-bit fix. def _check_result(val, func, args): if val == 0: @@ -147,7 +150,7 @@ class CECKey(object): r = self.get_raw_ecdh_key(other_pubkey) return kdf(r) - def sign(self, hash): + def sign(self, hash, low_s = True): # FIXME: need unit tests for below cases if not isinstance(hash, bytes): raise TypeError('Hash must be bytes instance; got %r' % hash.__class__) @@ -159,7 +162,25 @@ class CECKey(object): 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] + assert mb_sig.raw[0] == 0x30 + assert mb_sig.raw[1] == sig_size0.value - 2 + total_size = mb_sig.raw[1] + assert mb_sig.raw[2] == 2 + r_size = mb_sig.raw[3] + assert mb_sig.raw[4 + r_size] == 2 + s_size = mb_sig.raw[5 + r_size] + s_value = int.from_bytes(mb_sig.raw[6+r_size:6+r_size+s_size], byteorder='big') + if (not low_s) or s_value <= SECP256K1_ORDER_HALF: + return mb_sig.raw[:sig_size0.value] + else: + low_s_value = SECP256K1_ORDER - s_value + low_s_bytes = (low_s_value).to_bytes(33, byteorder='big') + while len(low_s_bytes) > 1 and low_s_bytes[0] == 0 and low_s_bytes[1] < 0x80: + low_s_bytes = low_s_bytes[1:] + new_s_size = len(low_s_bytes) + new_total_size_byte = (total_size + new_s_size - s_size).to_bytes(1,byteorder='big') + new_s_size_byte = (new_s_size).to_bytes(1,byteorder='big') + return b'\x30' + new_total_size_byte + mb_sig.raw[2:5+r_size] + new_s_size_byte + low_s_bytes def verify(self, hash, sig): """Verify a DER signature""" diff --git a/qa/rpc-tests/test_framework/mininode.py b/qa/rpc-tests/test_framework/mininode.py index 64985d58e2..4d238c08d9 100755 --- a/qa/rpc-tests/test_framework/mininode.py +++ b/qa/rpc-tests/test_framework/mininode.py @@ -1,7 +1,12 @@ -# mininode.py - Bitcoin P2P network half-a-node -# -# Distributed under the MIT/X11 software license, see the accompanying +#!/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. @@ -20,25 +25,35 @@ import struct import socket import asyncore -import binascii import time import sys import random -import cStringIO +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 +from test_framework.siphash import siphash256 BIP0031_VERSION = 60000 -MY_VERSION = 60001 # past bip-31 for ping/pong -MY_SUBVERSION = "/python-mininode-tester:0.0.1/" +MY_VERSION = 70014 # 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 +# ourselves (to workaround an issue with closing an asyncore socket when # using select) mininode_socket_map = dict() @@ -54,12 +69,25 @@ mininode_lock = RLock() 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 ser_compact_size(l): + r = b"" + if l < 253: + r = struct.pack("B", l) + elif l < 0x10000: + r = struct.pack("<BH", 253, l) + elif l < 0x100000000: + r = struct.pack("<BI", 254, l) + else: + r = struct.pack("<BQ", 255, l) + return r -def deser_string(f): +def deser_compact_size(f): nit = struct.unpack("<B", f.read(1))[0] if nit == 253: nit = struct.unpack("<H", f.read(2))[0] @@ -67,176 +95,130 @@ def deser_string(f): nit = struct.unpack("<I", f.read(4))[0] elif nit == 255: nit = struct.unpack("<Q", f.read(8))[0] - return f.read(nit) + return nit +def deser_string(f): + nit = deser_compact_size(f) + return f.read(nit) def ser_string(s): - if len(s) < 253: - return chr(len(s)) + s - elif len(s) < 0x10000: - return chr(253) + struct.pack("<H", len(s)) + s - elif len(s) < 0x100000000L: - return chr(254) + struct.pack("<I", len(s)) + s - return chr(255) + struct.pack("<Q", len(s)) + s - + return ser_compact_size(len(s)) + s def deser_uint256(f): - r = 0L - for i in xrange(8): + 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 = "" - for i in xrange(8): - rs += struct.pack("<I", u & 0xFFFFFFFFL) + rs = b"" + for i in range(8): + rs += struct.pack("<I", u & 0xFFFFFFFF) u >>= 32 return rs def uint256_from_str(s): - r = 0L + r = 0 t = struct.unpack("<IIIIIIII", s[:32]) - for i in xrange(8): + for i in range(8): r += t[i] << (i * 32) return r def uint256_from_compact(c): nbytes = (c >> 24) & 0xFF - v = (c & 0xFFFFFFL) << (8 * (nbytes - 3)) + 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] + nit = deser_compact_size(f) r = [] - for i in xrange(nit): + for i in range(nit): t = c() t.deserialize(f) r.append(t) return r -def ser_vector(l): - r = "" - if len(l) < 253: - r = chr(len(l)) - elif len(l) < 0x10000: - r = chr(253) + struct.pack("<H", len(l)) - elif len(l) < 0x100000000L: - r = chr(254) + struct.pack("<I", len(l)) - else: - r = chr(255) + struct.pack("<Q", len(l)) +# 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 = ser_compact_size(len(l)) for i in l: - r += i.serialize() + 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] + nit = deser_compact_size(f) r = [] - for i in xrange(nit): + for i in range(nit): t = deser_uint256(f) r.append(t) return r def ser_uint256_vector(l): - r = "" - if len(l) < 253: - r = chr(len(l)) - elif len(l) < 0x10000: - r = chr(253) + struct.pack("<H", len(l)) - elif len(l) < 0x100000000L: - r = chr(254) + struct.pack("<I", len(l)) - else: - r = chr(255) + struct.pack("<Q", len(l)) + r = ser_compact_size(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] + nit = deser_compact_size(f) r = [] - for i in xrange(nit): + for i in range(nit): t = deser_string(f) r.append(t) return r def ser_string_vector(l): - r = "" - if len(l) < 253: - r = chr(len(l)) - elif len(l) < 0x10000: - r = chr(253) + struct.pack("<H", len(l)) - elif len(l) < 0x100000000L: - r = chr(254) + struct.pack("<I", len(l)) - else: - r = chr(255) + struct.pack("<Q", len(l)) + r = ser_compact_size(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] + nit = deser_compact_size(f) r = [] - for i in xrange(nit): + for i in range(nit): t = struct.unpack("<i", f.read(4))[0] r.append(t) return r def ser_int_vector(l): - r = "" - if len(l) < 253: - r = chr(len(l)) - elif len(l) < 0x10000: - r = chr(253) + struct.pack("<H", len(l)) - elif len(l) < 0x100000000L: - r = chr(254) + struct.pack("<I", len(l)) - else: - r = chr(255) + struct.pack("<Q", len(l)) + r = ser_compact_size(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 = "\x00" * 10 + "\xff" * 2 + self.pchReserved = b"\x00" * 10 + b"\xff" * 2 self.ip = "0.0.0.0" self.port = 0 @@ -247,7 +229,7 @@ class CAddress(object): self.port = struct.unpack(">H", f.read(2))[0] def serialize(self): - r = "" + r = b"" r += struct.pack("<Q", self.nServices) r += self.pchReserved r += socket.inet_aton(self.ip) @@ -258,14 +240,19 @@ class CAddress(object): 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"} + 2: "Block", + 1|MSG_WITNESS_FLAG: "WitnessTx", + 2|MSG_WITNESS_FLAG : "WitnessBlock", + 4: "CompactBlock" + } - def __init__(self, t=0, h=0L): + def __init__(self, t=0, h=0): self.type = t self.hash = h @@ -274,7 +261,7 @@ class CInv(object): self.hash = deser_uint256(f) def serialize(self): - r = "" + r = b"" r += struct.pack("<i", self.type) r += ser_uint256(self.hash) return r @@ -294,7 +281,7 @@ class CBlockLocator(object): self.vHave = deser_uint256_vector(f) def serialize(self): - r = "" + r = b"" r += struct.pack("<i", self.nVersion) r += ser_uint256_vector(self.vHave) return r @@ -314,7 +301,7 @@ class COutPoint(object): self.n = struct.unpack("<I", f.read(4))[0] def serialize(self): - r = "" + r = b"" r += ser_uint256(self.hash) r += struct.pack("<I", self.n) return r @@ -324,7 +311,7 @@ class COutPoint(object): class CTxIn(object): - def __init__(self, outpoint=None, scriptSig="", nSequence=0): + def __init__(self, outpoint=None, scriptSig=b"", nSequence=0): if outpoint is None: self.prevout = COutPoint() else: @@ -339,7 +326,7 @@ class CTxIn(object): self.nSequence = struct.unpack("<I", f.read(4))[0] def serialize(self): - r = "" + r = b"" r += self.prevout.serialize() r += ser_string(self.scriptSig) r += struct.pack("<I", self.nSequence) @@ -347,12 +334,12 @@ class CTxIn(object): def __repr__(self): return "CTxIn(prevout=%s scriptSig=%s nSequence=%i)" \ - % (repr(self.prevout), binascii.hexlify(self.scriptSig), + % (repr(self.prevout), bytes_to_hex_str(self.scriptSig), self.nSequence) class CTxOut(object): - def __init__(self, nValue=0, scriptPubKey=""): + def __init__(self, nValue=0, scriptPubKey=b""): self.nValue = nValue self.scriptPubKey = scriptPubKey @@ -361,15 +348,75 @@ class CTxOut(object): self.scriptPubKey = deser_string(f) def serialize(self): - r = "" + 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 // 100000000, self.nValue % 100000000, - binascii.hexlify(self.scriptPubKey)) + % (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): @@ -378,6 +425,7 @@ class CTransaction(object): self.nVersion = 1 self.vin = [] self.vout = [] + self.wit = CTxWitness() self.nLockTime = 0 self.sha256 = None self.hash = None @@ -386,44 +434,92 @@ class CTransaction(object): self.vin = copy.deepcopy(tx.vin) self.vout = copy.deepcopy(tx.vout) self.nLockTime = tx.nLockTime - self.sha256 = None - self.hash = None + 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) - self.vout = deser_vector(f, CTxOut) + 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() for i in range(len(self.vin))] + self.wit.deserialize(f) self.nLockTime = struct.unpack("<I", f.read(4))[0] self.sha256 = None self.hash = None - def serialize(self): - r = "" + 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() - def calc_sha256(self): + # 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())) - self.hash = hash256(self.serialize())[::-1].encode('hex_codec') + 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 > 21000000L * 100000000L: + 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) + return "CTransaction(nVersion=%i vin=%s vout=%s wit=%s nLockTime=%i)" \ + % (self.nVersion, repr(self.vin), repr(self.vout), repr(self.wit), self.nLockTime) class CBlockHeader(object): @@ -462,7 +558,7 @@ class CBlockHeader(object): self.hash = None def serialize(self): - r = "" + r = b"" r += struct.pack("<i", self.nVersion) r += ser_uint256(self.hashPrevBlock) r += ser_uint256(self.hashMerkleRoot) @@ -473,7 +569,7 @@ class CBlockHeader(object): def calc_sha256(self): if self.sha256 is None: - r = "" + r = b"" r += struct.pack("<i", self.nVersion) r += ser_uint256(self.hashPrevBlock) r += ser_uint256(self.hashMerkleRoot) @@ -481,7 +577,7 @@ class CBlockHeader(object): r += struct.pack("<I", self.nBits) r += struct.pack("<I", self.nNonce) self.sha256 = uint256_from_str(hash256(r)) - self.hash = hash256(r)[::-1].encode('hex_codec') + self.hash = encode(hash256(r)[::-1], 'hex_codec').decode('ascii') def rehash(self): self.sha256 = None @@ -503,25 +599,43 @@ class CBlock(CBlockHeader): super(CBlock, self).deserialize(f) self.vtx = deser_vector(f, CTransaction) - def serialize(self): - r = "" + def serialize(self, with_witness=False): + r = b"" r += super(CBlock, self).serialize() - r += ser_vector(self.vtx) + if with_witness: + r += ser_vector(self.vtx, "serialize_with_witness") + else: + r += ser_vector(self.vtx) return r - def calc_merkle_root(self): - hashes = [] - for tx in self.vtx: - tx.calc_sha256() - hashes.append(ser_uint256(tx.sha256)) + # Calculate the merkle root given a vector of transaction hashes + def get_merkle_root(self, hashes): while len(hashes) > 1: newhashes = [] - for i in xrange(0, len(hashes), 2): + 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) @@ -535,7 +649,7 @@ class CBlock(CBlockHeader): return True def solve(self): - self.calc_sha256() + self.rehash() target = uint256_from_compact(self.nBits) while self.sha256 > target: self.nNonce += 1 @@ -559,9 +673,9 @@ class CUnsignedAlert(object): self.nMaxVer = 0 self.setSubVer = [] self.nPriority = 0 - self.strComment = "" - self.strStatusBar = "" - self.strReserved = "" + self.strComment = b"" + self.strStatusBar = b"" + self.strReserved = b"" def deserialize(self, f): self.nVersion = struct.unpack("<i", f.read(4))[0] @@ -579,7 +693,7 @@ class CUnsignedAlert(object): self.strReserved = deser_string(f) def serialize(self): - r = "" + r = b"" r += struct.pack("<i", self.nVersion) r += struct.pack("<q", self.nRelayUntil) r += struct.pack("<q", self.nExpiration) @@ -604,15 +718,15 @@ class CUnsignedAlert(object): class CAlert(object): def __init__(self): - self.vchMsg = "" - self.vchSig = "" + self.vchMsg = b"" + self.vchSig = b"" def deserialize(self, f): self.vchMsg = deser_string(f) self.vchSig = deser_string(f) def serialize(self): - r = "" + r = b"" r += ser_string(self.vchMsg) r += ser_string(self.vchSig) return r @@ -622,14 +736,216 @@ class CAlert(object): % (len(self.vchMsg), len(self.vchSig)) +class PrefilledTransaction(object): + def __init__(self, index=0, tx = None): + self.index = index + self.tx = tx + + def deserialize(self, f): + self.index = deser_compact_size(f) + self.tx = CTransaction() + self.tx.deserialize(f) + + def serialize(self, with_witness=False): + r = b"" + r += ser_compact_size(self.index) + if with_witness: + r += self.tx.serialize_with_witness() + else: + r += self.tx.serialize_without_witness() + return r + + def serialize_with_witness(self): + return self.serialize(with_witness=True) + + def __repr__(self): + return "PrefilledTransaction(index=%d, tx=%s)" % (self.index, repr(self.tx)) + +# This is what we send on the wire, in a cmpctblock message. +class P2PHeaderAndShortIDs(object): + def __init__(self): + self.header = CBlockHeader() + self.nonce = 0 + self.shortids_length = 0 + self.shortids = [] + self.prefilled_txn_length = 0 + self.prefilled_txn = [] + + def deserialize(self, f): + self.header.deserialize(f) + self.nonce = struct.unpack("<Q", f.read(8))[0] + self.shortids_length = deser_compact_size(f) + for i in range(self.shortids_length): + # shortids are defined to be 6 bytes in the spec, so append + # two zero bytes and read it in as an 8-byte number + self.shortids.append(struct.unpack("<Q", f.read(6) + b'\x00\x00')[0]) + self.prefilled_txn = deser_vector(f, PrefilledTransaction) + self.prefilled_txn_length = len(self.prefilled_txn) + + # When using version 2 compact blocks, we must serialize with_witness. + def serialize(self, with_witness=False): + r = b"" + r += self.header.serialize() + r += struct.pack("<Q", self.nonce) + r += ser_compact_size(self.shortids_length) + for x in self.shortids: + # We only want the first 6 bytes + r += struct.pack("<Q", x)[0:6] + if with_witness: + r += ser_vector(self.prefilled_txn, "serialize_with_witness") + else: + r += ser_vector(self.prefilled_txn) + return r + + def __repr__(self): + return "P2PHeaderAndShortIDs(header=%s, nonce=%d, shortids_length=%d, shortids=%s, prefilled_txn_length=%d, prefilledtxn=%s" % (repr(self.header), self.nonce, self.shortids_length, repr(self.shortids), self.prefilled_txn_length, repr(self.prefilled_txn)) + +# P2P version of the above that will use witness serialization (for compact +# block version 2) +class P2PHeaderAndShortWitnessIDs(P2PHeaderAndShortIDs): + def serialize(self): + return super(P2PHeaderAndShortWitnessIDs, self).serialize(with_witness=True) + +# Calculate the BIP 152-compact blocks shortid for a given transaction hash +def calculate_shortid(k0, k1, tx_hash): + expected_shortid = siphash256(k0, k1, tx_hash) + expected_shortid &= 0x0000ffffffffffff + return expected_shortid + +# This version gets rid of the array lengths, and reinterprets the differential +# encoding into indices that can be used for lookup. +class HeaderAndShortIDs(object): + def __init__(self, p2pheaders_and_shortids = None): + self.header = CBlockHeader() + self.nonce = 0 + self.shortids = [] + self.prefilled_txn = [] + self.use_witness = False + + if p2pheaders_and_shortids != None: + self.header = p2pheaders_and_shortids.header + self.nonce = p2pheaders_and_shortids.nonce + self.shortids = p2pheaders_and_shortids.shortids + last_index = -1 + for x in p2pheaders_and_shortids.prefilled_txn: + self.prefilled_txn.append(PrefilledTransaction(x.index + last_index + 1, x.tx)) + last_index = self.prefilled_txn[-1].index + + def to_p2p(self): + if self.use_witness: + ret = P2PHeaderAndShortWitnessIDs() + else: + ret = P2PHeaderAndShortIDs() + ret.header = self.header + ret.nonce = self.nonce + ret.shortids_length = len(self.shortids) + ret.shortids = self.shortids + ret.prefilled_txn_length = len(self.prefilled_txn) + ret.prefilled_txn = [] + last_index = -1 + for x in self.prefilled_txn: + ret.prefilled_txn.append(PrefilledTransaction(x.index - last_index - 1, x.tx)) + last_index = x.index + return ret + + def get_siphash_keys(self): + header_nonce = self.header.serialize() + header_nonce += struct.pack("<Q", self.nonce) + hash_header_nonce_as_str = sha256(header_nonce) + key0 = struct.unpack("<Q", hash_header_nonce_as_str[0:8])[0] + key1 = struct.unpack("<Q", hash_header_nonce_as_str[8:16])[0] + return [ key0, key1 ] + + # Version 2 compact blocks use wtxid in shortids (rather than txid) + def initialize_from_block(self, block, nonce=0, prefill_list = [0], use_witness = False): + self.header = CBlockHeader(block) + self.nonce = nonce + self.prefilled_txn = [ PrefilledTransaction(i, block.vtx[i]) for i in prefill_list ] + self.shortids = [] + self.use_witness = use_witness + [k0, k1] = self.get_siphash_keys() + for i in range(len(block.vtx)): + if i not in prefill_list: + tx_hash = block.vtx[i].sha256 + if use_witness: + tx_hash = block.vtx[i].calc_sha256(with_witness=True) + self.shortids.append(calculate_shortid(k0, k1, tx_hash)) + + def __repr__(self): + return "HeaderAndShortIDs(header=%s, nonce=%d, shortids=%s, prefilledtxn=%s" % (repr(self.header), self.nonce, repr(self.shortids), repr(self.prefilled_txn)) + + +class BlockTransactionsRequest(object): + + def __init__(self, blockhash=0, indexes = None): + self.blockhash = blockhash + self.indexes = indexes if indexes != None else [] + + def deserialize(self, f): + self.blockhash = deser_uint256(f) + indexes_length = deser_compact_size(f) + for i in range(indexes_length): + self.indexes.append(deser_compact_size(f)) + + def serialize(self): + r = b"" + r += ser_uint256(self.blockhash) + r += ser_compact_size(len(self.indexes)) + for x in self.indexes: + r += ser_compact_size(x) + return r + + # helper to set the differentially encoded indexes from absolute ones + def from_absolute(self, absolute_indexes): + self.indexes = [] + last_index = -1 + for x in absolute_indexes: + self.indexes.append(x-last_index-1) + last_index = x + + def to_absolute(self): + absolute_indexes = [] + last_index = -1 + for x in self.indexes: + absolute_indexes.append(x+last_index+1) + last_index = absolute_indexes[-1] + return absolute_indexes + + def __repr__(self): + return "BlockTransactionsRequest(hash=%064x indexes=%s)" % (self.blockhash, repr(self.indexes)) + + +class BlockTransactions(object): + + def __init__(self, blockhash=0, transactions = None): + self.blockhash = blockhash + self.transactions = transactions if transactions != None else [] + + def deserialize(self, f): + self.blockhash = deser_uint256(f) + self.transactions = deser_vector(f, CTransaction) + + def serialize(self, with_witness=False): + r = b"" + r += ser_uint256(self.blockhash) + if with_witness: + r += ser_vector(self.transactions, "serialize_with_witness") + else: + r += ser_vector(self.transactions) + return r + + def __repr__(self): + return "BlockTransactions(hash=%064x transactions=%s)" % (self.blockhash, repr(self.transactions)) + + # Objects that correspond to messages on the wire class msg_version(object): - command = "version" + command = b"version" def __init__(self): self.nVersion = MY_VERSION self.nServices = 1 - self.nTime = time.time() + self.nTime = int(time.time()) self.addrTo = CAddress() self.addrFrom = CAddress() self.nNonce = random.getrandbits(64) @@ -660,7 +976,7 @@ class msg_version(object): self.nStartingHeight = None def serialize(self): - r = "" + r = b"" r += struct.pack("<i", self.nVersion) r += struct.pack("<Q", self.nServices) r += struct.pack("<q", self.nTime) @@ -679,7 +995,7 @@ class msg_version(object): class msg_verack(object): - command = "verack" + command = b"verack" def __init__(self): pass @@ -688,14 +1004,14 @@ class msg_verack(object): pass def serialize(self): - return "" + return b"" def __repr__(self): return "msg_verack()" class msg_addr(object): - command = "addr" + command = b"addr" def __init__(self): self.addrs = [] @@ -711,7 +1027,7 @@ class msg_addr(object): class msg_alert(object): - command = "alert" + command = b"alert" def __init__(self): self.alert = CAlert() @@ -721,7 +1037,7 @@ class msg_alert(object): self.alert.deserialize(f) def serialize(self): - r = "" + r = b"" r += self.alert.serialize() return r @@ -730,7 +1046,7 @@ class msg_alert(object): class msg_inv(object): - command = "inv" + command = b"inv" def __init__(self, inv=None): if inv is None: @@ -749,7 +1065,7 @@ class msg_inv(object): class msg_getdata(object): - command = "getdata" + command = b"getdata" def __init__(self, inv=None): self.inv = inv if inv != None else [] @@ -765,11 +1081,11 @@ class msg_getdata(object): class msg_getblocks(object): - command = "getblocks" + command = b"getblocks" def __init__(self): self.locator = CBlockLocator() - self.hashstop = 0L + self.hashstop = 0 def deserialize(self, f): self.locator = CBlockLocator() @@ -777,7 +1093,7 @@ class msg_getblocks(object): self.hashstop = deser_uint256(f) def serialize(self): - r = "" + r = b"" r += self.locator.serialize() r += ser_uint256(self.hashstop) return r @@ -788,7 +1104,7 @@ class msg_getblocks(object): class msg_tx(object): - command = "tx" + command = b"tx" def __init__(self, tx=CTransaction()): self.tx = tx @@ -797,14 +1113,19 @@ class msg_tx(object): self.tx.deserialize(f) def serialize(self): - return self.tx.serialize() + 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 = "block" + command = b"block" def __init__(self, block=None): if block is None: @@ -821,9 +1142,27 @@ class msg_block(object): 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 = "getaddr" + command = b"getaddr" def __init__(self): pass @@ -832,14 +1171,14 @@ class msg_getaddr(object): pass def serialize(self): - return "" + return b"" def __repr__(self): return "msg_getaddr()" class msg_ping_prebip31(object): - command = "ping" + command = b"ping" def __init__(self): pass @@ -848,23 +1187,23 @@ class msg_ping_prebip31(object): pass def serialize(self): - return "" + return b"" def __repr__(self): return "msg_ping() (pre-bip31)" class msg_ping(object): - command = "ping" + command = b"ping" - def __init__(self, nonce=0L): + 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 = "" + r = b"" r += struct.pack("<Q", self.nonce) return r @@ -873,16 +1212,16 @@ class msg_ping(object): class msg_pong(object): - command = "pong" + command = b"pong" - def __init__(self, nonce=0L): + 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 = "" + r = b"" r += struct.pack("<Q", self.nonce) return r @@ -891,7 +1230,7 @@ class msg_pong(object): class msg_mempool(object): - command = "mempool" + command = b"mempool" def __init__(self): pass @@ -900,13 +1239,13 @@ class msg_mempool(object): pass def serialize(self): - return "" + return b"" def __repr__(self): return "msg_mempool()" class msg_sendheaders(object): - command = "sendheaders" + command = b"sendheaders" def __init__(self): pass @@ -915,21 +1254,22 @@ class msg_sendheaders(object): pass def serialize(self): - return "" + 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 = "getheaders" + command = b"getheaders" def __init__(self): self.locator = CBlockLocator() - self.hashstop = 0L + self.hashstop = 0 def deserialize(self, f): self.locator = CBlockLocator() @@ -937,7 +1277,7 @@ class msg_getheaders(object): self.hashstop = deser_uint256(f) def serialize(self): - r = "" + r = b"" r += self.locator.serialize() r += ser_uint256(self.hashstop) return r @@ -950,7 +1290,7 @@ class msg_getheaders(object): # headers message has # <count> <vector of block headers> class msg_headers(object): - command = "headers" + command = b"headers" def __init__(self): self.headers = [] @@ -970,26 +1310,29 @@ class msg_headers(object): class msg_reject(object): - command = "reject" + command = b"reject" + REJECT_MALFORMED = 1 def __init__(self): - self.message = "" - self.code = "" - self.reason = "" - self.data = 0L + 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.message == "block" or self.message == "tx"): + 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.message == "block" or self.message == "tx"): + if (self.code != self.REJECT_MALFORMED and + (self.message == b"block" or self.message == b"tx")): r += ser_uint256(self.data) return r @@ -997,12 +1340,136 @@ class msg_reject(object): 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 + +class msg_sendcmpct(object): + command = b"sendcmpct" + + def __init__(self): + self.announce = False + self.version = 1 + + def deserialize(self, f): + self.announce = struct.unpack("<?", f.read(1))[0] + self.version = struct.unpack("<Q", f.read(8))[0] + + def serialize(self): + r = b"" + r += struct.pack("<?", self.announce) + r += struct.pack("<Q", self.version) + return r + + def __repr__(self): + return "msg_sendcmpct(announce=%s, version=%lu)" % (self.announce, self.version) + +class msg_cmpctblock(object): + command = b"cmpctblock" + + def __init__(self, header_and_shortids = None): + self.header_and_shortids = header_and_shortids + + def deserialize(self, f): + self.header_and_shortids = P2PHeaderAndShortIDs() + self.header_and_shortids.deserialize(f) + + def serialize(self): + r = b"" + r += self.header_and_shortids.serialize() + return r + + def __repr__(self): + return "msg_cmpctblock(HeaderAndShortIDs=%s)" % repr(self.header_and_shortids) + +class msg_getblocktxn(object): + command = b"getblocktxn" + + def __init__(self): + self.block_txn_request = None + + def deserialize(self, f): + self.block_txn_request = BlockTransactionsRequest() + self.block_txn_request.deserialize(f) + + def serialize(self): + r = b"" + r += self.block_txn_request.serialize() + return r + + def __repr__(self): + return "msg_getblocktxn(block_txn_request=%s)" % (repr(self.block_txn_request)) + +class msg_blocktxn(object): + command = b"blocktxn" + + def __init__(self): + self.block_transactions = BlockTransactions() + + def deserialize(self, f): + self.block_transactions.deserialize(f) + + def serialize(self): + r = b"" + r += self.block_transactions.serialize() + return r + + def __repr__(self): + return "msg_blocktxn(block_transactions=%s)" % (repr(self.block_transactions)) + +class msg_witness_blocktxn(msg_blocktxn): + def serialize(self): + r = b"" + r += self.block_transactions.serialize(with_witness=True) + return r # 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. @@ -1015,35 +1482,16 @@ class NodeConnCB(object): return time.sleep(0.05) - # Derived classes should call this function once to set the message map - # which associates the derived classes' functions to incoming messages - def create_callback_map(self): - self.cbmap = { - "version": self.on_version, - "verack": self.on_verack, - "addr": self.on_addr, - "alert": self.on_alert, - "inv": self.on_inv, - "getdata": self.on_getdata, - "getblocks": self.on_getblocks, - "tx": self.on_tx, - "block": self.on_block, - "getaddr": self.on_getaddr, - "ping": self.on_ping, - "pong": self.on_pong, - "headers": self.on_headers, - "getheaders": self.on_getheaders, - "reject": self.on_reject, - "mempool": self.on_mempool - } - 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: - self.cbmap[message.command](conn, message) + getattr(self, 'on_' + message.command.decode('ascii'))(conn, message) except: - print "ERROR delivering %s (%s)" % (repr(message), - sys.exc_info()[0]) + print("ERROR delivering %s (%s)" % (repr(message), + sys.exc_info()[0])) def on_version(self, conn, message): if message.nVersion >= 209: @@ -1051,6 +1499,7 @@ class NodeConnCB(object): 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 @@ -1080,43 +1529,85 @@ class NodeConnCB(object): 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 + def on_sendcmpct(self, conn, message): pass + def on_cmpctblock(self, conn, message): pass + def on_getblocktxn(self, conn, message): pass + def on_blocktxn(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 send_and_ping(self, message): + self.send_message(message) + self.sync_with_ping() + + 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=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 = { - "version": msg_version, - "verack": msg_verack, - "addr": msg_addr, - "alert": msg_alert, - "inv": msg_inv, - "getdata": msg_getdata, - "getblocks": msg_getblocks, - "tx": msg_tx, - "block": msg_block, - "getaddr": msg_getaddr, - "ping": msg_ping, - "pong": msg_pong, - "headers": msg_headers, - "getheaders": msg_getheaders, - "reject": msg_reject, - "mempool": msg_mempool + 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, + b"sendcmpct": msg_sendcmpct, + b"cmpctblock": msg_cmpctblock, + b"getblocktxn": msg_getblocktxn, + b"blocktxn": msg_blocktxn } MAGIC_BYTES = { - "mainnet": "\xf9\xbe\xb4\xd9", # mainnet - "testnet3": "\x0b\x11\x09\x07", # testnet3 - "regtest": "\xfa\xbf\xb5\xda" # regtest + "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=1): + 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 = "" - self.recvbuf = "" + self.sendbuf = b"" + self.recvbuf = b"" self.ver_send = 209 self.ver_recv = 209 self.last_sent = 0 @@ -1124,6 +1615,7 @@ class NodeConn(asyncore.dispatcher): self.network = net self.cb = callback self.disconnect = False + self.nServices = 0 # stuff version msg into sendbuf vt = msg_version() @@ -1133,8 +1625,8 @@ class NodeConn(asyncore.dispatcher): 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) + print('MiniNode: Connecting to Bitcoin Node IP # ' + dstaddr + ':' \ + + str(dstport)) try: self.connect((dstaddr, dstport)) @@ -1153,8 +1645,8 @@ class NodeConn(asyncore.dispatcher): self.show_debug_msg("MiniNode: Closing Connection to %s:%d... " % (self.dstaddr, self.dstport)) self.state = "closed" - self.recvbuf = "" - self.sendbuf = "" + self.recvbuf = b"" + self.sendbuf = b"" try: self.close() except: @@ -1188,53 +1680,58 @@ class NodeConn(asyncore.dispatcher): self.sendbuf = self.sendbuf[sent:] def got_data(self): - 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("\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("\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: + try: + while True: + if len(self.recvbuf) < 4: 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 = cStringIO.StringIO(msg) - t = self.messagemap[command]() - t.deserialize(f) - self.got_message(t) - else: - self.show_debug_msg("Unknown command: '" + command + "' " + - repr(msg)) + 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: - return + 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 += "\x00" * (12 - len(command)) + tmsg += b"\x00" * (12 - len(command)) tmsg += struct.pack("<I", len(data)) if self.ver_send >= 209: th = sha256(data) @@ -1246,11 +1743,11 @@ class NodeConn(asyncore.dispatcher): self.last_sent = time.time() def got_message(self, message): - if message.command == "version": + if message.command == b"version": if message.nVersion <= BIP0031_VERSION: - self.messagemap['ping'] = msg_ping_prebip31 + self.messagemap[b'ping'] = msg_ping_prebip31 if self.last_sent + 30 * 60 < time.time(): - self.send_message(self.messagemap['ping']()) + self.send_message(self.messagemap[b'ping']()) self.show_debug_msg("Recv %s" % repr(message)) self.cb.deliver(self, message) diff --git a/qa/rpc-tests/test_framework/netutil.py b/qa/rpc-tests/test_framework/netutil.py index b30a88a4f7..b92a9f6e1c 100644 --- a/qa/rpc-tests/test_framework/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 @@ -57,7 +58,7 @@ def netstat(typ='tcp'): To get pid of all network process running on system, you must run this script as superuser ''' - with open('/proc/net/'+typ,'r') as f: + with open('/proc/net/'+typ,'r',encoding='utf8') as f: content = f.readlines() content.pop(0) result = [] @@ -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 index 0a78cf6fb1..83bbf20479 100644 --- a/qa/rpc-tests/test_framework/script.py +++ b/qa/rpc-tests/test_framework/script.py @@ -1,20 +1,23 @@ +#!/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. # -# Distributed under the MIT/X11 software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# """Scripts Functionality to build scripts, as well as SignatureHash(). """ -from __future__ import absolute_import, division, print_function, unicode_literals -from test_framework.mininode import CTransaction, CTxOut, hash256 +from .mininode import CTransaction, CTxOut, sha256, hash256, uint256_from_str, ser_uint256, ser_string +from binascii import hexlify +import hashlib import sys bchr = chr @@ -24,10 +27,9 @@ if sys.version > '3': bchr = lambda x: bytes([x]) bord = lambda x: x -import copy import struct -from test_framework.bignum import bn2vch +from .bignum import bn2vch MAX_SCRIPT_SIZE = 10000 MAX_SCRIPT_ELEMENT_SIZE = 520 @@ -35,6 +37,10 @@ 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""" @@ -226,8 +232,8 @@ OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf) # expansion OP_NOP1 = CScriptOp(0xb0) -OP_NOP2 = CScriptOp(0xb1) -OP_NOP3 = CScriptOp(0xb2) +OP_CHECKLOCKTIMEVERIFY = CScriptOp(0xb1) +OP_CHECKSEQUENCEVERIFY = CScriptOp(0xb2) OP_NOP4 = CScriptOp(0xb3) OP_NOP5 = CScriptOp(0xb4) OP_NOP6 = CScriptOp(0xb5) @@ -353,8 +359,8 @@ VALID_OPCODES = { OP_CHECKMULTISIGVERIFY, OP_NOP1, - OP_NOP2, - OP_NOP3, + OP_CHECKLOCKTIMEVERIFY, + OP_CHECKSEQUENCEVERIFY, OP_NOP4, OP_NOP5, OP_NOP6, @@ -472,8 +478,8 @@ OPCODE_NAMES.update({ OP_CHECKMULTISIG : 'OP_CHECKMULTISIG', OP_CHECKMULTISIGVERIFY : 'OP_CHECKMULTISIGVERIFY', OP_NOP1 : 'OP_NOP1', - OP_NOP2 : 'OP_NOP2', - OP_NOP3 : 'OP_NOP3', + OP_CHECKLOCKTIMEVERIFY : 'OP_CHECKLOCKTIMEVERIFY', + OP_CHECKSEQUENCEVERIFY : 'OP_CHECKSEQUENCEVERIFY', OP_NOP4 : 'OP_NOP4', OP_NOP5 : 'OP_NOP5', OP_NOP6 : 'OP_NOP6', @@ -591,8 +597,8 @@ OPCODES_BY_NAME = { 'OP_CHECKMULTISIG' : OP_CHECKMULTISIG, 'OP_CHECKMULTISIGVERIFY' : OP_CHECKMULTISIGVERIFY, 'OP_NOP1' : OP_NOP1, - 'OP_NOP2' : OP_NOP2, - 'OP_NOP3' : OP_NOP3, + 'OP_CHECKLOCKTIMEVERIFY' : OP_CHECKLOCKTIMEVERIFY, + 'OP_CHECKSEQUENCEVERIFY' : OP_CHECKSEQUENCEVERIFY, 'OP_NOP4' : OP_NOP4, 'OP_NOP5' : OP_NOP5, 'OP_NOP6' : OP_NOP6, @@ -629,7 +635,7 @@ class CScriptNum(object): neg = obj.value < 0 absvalue = -obj.value if neg else obj.value while (absvalue): - r.append(chr(absvalue & 0xff)) + r.append(absvalue & 0xff) absvalue >>= 8 if r[-1] & 0x80: r.append(0x80 if neg else 0) @@ -658,7 +664,7 @@ class CScript(bytes): other = bchr(CScriptOp(OP_0)) else: other = CScriptNum.encode(other) - elif isinstance(other, (int, long)): + elif isinstance(other, int): if 0 <= other <= 16: other = bytes(bchr(CScriptOp.encode_op_n(other))) elif other == -1: @@ -777,7 +783,7 @@ class CScript(bytes): # need to change def _repr(o): if isinstance(o, bytes): - return "x('%s')" % binascii.hexlify(o).decode('utf8') + return b"x('%s')" % hexlify(o).decode('ascii') else: return repr(o) @@ -876,7 +882,7 @@ def SignatureHash(script, txTo, inIdx, hashtype): tmp = txtmp.vout[outIdx] txtmp.vout = [] for i in range(outIdx): - txtmp.vout.append(CTxOut()) + txtmp.vout.append(CTxOut(-1)) txtmp.vout.append(tmp) for i in range(len(txtmp.vin)): @@ -894,3 +900,48 @@ def SignatureHash(script, txTo, inIdx, 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/siphash.py b/qa/rpc-tests/test_framework/siphash.py new file mode 100644 index 0000000000..9c0574bd93 --- /dev/null +++ b/qa/rpc-tests/test_framework/siphash.py @@ -0,0 +1,64 @@ +#!/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. + +# +# siphash.py - Specialized SipHash-2-4 implementations +# +# This implements SipHash-2-4 for 256-bit integers. + +def rotl64(n, b): + return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b + +def siphash_round(v0, v1, v2, v3): + v0 = (v0 + v1) & ((1 << 64) - 1) + v1 = rotl64(v1, 13) + v1 ^= v0 + v0 = rotl64(v0, 32) + v2 = (v2 + v3) & ((1 << 64) - 1) + v3 = rotl64(v3, 16) + v3 ^= v2 + v0 = (v0 + v3) & ((1 << 64) - 1) + v3 = rotl64(v3, 21) + v3 ^= v0 + v2 = (v2 + v1) & ((1 << 64) - 1) + v1 = rotl64(v1, 17) + v1 ^= v2 + v2 = rotl64(v2, 32) + return (v0, v1, v2, v3) + +def siphash256(k0, k1, h): + n0 = h & ((1 << 64) - 1) + n1 = (h >> 64) & ((1 << 64) - 1) + n2 = (h >> 128) & ((1 << 64) - 1) + n3 = (h >> 192) & ((1 << 64) - 1) + v0 = 0x736f6d6570736575 ^ k0 + v1 = 0x646f72616e646f6d ^ k1 + v2 = 0x6c7967656e657261 ^ k0 + v3 = 0x7465646279746573 ^ k1 ^ n0 + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0 ^= n0 + v3 ^= n1 + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0 ^= n1 + v3 ^= n2 + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0 ^= n2 + v3 ^= n3 + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0 ^= n3 + v3 ^= 0x2000000000000000 + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0 ^= 0x2000000000000000 + v2 ^= 0xFF + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) + return v0 ^ v1 ^ v2 ^ v3 diff --git a/qa/rpc-tests/test_framework/socks5.py b/qa/rpc-tests/test_framework/socks5.py index 1dbfb98d5d..372f5ed605 100644 --- a/qa/rpc-tests/test_framework/socks5.py +++ b/qa/rpc-tests/test_framework/socks5.py @@ -1,11 +1,12 @@ -# Copyright (c) 2015 The Bitcoin Core developers +#!/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. ''' -from __future__ import print_function, division, unicode_literals -import socket, threading, Queue + +import socket, threading, queue import traceback, sys ### Protocol constants @@ -102,7 +103,7 @@ class Socks5Connection(object): addr = recvall(self.conn, 4) elif atyp == AddressType.DOMAINNAME: n = recvall(self.conn, 1)[0] - addr = str(recvall(self.conn, n)) + addr = recvall(self.conn, n) elif atyp == AddressType.IPV6: addr = recvall(self.conn, 16) else: @@ -117,7 +118,7 @@ class Socks5Connection(object): self.serv.queue.put(cmdin) print('Proxy: ', cmdin) # Fall through to disconnect - except Exception,e: + except Exception as e: traceback.print_exc(file=sys.stderr) self.serv.queue.put(e) finally: @@ -132,7 +133,7 @@ class Socks5Server(object): self.s.listen(5) self.running = False self.thread = None - self.queue = Queue.Queue() # report connections and exceptions to client + self.queue = queue.Queue() # report connections and exceptions to client def run(self): while self.running: diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index ae2d91ab60..e6fc5fd8a2 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -1,51 +1,59 @@ -#!/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. # Base class for RPC testing -# Add python-bitcoinrpc to module search path: +import logging +import optparse import os import sys - import shutil import tempfile import traceback from .util import ( initialize_chain, - assert_equal, start_nodes, connect_nodes_bi, sync_blocks, sync_mempools, stop_nodes, - wait_bitcoinds, + stop_node, enable_coverage, check_json_precision, initialize_chain_clean, + PortSeed, ) -from authproxy import AuthServiceProxy, JSONRPCException +from .authproxy import JSONRPCException class BitcoinTestFramework(object): - # These may be over-ridden by subclasses: + def __init__(self): + self.num_nodes = 4 + self.setup_clean_chain = False + self.nodes = None + def run_test(self): - for node in self.nodes: - assert_equal(node.getblockcount(), 200) - assert_equal(node.getbalance(), 25*50) + raise NotImplementedError def add_options(self, parser): pass def setup_chain(self): print("Initializing test directory "+self.options.tmpdir) - initialize_chain(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, self.options.cachedir) + + def stop_node(self, num_node): + stop_node(self.nodes[num_node], num_node) def setup_nodes(self): - return start_nodes(4, self.options.tmpdir) + return start_nodes(self.num_nodes, self.options.tmpdir) def setup_network(self, split = False): self.nodes = self.setup_nodes() @@ -72,7 +80,6 @@ class BitcoinTestFramework(object): """ assert not self.is_network_split stop_nodes(self.nodes) - wait_bitcoinds() self.setup_network(True) def sync_all(self): @@ -91,36 +98,42 @@ class BitcoinTestFramework(object): """ 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("--noshutdown", dest="noshutdown", default=False, action="store_true", help="Don't stop bitcoinds after the test execution") - parser.add_option("--srcdir", dest="srcdir", default="../../src", + 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("--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__))+"/../../cache"), + help="Directory for caching pregenerated datadirs") 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() + # backup dir variable for removal at cleanup + self.options.root, self.options.tmpdir = self.options.tmpdir, self.options.tmpdir + '/' + str(self.options.port_seed) + if self.options.trace_rpc: - import logging - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG, stream=sys.stdout) if self.options.coveragedir: enable_coverage(self.options.coveragedir) - os.environ['PATH'] = self.options.srcdir+":"+os.environ['PATH'] + PortSeed.n = self.options.port_seed + + os.environ['PATH'] = self.options.srcdir+":"+self.options.srcdir+"/qt:"+os.environ['PATH'] check_json_precision() @@ -140,22 +153,30 @@ class BitcoinTestFramework(object): print("JSONRPC error: "+e.error['message']) traceback.print_tb(sys.exc_info()[2]) except AssertionError as e: - print("Assertion failed: "+e.message) + 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: "+str(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: + if not self.options.nocleanup and not self.options.noshutdown and success: print("Cleaning up") shutil.rmtree(self.options.tmpdir) + if not os.listdir(self.options.root): + os.rmdir(self.options.root) + else: + print("Not cleaning up dir %s" % self.options.tmpdir) if success: print("Tests successful") @@ -173,9 +194,10 @@ class BitcoinTestFramework(object): class ComparisonTestFramework(BitcoinTestFramework): - # Can override the num_nodes variable to indicate how many nodes to run. 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", @@ -185,10 +207,6 @@ class ComparisonTestFramework(BitcoinTestFramework): default=os.getenv("BITCOIND", "bitcoind"), help="bitcoind binary to use for reference nodes (if any)") - def setup_chain(self): - print "Initializing test directory "+self.options.tmpdir - initialize_chain_clean(self.options.tmpdir, self.num_nodes) - def setup_network(self): self.nodes = start_nodes( self.num_nodes, self.options.tmpdir, diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index b7e90a8a8b..c6b0367b41 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -1,27 +1,67 @@ -# 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. + + # # Helpful routines for regression testing # -# Add python-bitcoinrpc to module search path: 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.""" @@ -56,9 +96,11 @@ def get_rpc_proxy(url, node_number, timeout=None): def p2p_port(n): - return 11000 + n + os.getpid()%999 + assert(n <= MAX_NODES) + return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES) + def rpc_port(n): - return 12000 + n + os.getpid()%999 + 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""" @@ -70,30 +112,47 @@ def check_json_precision(): def count_bytes(hex_string): return len(bytearray.fromhex(hex_string)) -def sync_blocks(rpc_connections, wait=1): +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 block count + Wait until everybody has the same tip """ - while True: - counts = [ x.getblockcount() for x in rpc_connections ] - if counts == [ counts[0] ]*len(counts): - break - time.sleep(wait) - -def sync_mempools(rpc_connections, wait=1): + maxheight = 0 + while timeout > 0: + tips = [ x.waitforblockheight(maxheight, int(wait * 1000)) for x in rpc_connections ] + heights = [ x["height"] for x in tips ] + if tips == [ tips[0] ]*len(tips): + return True + if heights == [ heights[0] ]*len(heights): #heights are the same but hashes are not + raise AssertionError("Block sync failed") + timeout -= wait + maxheight = max(heights) + 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 True: + 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): - break + return True time.sleep(wait) + timeout -= wait + raise AssertionError("Mempool sync failed") bitcoind_processes = {} @@ -101,62 +160,101 @@ 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"); + rpc_u, rpc_p = rpc_auth_pair(n) + with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') 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 initialize_chain(test_dir): +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) + host = '127.0.0.1' + port = rpc_port(i) + if rpchost: + parts = rpchost.split(':') + if len(parts) == 2: + host, port = parts + else: + host = rpchost + return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, host, int(port)) + +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, cachedir): """ - Create (or copy from cache) a 200-block-long chain and - 4 wallets. - bitcoind and bitcoin-cli must be in search path. + Create a cache of a 200-block-long chain (with wallet) for MAX_NODES + Afterward, create num_nodes copies from the cache """ - if (not os.path.isdir(os.path.join("cache","node0")) - or not os.path.isdir(os.path.join("cache","node1")) - or not os.path.isdir(os.path.join("cache","node2")) - or not os.path.isdir(os.path.join("cache","node3"))): + assert num_nodes <= MAX_NODES + create_cache = False + for i in range(MAX_NODES): + if not os.path.isdir(os.path.join(cachedir, 'node'+str(i))): + create_cache = True + break + + if create_cache: #find and delete old cache directories if any exist - for i in range(4): - if os.path.isdir(os.path.join("cache","node"+str(i))): - shutil.rmtree(os.path.join("cache","node"+str(i))) + for i in range(MAX_NODES): + if os.path.isdir(os.path.join(cachedir,"node"+str(i))): + shutil.rmtree(os.path.join(cachedir,"node"+str(i))) - devnull = open(os.devnull, "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" ] + for i in range(MAX_NODES): + datadir=initialize_datadir(cachedir, 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, calling bitcoin-cli -rpcwait getblockcount" - subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir, - "-rpcwait", "getblockcount"], stdout=devnull) + 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: bitcoin-cli -rpcwait getblockcount completed" - devnull.close() + print("initialize_chain: RPC succesfully started") rpcs = [] - - for i in range(4): + for i in range(MAX_NODES): try: - url = "http://rt:rt@127.0.0.1:%d" % (rpc_port(i),) - rpcs.append(get_rpc_proxy(url, i)) + 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 nodes + # Create a 200-block-long chain; each of the 4 first nodes # gets 25 mature blocks and 25 immature. - # blocks are created with timestamps 10 minutes apart, starting - # at 1 Jan 2014 - block_time = 1388534400 + # 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): @@ -168,15 +266,15 @@ def initialize_chain(test_dir): # 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)) + disable_mocktime() + for i in range(MAX_NODES): + os.remove(log_filename(cachedir, i, "debug.log")) + os.remove(log_filename(cachedir, i, "db.log")) + os.remove(log_filename(cachedir, i, "peers.dat")) + os.remove(log_filename(cachedir, i, "fee_estimates.dat")) + + for i in range(num_nodes): + from_dir = os.path.join(cachedir, "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 @@ -217,21 +315,15 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary= datadir = os.path.join(dirname, "node"+str(i)) if binary is None: binary = os.getenv("BITCOIND", "bitcoind") - # RPC tests still depend on free transactions - args = [ binary, "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest", "-blockprioritysize=50000" ] + 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) - devnull = open(os.devnull, "w") if os.getenv("PYTHON_DEBUG", ""): - print "start_node: bitcoind started, calling bitcoin-cli -rpcwait getblockcount" - subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir] + - _rpchost_to_args(rpchost) + - ["-rpcwait", "getblockcount"], stdout=devnull) + 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: calling bitcoin-cli -rpcwait getblockcount returned" - devnull.close() - url = "http://rt:rt@%s:%d" % (rpchost or '127.0.0.1', rpc_port(i)) - + print("start_node: RPC succesfully started") proxy = get_rpc_proxy(url, i, timeout=timewait) if COVERAGE_DIR: @@ -243,22 +335,36 @@ 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 i in range(num_nodes) ] - if binary is None: binary = [ None for i in range(num_nodes) ] - return [ start_node(i, dirname, extra_args[i], rpchost, binary=binary[i]) for i in range(num_nodes) ] + 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): - node.stop() - bitcoind_processes[i].wait() + 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: - node.stop() + 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 + wait_bitcoinds() def set_node_times(nodes, t): for node in nodes: @@ -267,7 +373,7 @@ def set_node_times(nodes, t): def wait_bitcoinds(): # Wait for all bitcoinds to cleanly exit for bitcoind in bitcoind_processes.values(): - bitcoind.wait() + bitcoind.wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT) bitcoind_processes.clear() def connect_nodes(from_connection, node_num): @@ -388,6 +494,15 @@ def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants): 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))) @@ -406,5 +521,133 @@ def assert_raises(exc, fun, *args, **kwds): 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) + 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 index b1f603a192..22f850ece6 100755 --- a/qa/rpc-tests/txn_clone.py +++ b/qa/rpc-tests/txn_clone.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 @@ # from test_framework.test_framework import BitcoinTestFramework -from test_framework.authproxy import AuthServiceProxy, JSONRPCException -from decimal import Decimal from test_framework.util import * -import os -import shutil 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") @@ -57,16 +58,10 @@ class TxnMallTest(BitcoinTestFramework): 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_raw = self.nodes[0].createrawtransaction(clone_inputs, clone_outputs) - - # 3 hex manipulations on the clone are required + clone_locktime = rawtx1["locktime"] + clone_raw = self.nodes[0].createrawtransaction(clone_inputs, clone_outputs, clone_locktime) - # manipulation 1. sequence is at version+#inputs+input+sigstub - posseq = 2*(4+1+36+1) - seqbe = '%08x' % rawtx1["vin"][0]["sequence"] - clone_raw = clone_raw[:posseq] + seqbe[6:8] + seqbe[4:6] + seqbe[2:4] + seqbe[0:2] + clone_raw[posseq + 8:] - - # manipulation 2. createrawtransaction randomizes the order of its outputs, so swap them if necessary. + # 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) @@ -78,11 +73,6 @@ class TxnMallTest(BitcoinTestFramework): output1 = clone_raw[pos0 + output_len : pos0 + 2 * output_len] clone_raw = clone_raw[:pos0] + output1 + output0 + clone_raw[pos0 + 2 * output_len:] - # manipulation 3. locktime is after outputs - poslt = pos0 + 2 * output_len - ltbe = '%08x' % rawtx1["locktime"] - clone_raw = clone_raw[:poslt] + ltbe[6:8] + ltbe[4:6] + ltbe[2:4] + ltbe[0:2] + clone_raw[poslt + 8:] - # 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") diff --git a/qa/rpc-tests/txn_doublespend.py b/qa/rpc-tests/txn_doublespend.py index d4665b3d42..84944c3c19 100755 --- a/qa/rpc-tests/txn_doublespend.py +++ b/qa/rpc-tests/txn_doublespend.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. @@ -9,12 +9,14 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -from decimal import Decimal -import os -import shutil 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") diff --git a/qa/rpc-tests/wallet-accounts.py b/qa/rpc-tests/wallet-accounts.py new file mode 100755 index 0000000000..c51181e4f8 --- /dev/null +++ b/qa/rpc-tests/wallet-accounts.py @@ -0,0 +1,94 @@ +#!/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, +) + + +class WalletAccountsTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 + self.node_args = [[]] + + def setup_network(self): + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.node_args) + self.is_network_split = False + + def run_test (self): + node = self.nodes[0] + # Check that there's no UTXO on any of the nodes + assert_equal(len(node.listunspent()), 0) + + node.generate(101) + + assert_equal(node.getbalance(), 50) + + accounts = ["a","b","c","d","e"] + amount_to_send = 1.0 + account_addresses = dict() + for account in accounts: + address = node.getaccountaddress(account) + account_addresses[account] = address + + node.getnewaddress(account) + assert_equal(node.getaccount(address), account) + assert(address in node.getaddressesbyaccount(account)) + + node.sendfrom("", address, amount_to_send) + + node.generate(1) + + for i in range(len(accounts)): + from_account = accounts[i] + to_account = accounts[(i+1)%len(accounts)] + to_address = account_addresses[to_account] + node.sendfrom(from_account, to_address, amount_to_send) + + node.generate(1) + + for account in accounts: + address = node.getaccountaddress(account) + assert(address != account_addresses[account]) + assert_equal(node.getreceivedbyaccount(account), 2) + node.move(account, "", node.getbalance(account)) + + node.generate(101) + + expected_account_balances = {"": 5200} + for account in accounts: + expected_account_balances[account] = 0 + + assert_equal(node.listaccounts(), expected_account_balances) + + assert_equal(node.getbalance(""), 5200) + + for account in accounts: + address = node.getaccountaddress("") + node.setaccount(address, account) + assert(address in node.getaddressesbyaccount(account)) + assert(address not in node.getaddressesbyaccount("")) + + for account in accounts: + addresses = [] + for x in range(10): + addresses.append(node.getnewaddress()) + multisig_address = node.addmultisigaddress(5, addresses, account) + node.sendfrom("", multisig_address, 50) + + node.generate(101) + + for account in accounts: + assert_equal(node.getbalance(account), 50) + +if __name__ == '__main__': + WalletAccountsTest().main () diff --git a/qa/rpc-tests/wallet-dump.py b/qa/rpc-tests/wallet-dump.py new file mode 100755 index 0000000000..a37096a40c --- /dev/null +++ b/qa/rpc-tests/wallet-dump.py @@ -0,0 +1,104 @@ +#!/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, bitcoind_processes) + + +def read_dump(file_name, addrs, hd_master_addr_old): + """ + Read the given dump, count the addrs that match, count change and reserve. + Also check that the old hd_master is inactive + """ + with open(file_name, encoding='utf8') as inputfile: + found_addr = 0 + found_addr_chg = 0 + found_addr_rsv = 0 + hd_master_addr_ret = None + for line in inputfile: + # only read non comment lines + if line[0] != "#" and len(line) > 10: + # split out some data + key_label, comment = line.split("#") + # key = key_label.split(" ")[0] + keytype = key_label.split(" ")[2] + if len(comment) > 1: + addr_keypath = comment.split(" addr=")[1] + addr = addr_keypath.split(" ")[0] + keypath = None + if keytype == "inactivehdmaster=1": + # ensure the old master is still available + assert(hd_master_addr_old == addr) + elif keytype == "hdmaster=1": + # ensure we have generated a new hd master key + assert(hd_master_addr_old != addr) + hd_master_addr_ret = addr + else: + keypath = addr_keypath.rstrip().split("hdkeypath=")[1] + + # count key types + for addrObj in addrs: + if addrObj['address'] == addr and addrObj['hdkeypath'] == keypath and keytype == "label=": + found_addr += 1 + break + elif keytype == "change=1": + found_addr_chg += 1 + break + elif keytype == "reserve=1": + found_addr_rsv += 1 + break + return found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_ret + + +class WalletDumpTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = False + self.num_nodes = 1 + self.extra_args = [["-keypool=90"]] + + def setup_network(self, split=False): + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) + + def run_test (self): + tmpdir = self.options.tmpdir + + # generate 20 addresses to compare against the dump + test_addr_count = 20 + addrs = [] + for i in range(0,test_addr_count): + addr = self.nodes[0].getnewaddress() + vaddr= self.nodes[0].validateaddress(addr) #required to get hd keypath + addrs.append(vaddr) + # Should be a no-op: + self.nodes[0].keypoolrefill() + + # dump unencrypted wallet + self.nodes[0].dumpwallet(tmpdir + "/node0/wallet.unencrypted.dump") + + found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_unenc = \ + read_dump(tmpdir + "/node0/wallet.unencrypted.dump", addrs, None) + assert_equal(found_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_addr_chg, 50) # 50 blocks where mined + assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) + + #encrypt wallet, restart, unlock and dump + self.nodes[0].encryptwallet('test') + bitcoind_processes[0].wait() + self.nodes[0] = start_node(0, self.options.tmpdir, self.extra_args[0]) + self.nodes[0].walletpassphrase('test', 10) + # Should be a no-op: + self.nodes[0].keypoolrefill() + self.nodes[0].dumpwallet(tmpdir + "/node0/wallet.encrypted.dump") + + found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_enc = \ + read_dump(tmpdir + "/node0/wallet.encrypted.dump", addrs, hd_master_addr_unenc) + assert_equal(found_addr, test_addr_count) + assert_equal(found_addr_chg, 90 + 1 + 50) # old reserve keys are marked as change now + assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) + +if __name__ == '__main__': + WalletDumpTest().main () diff --git a/qa/rpc-tests/wallet-hd.py b/qa/rpc-tests/wallet-hd.py new file mode 100755 index 0000000000..a49d91f6f4 --- /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()['hdmasterkeyid'] + 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 6f6bc31895..e43f6ea5d2 100755 --- a/qa/rpc-tests/wallet.py +++ b/qa/rpc-tests/wallet.py @@ -1,24 +1,8 @@ -#!/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. 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 -# k) test ResendWalletTransactions - create transactions, startup fourth node, make sure it syncs -# - from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * @@ -27,20 +11,17 @@ class WalletTest (BitcoinTestFramework): 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 - target_fee = fee_per_byte * tx_size - if fee < target_fee: - raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)"%(str(fee), str(target_fee))) - # allow the node's estimation to be at most 2 bytes off - if fee > fee_per_byte * (tx_size + 2): - raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)"%(str(fee), str(target_fee))) + assert_fee_amount(fee, tx_size, fee_per_byte * 1000) return curr_balance - def setup_chain(self): - print("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 + self.extra_args = [['-usehd={:d}'.format(i%2==0)] for i in range(4)] def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir) + self.nodes = start_nodes(3, self.options.tmpdir, self.extra_args[:3]) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) @@ -48,7 +29,13 @@ class WalletTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + + # 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) @@ -64,8 +51,12 @@ class WalletTest (BitcoinTestFramework): 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) @@ -76,6 +67,15 @@ class WalletTest (BitcoinTestFramework): 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].generate(100) self.sync_all() @@ -97,7 +97,7 @@ class WalletTest (BitcoinTestFramework): 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)) @@ -110,8 +110,8 @@ class WalletTest (BitcoinTestFramework): 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") @@ -120,7 +120,7 @@ class WalletTest (BitcoinTestFramework): 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('90'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) + 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 @@ -155,16 +155,20 @@ class WalletTest (BitcoinTestFramework): txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1) sync_mempools(self.nodes) - self.nodes.append(start_node(3, self.options.tmpdir)) + self.nodes.append(start_node(3, self.options.tmpdir, self.extra_args[3])) connect_nodes_bi(self.nodes, 0, 3) sync_blocks(self.nodes) relayed = self.nodes[0].resendwallettransactions() - assert_equal(set(relayed), set([txid1, txid2])) + 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 @@ -190,37 +194,36 @@ class WalletTest (BitcoinTestFramework): for uTx in unspentTxs: if uTx['txid'] == zeroValueTxid: found = True - assert_equal(uTx['amount'], Decimal('0.00000000')); + 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); + 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 + 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 + Decimal('2')); #should not be + assert_equal(self.nodes[2].getbalance(), node_2_bal) #create another tx - txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2); + 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) @@ -229,41 +232,119 @@ class WalletTest (BitcoinTestFramework): 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 + Decimal('4')); #should not be + 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.00000000')) + 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.00010000')) + 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.00010000')) + assert_equal(txObj['amount'], Decimal('-0.0001')) - #this should fail - errorString = "" try: txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4") - except JSONRPCException,e: - errorString = e.error['message'] + except JSONRPCException as e: + assert("Invalid amount" in e.error['message']) + else: + raise AssertionError("Must not parse invalid amounts") - assert_equal("Invalid amount" in errorString, True); - errorString = "" try: - self.nodes[0].generate("2") #use a string to as block amount parameter must fail because it's not interpreted as amount - except JSONRPCException,e: - errorString = e.error['message'] + 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) - assert_equal("not an integer" in errorString, True); + # 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) + 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 da100d7fc0..e12cb10a50 100755 --- a/qa/rpc-tests/walletbackup.py +++ b/qa/rpc-tests/walletbackup.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. @@ -37,19 +37,20 @@ 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 + # nodes 1, 2,3 are spenders, let's give them a keypool=100 + self.extra_args = [["-keypool=100"], ["-keypool=100"], ["-keypool=100"], []] # 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, self.extra_args) connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[1], 3) connect_nodes(self.nodes[2], 3) @@ -78,6 +79,7 @@ class WalletBackupTest(BitcoinTestFramework): # Must sync mempools before mining. sync_mempools(self.nodes) self.nodes[3].generate(1) + sync_blocks(self.nodes) # As above, this mirrors the original bash test. def start_three(self): diff --git a/qa/rpc-tests/zapwallettxes.py b/qa/rpc-tests/zapwallettxes.py index 0ec8ec5364..17ba53a844 100755 --- a/qa/rpc-tests/zapwallettxes.py +++ b/qa/rpc-tests/zapwallettxes.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. @@ -9,12 +9,13 @@ from test_framework.util import * class ZapWalletTXesTest (BitcoinTestFramework): - def setup_chain(self): - print("Initializing test directory "+self.options.tmpdir) - initialize_chain_clean(self.options.tmpdir, 3) + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 3 def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir) + 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) @@ -22,7 +23,7 @@ class ZapWalletTXesTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(1) self.sync_all() self.nodes[1].generate(101) @@ -65,14 +66,8 @@ class ZapWalletTXesTest (BitcoinTestFramework): #restart bitcoind with zapwallettxes self.nodes[0] = start_node(0,self.options.tmpdir, ["-zapwallettxes=1"]) - aException = False - try: - tx3 = self.nodes[0].gettransaction(txid3) - except JSONRPCException,e: - print e - aException = True - - assert_equal(aException, True) #there must be a expection because the unconfirmed wallettx0 must be gone by now + 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 diff --git a/qa/rpc-tests/zmq_test.py b/qa/rpc-tests/zmq_test.py index bcb132321a..3a116317fe 100755 --- a/qa/rpc-tests/zmq_test.py +++ b/qa/rpc-tests/zmq_test.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python2 -# Copyright (c) 2015 The Bitcoin Core developers +#!/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. @@ -10,29 +10,26 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * import zmq -import binascii -from test_framework.mininode import hash256 - -try: - import http.client as httplib -except ImportError: - import httplib -try: - import urllib.parse as urlparse -except ImportError: - import urlparse +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, "hashblock") - self.zmqSubSocket.setsockopt(zmq.SUBSCRIBE, "hashtx") + 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(4, self.options.tmpdir, extra_args=[ + 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)], [], [], @@ -42,32 +39,42 @@ class ZMQTest (BitcoinTestFramework): def run_test(self): self.sync_all() - genhashes = self.nodes[0].generate(1); + genhashes = self.nodes[0].generate(1) self.sync_all() - print "listen..." + print("listen...") msg = self.zmqSubSocket.recv_multipart() - topic = str(msg[0]) + 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 = str(msg[0]) + topic = msg[0] body = msg[1] - blkhash = binascii.hexlify(body) + 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); + 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 = str(msg[0]) + topic = msg[0] body = msg[1] - if topic == "hashblock": - zmqHashes.append(binascii.hexlify(body)) + 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 @@ -76,13 +83,15 @@ class ZMQTest (BitcoinTestFramework): 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 broadcastet + # now we should receive a zmq msg because the tx was broadcast msg = self.zmqSubSocket.recv_multipart() - topic = str(msg[0]) + topic = msg[0] body = msg[1] hashZMQ = "" - if topic == "hashtx": - hashZMQ = binascii.hexlify(body) + 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 |