aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md20
-rwxr-xr-xtest/functional/fundrawtransaction.py17
-rwxr-xr-xtest/functional/getblocktemplate_proposals.py157
-rwxr-xr-xtest/functional/listsinceblock.py190
-rwxr-xr-xtest/functional/mining.py124
-rwxr-xr-xtest/functional/multiwallet.py78
-rwxr-xr-xtest/functional/rawtransactions.py57
-rwxr-xr-xtest/functional/signrawtransactions.py16
-rw-r--r--test/functional/test_framework/authproxy.py3
-rw-r--r--test/functional/test_framework/coverage.py2
-rwxr-xr-xtest/functional/test_runner.py3
-rwxr-xr-xtest/functional/zapwallettxes.py100
12 files changed, 513 insertions, 254 deletions
diff --git a/test/README.md b/test/README.md
index 15f6df790f..868eb667ae 100644
--- a/test/README.md
+++ b/test/README.md
@@ -155,6 +155,26 @@ import pdb; pdb.set_trace()
anywhere in the test. You will then be able to inspect variables, as well as
call methods that interact with the bitcoind nodes-under-test.
+If further introspection of the bitcoind instances themselves becomes
+necessary, this can be accomplished by first setting a pdb breakpoint
+at an appropriate location, running the test to that point, then using
+`gdb` to attach to the process and debug.
+
+For instance, to attach to `self.node[1]` during a run:
+
+```bash
+2017-06-27 14:13:56.686000 TestFramework (INFO): Initializing test directory /tmp/user/1000/testo9vsdjo3
+```
+
+use the directory path to get the pid from the pid file:
+
+```bash
+cat /tmp/user/1000/testo9vsdjo3/node1/regtest/bitcoind.pid
+gdb /home/example/bitcoind <pid>
+```
+
+Note: gdb attach step may require `sudo`
+
### Util tests
Util tests can be run locally by running `test/util/bitcoin-util-test.py`.
diff --git a/test/functional/fundrawtransaction.py b/test/functional/fundrawtransaction.py
index 0baab6d01c..e52e773918 100755
--- a/test/functional/fundrawtransaction.py
+++ b/test/functional/fundrawtransaction.py
@@ -636,20 +636,9 @@ class RawTransactionsTest(BitcoinTestFramework):
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)
- #############################
- # Test address reuse option #
- #############################
-
- result3 = self.nodes[3].fundrawtransaction(rawtx, {"reserveChangeKey": False})
- res_dec = self.nodes[0].decoderawtransaction(result3["hex"])
- changeaddress = ""
- for out in res_dec['vout']:
- if out['value'] > 1.0:
- changeaddress += out['scriptPubKey']['addresses'][0]
- assert(changeaddress != "")
- nextaddr = self.nodes[3].getrawchangeaddress()
- # frt should not have removed the key from the keypool
- assert(changeaddress == nextaddr)
+ ################################
+ # Test no address reuse occurs #
+ ################################
result3 = self.nodes[3].fundrawtransaction(rawtx)
res_dec = self.nodes[0].decoderawtransaction(result3["hex"])
diff --git a/test/functional/getblocktemplate_proposals.py b/test/functional/getblocktemplate_proposals.py
deleted file mode 100755
index fca99c7df5..0000000000
--- a/test/functional/getblocktemplate_proposals.py
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/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 block proposals with getblocktemplate."""
-
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import *
-
-from binascii import a2b_hex, b2a_hex
-from hashlib import sha256
-from struct import pack
-
-def b2x(b):
- return b2a_hex(b).decode('ascii')
-
-# NOTE: This does not work for signed numbers (set the high bit) or zero (use b'\0')
-def encodeUNum(n):
- s = bytearray(b'\1')
- while n > 127:
- s[0] += 1
- s.append(n % 256)
- n //= 256
- s.append(n)
- return bytes(s)
-
-def varlenEncode(n):
- if n < 0xfd:
- return pack('<B', n)
- if n <= 0xffff:
- return b'\xfd' + pack('<H', n)
- if n <= 0xffffffff:
- return b'\xfe' + pack('<L', n)
- return b'\xff' + pack('<Q', n)
-
-def dblsha(b):
- return sha256(sha256(b).digest()).digest()
-
-def genmrklroot(leaflist):
- cur = leaflist
- while len(cur) > 1:
- n = []
- if len(cur) & 1:
- cur.append(cur[-1])
- for i in range(0, len(cur), 2):
- n.append(dblsha(cur[i] + cur[i+1]))
- cur = n
- return cur[0]
-
-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'])
- nonce = b'\0\0\0\0'
- blk = blkver + a2b_hex(tmpl['previousblockhash'])[::-1] + mrklroot + timestamp + a2b_hex(tmpl['bits'])[::-1] + nonce
- blk += varlenEncode(len(txlist))
- for tx in txlist:
- blk += tx
- return bytearray(blk)
-
-def template_to_hex(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'})
- if rsp != expect:
- raise AssertionError('unexpected: %s' % (rsp,))
-
-class GetBlockTemplateProposalTest(BitcoinTestFramework):
-
- def __init__(self):
- super().__init__()
- self.num_nodes = 2
- self.setup_clean_chain = False
-
- def run_test(self):
- node = self.nodes[0]
- node.generate(1) # Mine a block to leave initial block download
- tmpl = node.getblocktemplate()
- if 'coinbasetxn' not in tmpl:
- rawcoinbase = encodeUNum(tmpl['height'])
- rawcoinbase += b'\x01-'
- hexcoinbase = b2x(rawcoinbase)
- hexoutval = b2x(pack('<Q', tmpl['coinbasevalue']))
- tmpl['coinbasetxn'] = {'data': '01000000' + '01' + '0000000000000000000000000000000000000000000000000000000000000000ffffffff' + ('%02x' % (len(rawcoinbase),)) + hexcoinbase + 'fffffffe' + '01' + hexoutval + '00' + '00000000'}
- txlist = list(bytearray(a2b_hex(a['data'])) for a in (tmpl['coinbasetxn'],) + tuple(tmpl['transactions']))
-
- # Test 0: Capability advertised
- assert('proposal' in tmpl['capabilities'])
-
- # NOTE: This test currently FAILS (regtest mode doesn't enforce block height in coinbase)
- ## Test 1: Bad height in coinbase
- #txlist[0][4+1+36+1+1] += 1
- #assert_template(node, tmpl, txlist, 'FIXME')
- #txlist[0][4+1+36+1+1] -= 1
-
- # Test 2: Bad input hash for gen tx
- txlist[0][4+1] += 1
- assert_template(node, tmpl, txlist, 'bad-cb-missing')
- txlist[0][4+1] -= 1
-
- # Test 3: Truncated final tx
- lastbyte = txlist[-1].pop()
- assert_raises_jsonrpc(-22, "Block decode failed", assert_template, node, tmpl, txlist, 'n/a')
- txlist[-1].append(lastbyte)
-
- # Test 4: Add an invalid tx to the end (duplicate of gen tx)
- txlist.append(txlist[0])
- assert_template(node, tmpl, txlist, 'bad-txns-duplicate')
- txlist.pop()
-
- # Test 5: Add an invalid tx to the end (non-duplicate)
- txlist.append(bytearray(txlist[0]))
- txlist[-1][4+1] = 0xff
- assert_template(node, tmpl, txlist, 'bad-txns-inputs-missingorspent')
- txlist.pop()
-
- # Test 6: Future tx lock time
- txlist[0][-4:] = b'\xff\xff\xff\xff'
- assert_template(node, tmpl, txlist, 'bad-txns-nonfinal')
- txlist[0][-4:] = b'\0\0\0\0'
-
- # Test 7: Bad tx count
- txlist.append(b'')
- assert_raises_jsonrpc(-22, 'Block decode failed', assert_template, node, tmpl, txlist, 'n/a')
- txlist.pop()
-
- # Test 8: Bad bits
- realbits = tmpl['bits']
- tmpl['bits'] = '1c0000ff' # impossible in the real world
- assert_template(node, tmpl, txlist, 'bad-diffbits')
- tmpl['bits'] = realbits
-
- # Test 9: Bad merkle root
- 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':
- raise AssertionError('unexpected: %s' % (rsp,))
-
- # Test 10: Bad timestamps
- realtime = tmpl['curtime']
- tmpl['curtime'] = 0x7fffffff
- assert_template(node, tmpl, txlist, 'time-too-new')
- tmpl['curtime'] = 0
- assert_template(node, tmpl, txlist, 'time-too-old')
- tmpl['curtime'] = realtime
-
- # Test 11: Valid block
- assert_template(node, tmpl, txlist, None)
-
- # Test 12: Orphan block
- tmpl['previousblockhash'] = 'ff00' * 16
- assert_template(node, tmpl, txlist, 'inconclusive-not-best-prevblk')
-
-if __name__ == '__main__':
- GetBlockTemplateProposalTest().main()
diff --git a/test/functional/listsinceblock.py b/test/functional/listsinceblock.py
index f3d41e573e..ce2d556ef0 100755
--- a/test/functional/listsinceblock.py
+++ b/test/functional/listsinceblock.py
@@ -14,7 +14,15 @@ class ListSinceBlockTest (BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 4
- def run_test (self):
+ def run_test(self):
+ self.nodes[2].generate(101)
+ self.sync_all()
+
+ self.test_reorg()
+ self.test_double_spend()
+ self.test_double_send()
+
+ def test_reorg(self):
'''
`listsinceblock` did not behave correctly when handed a block that was
no longer in the main chain:
@@ -43,14 +51,6 @@ class ListSinceBlockTest (BitcoinTestFramework):
This test only checks that [tx0] is present.
'''
- self.nodes[2].generate(101)
- self.sync_all()
-
- assert_equal(self.nodes[0].getbalance(), 0)
- assert_equal(self.nodes[1].getbalance(), 0)
- assert_equal(self.nodes[2].getbalance(), 50)
- assert_equal(self.nodes[3].getbalance(), 0)
-
# Split network into two
self.split_network()
@@ -73,7 +73,177 @@ class ListSinceBlockTest (BitcoinTestFramework):
if tx['txid'] == senttx:
found = True
break
- assert_equal(found, True)
+ assert found
+
+ def test_double_spend(self):
+ '''
+ This tests the case where the same UTXO is spent twice on two separate
+ blocks as part of a reorg.
+
+ ab0
+ / \
+ aa1 [tx1] bb1 [tx2]
+ | |
+ aa2 bb2
+ | |
+ aa3 bb3
+ |
+ bb4
+
+ Problematic case:
+
+ 1. User 1 receives BTC in tx1 from utxo1 in block aa1.
+ 2. User 2 receives BTC in tx2 from utxo1 (same) in block bb1
+ 3. User 1 sees 2 confirmations at block aa3.
+ 4. Reorg into bb chain.
+ 5. User 1 asks `listsinceblock aa3` and does not see that tx1 is now
+ invalidated.
+
+ Currently the solution to this is to detect that a reorg'd block is
+ asked for in listsinceblock, and to iterate back over existing blocks up
+ until the fork point, and to include all transactions that relate to the
+ node wallet.
+ '''
+
+ self.sync_all()
+
+ # Split network into two
+ self.split_network()
+
+ # share utxo between nodes[1] and nodes[2]
+ utxos = self.nodes[2].listunspent()
+ utxo = utxos[0]
+ privkey = self.nodes[2].dumpprivkey(utxo['address'])
+ self.nodes[1].importprivkey(privkey)
+
+ # send from nodes[1] using utxo to nodes[0]
+ change = '%.8f' % (float(utxo['amount']) - 1.0003)
+ recipientDict = {
+ self.nodes[0].getnewaddress(): 1,
+ self.nodes[1].getnewaddress(): change,
+ }
+ utxoDicts = [{
+ 'txid': utxo['txid'],
+ 'vout': utxo['vout'],
+ }]
+ txid1 = self.nodes[1].sendrawtransaction(
+ self.nodes[1].signrawtransaction(
+ self.nodes[1].createrawtransaction(utxoDicts, recipientDict))['hex'])
+
+ # send from nodes[2] using utxo to nodes[3]
+ recipientDict2 = {
+ self.nodes[3].getnewaddress(): 1,
+ self.nodes[2].getnewaddress(): change,
+ }
+ self.nodes[2].sendrawtransaction(
+ self.nodes[2].signrawtransaction(
+ self.nodes[2].createrawtransaction(utxoDicts, recipientDict2))['hex'])
+
+ # generate on both sides
+ lastblockhash = self.nodes[1].generate(3)[2]
+ self.nodes[2].generate(4)
+
+ self.join_network()
+
+ self.sync_all()
+
+ # gettransaction should work for txid1
+ assert self.nodes[0].gettransaction(txid1)['txid'] == txid1, "gettransaction failed to find txid1"
+
+ # listsinceblock(lastblockhash) should now include txid1, as seen from nodes[0]
+ lsbres = self.nodes[0].listsinceblock(lastblockhash)
+ assert any(tx['txid'] == txid1 for tx in lsbres['removed'])
+
+ # but it should not include 'removed' if include_removed=false
+ lsbres2 = self.nodes[0].listsinceblock(blockhash=lastblockhash, include_removed=False)
+ assert 'removed' not in lsbres2
+
+ def test_double_send(self):
+ '''
+ This tests the case where the same transaction is submitted twice on two
+ separate blocks as part of a reorg. The former will vanish and the
+ latter will appear as the true transaction (with confirmations dropping
+ as a result).
+
+ ab0
+ / \
+ aa1 [tx1] bb1
+ | |
+ aa2 bb2
+ | |
+ aa3 bb3 [tx1]
+ |
+ bb4
+
+ Asserted:
+
+ 1. tx1 is listed in listsinceblock.
+ 2. It is included in 'removed' as it was removed, even though it is now
+ present in a different block.
+ 3. It is listed with a confirmations count of 2 (bb3, bb4), not
+ 3 (aa1, aa2, aa3).
+ '''
+
+ self.sync_all()
+
+ # Split network into two
+ self.split_network()
+
+ # create and sign a transaction
+ utxos = self.nodes[2].listunspent()
+ utxo = utxos[0]
+ change = '%.8f' % (float(utxo['amount']) - 1.0003)
+ recipientDict = {
+ self.nodes[0].getnewaddress(): 1,
+ self.nodes[2].getnewaddress(): change,
+ }
+ utxoDicts = [{
+ 'txid': utxo['txid'],
+ 'vout': utxo['vout'],
+ }]
+ signedtxres = self.nodes[2].signrawtransaction(
+ self.nodes[2].createrawtransaction(utxoDicts, recipientDict))
+ assert signedtxres['complete']
+
+ signedtx = signedtxres['hex']
+
+ # send from nodes[1]; this will end up in aa1
+ txid1 = self.nodes[1].sendrawtransaction(signedtx)
+
+ # generate bb1-bb2 on right side
+ self.nodes[2].generate(2)
+
+ # send from nodes[2]; this will end up in bb3
+ txid2 = self.nodes[2].sendrawtransaction(signedtx)
+
+ assert_equal(txid1, txid2)
+
+ # generate on both sides
+ lastblockhash = self.nodes[1].generate(3)[2]
+ self.nodes[2].generate(2)
+
+ self.join_network()
+
+ self.sync_all()
+
+ # gettransaction should work for txid1
+ self.nodes[0].gettransaction(txid1)
+
+ # listsinceblock(lastblockhash) should now include txid1 in transactions
+ # as well as in removed
+ lsbres = self.nodes[0].listsinceblock(lastblockhash)
+ assert any(tx['txid'] == txid1 for tx in lsbres['transactions'])
+ assert any(tx['txid'] == txid1 for tx in lsbres['removed'])
+
+ # find transaction and ensure confirmations is valid
+ for tx in lsbres['transactions']:
+ if tx['txid'] == txid1:
+ assert_equal(tx['confirmations'], 2)
+
+ # the same check for the removed array; confirmations should STILL be 2
+ for tx in lsbres['removed']:
+ if tx['txid'] == txid1:
+ assert_equal(tx['confirmations'], 2)
if __name__ == '__main__':
ListSinceBlockTest().main()
diff --git a/test/functional/mining.py b/test/functional/mining.py
new file mode 100755
index 0000000000..dbd4e29eca
--- /dev/null
+++ b/test/functional/mining.py
@@ -0,0 +1,124 @@
+#!/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 mining RPCs
+
+- getblocktemplate proposal mode
+- submitblock"""
+
+from binascii import b2a_hex
+import copy
+
+from test_framework.blocktools import create_coinbase
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.mininode import CBlock
+from test_framework.util import *
+
+def b2x(b):
+ return b2a_hex(b).decode('ascii')
+
+def assert_template(node, block, expect, rehash=True):
+ if rehash:
+ block.hashMerkleRoot = block.calc_merkle_root()
+ rsp = node.getblocktemplate({'data': b2x(block.serialize()), 'mode': 'proposal'})
+ assert_equal(rsp, expect)
+
+class MiningTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = False
+
+ def run_test(self):
+ node = self.nodes[0]
+ # Mine a block to leave initial block download
+ node.generate(1)
+ tmpl = node.getblocktemplate()
+ self.log.info("getblocktemplate: Test capability advertised")
+ assert 'proposal' in tmpl['capabilities']
+ assert 'coinbasetxn' not in tmpl
+
+ coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1)
+ # sequence numbers must not be max for nLockTime to have effect
+ coinbase_tx.vin[0].nSequence = 2 ** 32 - 2
+ coinbase_tx.rehash()
+
+ block = CBlock()
+ block.nVersion = tmpl["version"]
+ block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
+ block.nTime = tmpl["curtime"]
+ block.nBits = int(tmpl["bits"], 16)
+ block.nNonce = 0
+ block.vtx = [coinbase_tx]
+
+ self.log.info("getblocktemplate: Test valid block")
+ assert_template(node, block, None)
+
+ self.log.info("submitblock: Test block decode failure")
+ assert_raises_jsonrpc(-22, "Block decode failed", node.submitblock, b2x(block.serialize()[:-15]))
+
+ self.log.info("getblocktemplate: Test bad input hash for coinbase transaction")
+ bad_block = copy.deepcopy(block)
+ bad_block.vtx[0].vin[0].prevout.hash += 1
+ bad_block.vtx[0].rehash()
+ assert_template(node, bad_block, 'bad-cb-missing')
+
+ self.log.info("submitblock: Test invalid coinbase transaction")
+ assert_raises_jsonrpc(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize()))
+
+ self.log.info("getblocktemplate: Test truncated final transaction")
+ assert_raises_jsonrpc(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(block.serialize()[:-1]), 'mode': 'proposal'})
+
+ self.log.info("getblocktemplate: Test duplicate transaction")
+ bad_block = copy.deepcopy(block)
+ bad_block.vtx.append(bad_block.vtx[0])
+ assert_template(node, bad_block, 'bad-txns-duplicate')
+
+ self.log.info("getblocktemplate: Test invalid transaction")
+ bad_block = copy.deepcopy(block)
+ bad_tx = copy.deepcopy(bad_block.vtx[0])
+ bad_tx.vin[0].prevout.hash = 255
+ bad_tx.rehash()
+ bad_block.vtx.append(bad_tx)
+ assert_template(node, bad_block, 'bad-txns-inputs-missingorspent')
+
+ self.log.info("getblocktemplate: Test nonfinal transaction")
+ bad_block = copy.deepcopy(block)
+ bad_block.vtx[0].nLockTime = 2 ** 32 - 1
+ bad_block.vtx[0].rehash()
+ assert_template(node, bad_block, 'bad-txns-nonfinal')
+
+ self.log.info("getblocktemplate: Test bad tx count")
+ # The tx count is immediately after the block header
+ TX_COUNT_OFFSET = 80
+ bad_block_sn = bytearray(block.serialize())
+ assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1)
+ bad_block_sn[TX_COUNT_OFFSET] += 1
+ assert_raises_jsonrpc(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(bad_block_sn), 'mode': 'proposal'})
+
+ self.log.info("getblocktemplate: Test bad bits")
+ bad_block = copy.deepcopy(block)
+ bad_block.nBits = 469762303 # impossible in the real world
+ assert_template(node, bad_block, 'bad-diffbits')
+
+ self.log.info("getblocktemplate: Test bad merkle root")
+ bad_block = copy.deepcopy(block)
+ bad_block.hashMerkleRoot += 1
+ assert_template(node, bad_block, 'bad-txnmrklroot', False)
+
+ self.log.info("getblocktemplate: Test bad timestamps")
+ bad_block = copy.deepcopy(block)
+ bad_block.nTime = 2 ** 31 - 1
+ assert_template(node, bad_block, 'time-too-new')
+ bad_block.nTime = 0
+ assert_template(node, bad_block, 'time-too-old')
+
+ self.log.info("getblocktemplate: Test not best block")
+ bad_block = copy.deepcopy(block)
+ bad_block.hashPrevBlock = 123
+ assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
+
+if __name__ == '__main__':
+ MiningTest().main()
diff --git a/test/functional/multiwallet.py b/test/functional/multiwallet.py
new file mode 100755
index 0000000000..5679f40503
--- /dev/null
+++ b/test/functional/multiwallet.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+# Copyright (c) 2017 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 multiwallet.
+
+Verify that a bitcoind node can load multiple wallet files
+"""
+import os
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal, assert_raises_jsonrpc
+
+class MultiWalletTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+ self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3']]
+
+ def run_test(self):
+ self.stop_node(0)
+
+ # should not initialize if there are duplicate wallets
+ self.assert_start_raises_init_error(0, self.options.tmpdir, ['-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.')
+
+ # should not initialize if wallet file is a directory
+ os.mkdir(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w11'))
+ self.assert_start_raises_init_error(0, self.options.tmpdir, ['-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.')
+
+ # should not initialize if wallet file is a symlink
+ os.symlink(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w1'), os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w12'))
+ self.assert_start_raises_init_error(0, self.options.tmpdir, ['-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.')
+
+ self.nodes[0] = self.start_node(0, self.options.tmpdir, self.extra_args[0])
+
+ w1 = self.nodes[0] / "wallet/w1"
+ w1.generate(1)
+
+ # accessing invalid wallet fails
+ assert_raises_jsonrpc(-18, "Requested wallet does not exist or is not loaded", (self.nodes[0] / "wallet/bad").getwalletinfo)
+
+ # accessing wallet RPC without using wallet endpoint fails
+ assert_raises_jsonrpc(-19, "Wallet file not specified", self.nodes[0].getwalletinfo)
+
+ # check w1 wallet balance
+ w1_info = w1.getwalletinfo()
+ assert_equal(w1_info['immature_balance'], 50)
+ w1_name = w1_info['walletname']
+ assert_equal(w1_name, "w1")
+
+ # check w1 wallet balance
+ w2 = self.nodes[0] / "wallet/w2"
+ w2_info = w2.getwalletinfo()
+ assert_equal(w2_info['immature_balance'], 0)
+ w2_name = w2_info['walletname']
+ assert_equal(w2_name, "w2")
+
+ w3 = self.nodes[0] / "wallet/w3"
+ w3_name = w3.getwalletinfo()['walletname']
+ assert_equal(w3_name, "w3")
+
+ assert_equal({"w1", "w2", "w3"}, {w1_name, w2_name, w3_name})
+
+ w1.generate(101)
+ assert_equal(w1.getbalance(), 100)
+ assert_equal(w2.getbalance(), 0)
+ assert_equal(w3.getbalance(), 0)
+
+ w1.sendtoaddress(w2.getnewaddress(), 1)
+ w1.sendtoaddress(w3.getnewaddress(), 2)
+ w1.generate(1)
+ assert_equal(w2.getbalance(), 1)
+ assert_equal(w3.getbalance(), 2)
+
+if __name__ == '__main__':
+ MultiWalletTest().main()
diff --git a/test/functional/rawtransactions.py b/test/functional/rawtransactions.py
index 35debf9cab..6272fc69b7 100755
--- a/test/functional/rawtransactions.py
+++ b/test/functional/rawtransactions.py
@@ -114,7 +114,7 @@ class RawTransactionsTest(BitcoinTestFramework):
rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
rawTxPartialSigned = self.nodes[1].signrawtransaction(rawTx, inputs)
assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx
-
+
rawTxSigned = self.nodes[2].signrawtransaction(rawTx, inputs)
assert_equal(rawTxSigned['complete'], True) #node2 can sign the tx compl., own two of three keys
self.nodes[2].sendrawtransaction(rawTxSigned['hex'])
@@ -124,6 +124,55 @@ class RawTransactionsTest(BitcoinTestFramework):
self.sync_all()
assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
+ # 2of2 test for combining transactions
+ bal = self.nodes[2].getbalance()
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[1].validateaddress(addr1)
+ addr2Obj = self.nodes[2].validateaddress(addr2)
+
+ self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+ mSigObjValid = self.nodes[2].validateaddress(mSigObj)
+
+ txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
+ decTx = self.nodes[0].gettransaction(txId)
+ rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ assert_equal(self.nodes[2].getbalance(), bal) # the funds of a 2of2 multisig tx should not be marked as spendable
+
+ txDetails = self.nodes[0].gettransaction(txId, True)
+ rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex'])
+ vout = False
+ for outpoint in rawTx2['vout']:
+ if outpoint['value'] == Decimal('2.20000000'):
+ vout = outpoint
+ break
+
+ bal = self.nodes[0].getbalance()
+ inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}]
+ outputs = { self.nodes[0].getnewaddress() : 2.19 }
+ rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs)
+ rawTxPartialSigned1 = self.nodes[1].signrawtransaction(rawTx2, inputs)
+ self.log.info(rawTxPartialSigned1)
+ assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx
+
+ rawTxPartialSigned2 = self.nodes[2].signrawtransaction(rawTx2, inputs)
+ self.log.info(rawTxPartialSigned2)
+ assert_equal(rawTxPartialSigned2['complete'], False) #node2 only has one key, can't comp. sign the tx
+ rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
+ self.log.info(rawTxComb)
+ self.nodes[2].sendrawtransaction(rawTxComb)
+ rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
+
# getrawtransaction tests
# 1. valid parameters - only supply txid
txHash = rawTx["hash"]
@@ -156,17 +205,17 @@ class RawTransactionsTest(BitcoinTestFramework):
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
decrawtx= self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['vin'][0]['sequence'], 1000)
-
+
# 9. invalid parameters - sequence number out of range
inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : -1}]
outputs = { self.nodes[0].getnewaddress() : 1 }
assert_raises_jsonrpc(-8, 'Invalid parameter, sequence number is out of range', self.nodes[0].createrawtransaction, inputs, outputs)
-
+
# 10. invalid parameters - sequence number out of range
inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1, 'sequence' : 4294967296}]
outputs = { self.nodes[0].getnewaddress() : 1 }
assert_raises_jsonrpc(-8, 'Invalid parameter, sequence number is out of range', 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)
diff --git a/test/functional/signrawtransactions.py b/test/functional/signrawtransactions.py
index 437905e764..415727268a 100755
--- a/test/functional/signrawtransactions.py
+++ b/test/functional/signrawtransactions.py
@@ -43,22 +43,6 @@ class SignRawTransactionsTest(BitcoinTestFramework):
# 2) No script verification error occurred
assert 'errors' not in rawTxSigned
- # Check that signrawtransaction doesn't blow up on garbage merge attempts
- dummyTxInconsistent = self.nodes[0].createrawtransaction([inputs[0]], outputs)
- rawTxUnsigned = self.nodes[0].signrawtransaction(rawTx + dummyTxInconsistent, inputs)
-
- assert 'complete' in rawTxUnsigned
- assert_equal(rawTxUnsigned['complete'], False)
-
- # Check that signrawtransaction properly merges unsigned and signed txn, even with garbage in the middle
- rawTxSigned2 = self.nodes[0].signrawtransaction(rawTxUnsigned["hex"] + dummyTxInconsistent + rawTxSigned["hex"], inputs)
-
- assert 'complete' in rawTxSigned2
- assert_equal(rawTxSigned2['complete'], True)
-
- assert 'errors' not in rawTxSigned2
-
-
def script_verification_error_test(self):
"""Create and sign a raw transaction with valid (vin 0), invalid (vin 1) and one missing (vin 2) input script.
diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py
index dfcc524313..b3671cbdc5 100644
--- a/test/functional/test_framework/authproxy.py
+++ b/test/functional/test_framework/authproxy.py
@@ -191,3 +191,6 @@ class AuthServiceProxy(object):
else:
log.debug("<-- [%.6f] %s"%(elapsed,responsedata))
return response
+
+ def __truediv__(self, relative_uri):
+ return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)
diff --git a/test/functional/test_framework/coverage.py b/test/functional/test_framework/coverage.py
index 3f87ef91f6..227b1a17af 100644
--- a/test/functional/test_framework/coverage.py
+++ b/test/functional/test_framework/coverage.py
@@ -56,6 +56,8 @@ class AuthServiceProxyWrapper(object):
def url(self):
return self.auth_service_proxy_instance.url
+ def __truediv__(self, relative_uri):
+ return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri)
def get_filename(dirname, n_node):
"""
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 54f625514b..a043560ea8 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -88,6 +88,7 @@ BASE_SCRIPTS= [
'mempool_spendcoinbase.py',
'mempool_reorg.py',
'mempool_persist.py',
+ 'multiwallet.py',
'httpbasics.py',
'multi_rpc.py',
'proxy_test.py',
@@ -108,6 +109,7 @@ BASE_SCRIPTS= [
'signmessages.py',
'nulldummy.py',
'import-rescan.py',
+ 'mining.py',
'bumpfee.py',
'rpcnamedargs.py',
'listsinceblock.py',
@@ -141,7 +143,6 @@ EXTENDED_SCRIPTS = [
'bipdersig-p2p.py',
'bipdersig.py',
'example_test.py',
- 'getblocktemplate_proposals.py',
'txn_doublespend.py',
'txn_clone.py --mineblock',
'forknotify.py',
diff --git a/test/functional/zapwallettxes.py b/test/functional/zapwallettxes.py
index e4d40520ef..af867d7a52 100755
--- a/test/functional/zapwallettxes.py
+++ b/test/functional/zapwallettxes.py
@@ -4,77 +4,73 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the zapwallettxes functionality.
-- start three bitcoind nodes
-- create four transactions on node 0 - two are confirmed and two are
- unconfirmed.
-- restart node 1 and verify that both the confirmed and the unconfirmed
+- start two bitcoind nodes
+- create two transactions on node 0 - one is confirmed and one is unconfirmed.
+- restart node 0 and verify that both the confirmed and the unconfirmed
transactions are still available.
-- restart node 0 and verify that the confirmed transactions are still
- available, but that the unconfirmed transaction has been zapped.
+- restart node 0 with zapwallettxes and persistmempool, and verify that both
+ the confirmed and the unconfirmed transactions are still available.
+- restart node 0 with just zapwallettxed and verify that the confirmed
+ transactions are still available, but that the unconfirmed transaction has
+ been zapped.
"""
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import *
-
+from test_framework.util import (assert_equal,
+ assert_raises_jsonrpc,
+ )
class ZapWalletTXesTest (BitcoinTestFramework):
def __init__(self):
super().__init__()
self.setup_clean_chain = True
- self.num_nodes = 3
-
- def setup_network(self):
- super().setup_network()
- connect_nodes_bi(self.nodes,0,2)
+ self.num_nodes = 2
- def run_test (self):
+ def run_test(self):
self.log.info("Mining blocks...")
self.nodes[0].generate(1)
self.sync_all()
- self.nodes[1].generate(101)
- self.sync_all()
-
- assert_equal(self.nodes[0].getbalance(), 50)
-
- txid0 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
- txid1 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
+ self.nodes[1].generate(100)
self.sync_all()
+
+ # This transaction will be confirmed
+ txid1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 10)
+
self.nodes[0].generate(1)
self.sync_all()
-
- txid2 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
- txid3 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
-
- tx0 = self.nodes[0].gettransaction(txid0)
- assert_equal(tx0['txid'], txid0) #tx0 must be available (confirmed)
-
- tx1 = self.nodes[0].gettransaction(txid1)
- assert_equal(tx1['txid'], txid1) #tx1 must be available (confirmed)
-
- tx2 = self.nodes[0].gettransaction(txid2)
- assert_equal(tx2['txid'], txid2) #tx2 must be available (unconfirmed)
-
- tx3 = self.nodes[0].gettransaction(txid3)
- assert_equal(tx3['txid'], txid3) #tx3 must be available (unconfirmed)
-
- #restart bitcoind
+
+ # This transaction will not be confirmed
+ txid2 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 20)
+
+ # Confirmed and unconfirmed transactions are now in the wallet.
+ assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
+ assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
+
+ # Stop-start node0. Both confirmed and unconfirmed transactions remain in the wallet.
+ self.stop_node(0)
+ self.nodes[0] = self.start_node(0, self.options.tmpdir)
+
+ assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
+ assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
+
+ # Stop node0 and restart with zapwallettxes and persistmempool. The unconfirmed
+ # transaction is zapped from the wallet, but is re-added when the mempool is reloaded.
self.stop_node(0)
- self.nodes[0] = self.start_node(0,self.options.tmpdir)
-
- tx3 = self.nodes[0].gettransaction(txid3)
- assert_equal(tx3['txid'], txid3) #tx must be available (unconfirmed)
-
+ self.nodes[0] = self.start_node(0, self.options.tmpdir, ["-persistmempool=1", "-zapwallettxes=2"])
+
+ assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
+ assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
+
+ # Stop node0 and restart with zapwallettxes, but not persistmempool.
+ # The unconfirmed transaction is zapped and is no longer in the wallet.
self.stop_node(0)
-
- #restart bitcoind with zapwallettxes
- self.nodes[0] = self.start_node(0,self.options.tmpdir, ["-zapwallettxes=1"])
-
- assert_raises(JSONRPCException, self.nodes[0].gettransaction, [txid3])
- #there must be an exception because the unconfirmed wallettx0 must be gone by now
+ self.nodes[0] = self.start_node(0, self.options.tmpdir, ["-zapwallettxes=2"])
- tx0 = self.nodes[0].gettransaction(txid0)
- assert_equal(tx0['txid'], txid0) #tx0 (confirmed) must still be available because it was confirmed
+ # tx1 is still be available because it was confirmed
+ assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
+ # This will raise an exception because the unconfirmed transaction has been zapped
+ assert_raises_jsonrpc(-5, 'Invalid or non-wallet transaction id', self.nodes[0].gettransaction, txid2)
if __name__ == '__main__':
- ZapWalletTXesTest ().main ()
+ ZapWalletTXesTest().main()