aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/functional/data/rpc_psbt.json3
-rwxr-xr-xtest/functional/example_test.py4
-rwxr-xr-xtest/functional/feature_block.py23
-rwxr-xr-xtest/functional/feature_cltv.py1
-rwxr-xr-xtest/functional/feature_config_args.py9
-rwxr-xr-xtest/functional/feature_dbcrash.py1
-rwxr-xr-xtest/functional/feature_dersig.py1
-rwxr-xr-xtest/functional/feature_fee_estimation.py8
-rwxr-xr-xtest/functional/feature_pruning.py4
-rwxr-xr-xtest/functional/interface_rpc.py46
-rwxr-xr-xtest/functional/interface_zmq.py9
-rwxr-xr-xtest/functional/mempool_resurrect.py5
-rwxr-xr-xtest/functional/mining_basic.py3
-rwxr-xr-xtest/functional/p2p_disconnect_ban.py2
-rwxr-xr-xtest/functional/p2p_invalid_block.py9
-rwxr-xr-xtest/functional/p2p_invalid_messages.py175
-rwxr-xr-xtest/functional/p2p_leak_tx.py57
-rwxr-xr-xtest/functional/p2p_node_network_limited.py4
-rwxr-xr-xtest/functional/rpc_blockchain.py6
-rwxr-xr-xtest/functional/rpc_help.py15
-rwxr-xr-xtest/functional/rpc_net.py6
-rwxr-xr-xtest/functional/rpc_psbt.py61
-rw-r--r--test/functional/test_framework/blocktools.py2
-rwxr-xr-xtest/functional/test_framework/messages.py24
-rwxr-xr-xtest/functional/test_framework/mininode.py66
-rw-r--r--test/functional/test_framework/socks5.py7
-rwxr-xr-xtest/functional/test_framework/test_framework.py7
-rwxr-xr-xtest/functional/test_framework/test_node.py61
-rwxr-xr-xtest/functional/test_runner.py62
-rwxr-xr-xtest/functional/wallet_basic.py25
-rwxr-xr-xtest/functional/wallet_encryption.py6
-rwxr-xr-xtest/functional/wallet_import_rescan.py31
-rwxr-xr-xtest/functional/wallet_import_with_label.py134
-rwxr-xr-xtest/functional/wallet_importmulti.py187
-rwxr-xr-xtest/functional/wallet_importprunedfunds.py2
-rwxr-xr-xtest/functional/wallet_listreceivedby.py12
-rwxr-xr-xtest/functional/wallet_listtransactions.py7
-rwxr-xr-xtest/functional/wallet_multiwallet.py3
-rwxr-xr-xtest/lint/lint-circular-dependencies.sh3
-rwxr-xr-xtest/lint/lint-format-strings.py4
-rwxr-xr-xtest/lint/lint-locale-dependence.sh24
-rwxr-xr-xtest/lint/lint-python-dead-code.sh19
-rwxr-xr-xtest/lint/lint-rpc-help.sh24
-rw-r--r--test/sanitizer_suppressions/tsan24
-rw-r--r--test/sanitizer_suppressions/ubsan36
-rwxr-xr-xtest/util/rpcauth-test.py6
46 files changed, 1054 insertions, 174 deletions
diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json
index dd40a096de..57f2608ee9 100644
--- a/test/functional/data/rpc_psbt.json
+++ b/test/functional/data/rpc_psbt.json
@@ -17,7 +17,8 @@
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
- "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A"
+ "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
+ "cHNidP8BAHMCAAAAAbiWoY6pOQepFsEGhUPXaulX9rvye2NH+NrdlAHg+WgpAQAAAAD/////AkBLTAAAAAAAF6kUqWwXCcLM5BN2zoNqMNT5qMlIi7+HQEtMAAAAAAAXqRSVF/in2XNxAlN1OSxkyp0z+Wtg2YcAAAAAAAEBIBNssgAAAAAAF6kUamsvautR8hRlMRY6OKNTx03DK96HAQcXFgAUo8u1LWpHprjt/uENAwBpGZD0UH0BCGsCRzBEAiAONfH3DYiw67ZbylrsxCF/XXpVwyWBRgofyRbPslzvwgIgIKCsWw5sHSIPh1icNvcVLZLHWj6NA7Dk+4Os2pOnMbQBIQPGStfYHPtyhpV7zIWtn0Q4GXv5gK1zy/tnJ+cBXu4iiwABABYAFMwmJQEz+HDpBEEabxJ5PogPsqZRAAEAFgAUyCrGc3h3FYCmiIspbv2pSTKZ5jU"
],
"valid" : [
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA",
diff --git a/test/functional/example_test.py b/test/functional/example_test.py
index 3f15367a75..be3544ee74 100755
--- a/test/functional/example_test.py
+++ b/test/functional/example_test.py
@@ -164,13 +164,13 @@ class ExampleTest(BitcoinTestFramework):
self.tip = int(self.nodes[0].getbestblockhash(), 16)
self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
- height = 1
+ height = self.nodes[0].getblockcount()
for i in range(10):
# Use the mininode and blocktools functionality to manually build a block
# Calling the generate() rpc is easier, but this allows us to exactly
# control the blocks and transactions.
- block = create_block(self.tip, create_coinbase(height), self.block_time)
+ block = create_block(self.tip, create_coinbase(height+1), self.block_time)
block.solve()
block_message = msg_block(block)
# Send message is used to send a P2P message to the node over our P2PInterface
diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py
index 628cefb76d..e50f67a345 100755
--- a/test/functional/feature_block.py
+++ b/test/functional/feature_block.py
@@ -579,14 +579,14 @@ class FullBlockTest(BitcoinTestFramework):
while b47.sha256 < target:
b47.nNonce += 1
b47.rehash()
- self.sync_blocks([b47], False, request_block=False)
+ self.sync_blocks([b47], False, force_send=True, reject_reason='high-hash')
self.log.info("Reject a block with a timestamp >2 hours in the future")
self.move_tip(44)
b48 = self.next_block(48, solve=False)
b48.nTime = int(time.time()) + 60 * 60 * 3
b48.solve()
- self.sync_blocks([b48], False, request_block=False)
+ self.sync_blocks([b48], False, force_send=True, reject_reason='time-too-new')
self.log.info("Reject a block with invalid merkle hash")
self.move_tip(44)
@@ -600,7 +600,7 @@ class FullBlockTest(BitcoinTestFramework):
b50 = self.next_block(50)
b50.nBits = b50.nBits - 1
b50.solve()
- self.sync_blocks([b50], False, request_block=False, reconnect=True)
+ self.sync_blocks([b50], False, force_send=True, reject_reason='bad-diffbits', reconnect=True)
self.log.info("Reject a block with two coinbase transactions")
self.move_tip(44)
@@ -630,7 +630,7 @@ class FullBlockTest(BitcoinTestFramework):
b54 = self.next_block(54, spend=out[15])
b54.nTime = b35.nTime - 1
b54.solve()
- self.sync_blocks([b54], False, request_block=False)
+ self.sync_blocks([b54], False, force_send=True, reject_reason='time-too-old')
# valid timestamp
self.move_tip(53)
@@ -824,7 +824,7 @@ class FullBlockTest(BitcoinTestFramework):
tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0)))
b64a = self.update_block("64a", [tx])
assert_equal(len(b64a.serialize()), MAX_BLOCK_BASE_SIZE + 8)
- self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize():')
+ self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize()')
# bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently
# resend the header message, it won't send us the getdata message again. Just
@@ -1078,11 +1078,11 @@ class FullBlockTest(BitcoinTestFramework):
self.move_tip(77)
b80 = self.next_block(80, spend=out[25])
- self.sync_blocks([b80], False, request_block=False)
+ self.sync_blocks([b80], False, force_send=True)
self.save_spendable_output()
b81 = self.next_block(81, spend=out[26])
- self.sync_blocks([b81], False, request_block=False) # other chain is same length
+ self.sync_blocks([b81], False, force_send=True) # other chain is same length
self.save_spendable_output()
b82 = self.next_block(82, spend=out[27])
@@ -1189,7 +1189,7 @@ class FullBlockTest(BitcoinTestFramework):
blocks2 = []
for i in range(89, LARGE_REORG_SIZE + 89):
blocks2.append(self.next_block("alt" + str(i)))
- self.sync_blocks(blocks2, False, request_block=False)
+ self.sync_blocks(blocks2, False, force_send=True)
# extend alt chain to trigger re-org
block = self.next_block("alt" + str(chain1_tip + 1))
@@ -1198,7 +1198,7 @@ class FullBlockTest(BitcoinTestFramework):
# ... and re-org back to the first chain
self.move_tip(chain1_tip)
block = self.next_block(chain1_tip + 1)
- self.sync_blocks([block], False, request_block=False)
+ self.sync_blocks([block], False, force_send=True)
block = self.next_block(chain1_tip + 2)
self.sync_blocks([block], True, timeout=180)
@@ -1309,14 +1309,15 @@ class FullBlockTest(BitcoinTestFramework):
self.nodes[0].disconnect_p2ps()
self.bootstrap_p2p()
- def sync_blocks(self, blocks, success=True, reject_reason=None, request_block=True, reconnect=False, timeout=60):
+ def sync_blocks(self, blocks, success=True, reject_reason=None, force_send=False, reconnect=False, timeout=60):
"""Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block.
Call with success = False if the tip shouldn't advance to the most recent block."""
- self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, request_block=request_block, timeout=timeout, expect_disconnect=reconnect)
+ self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect)
if reconnect:
self.reconnect_p2p()
+
if __name__ == '__main__':
FullBlockTest().main()
diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py
index f84b08a199..302a5ec1cb 100755
--- a/test/functional/feature_cltv.py
+++ b/test/functional/feature_cltv.py
@@ -25,7 +25,6 @@ CLTV_HEIGHT = 1351
# Reject codes that we might receive in this test
REJECT_INVALID = 16
-REJECT_OBSOLETE = 17
REJECT_NONSTANDARD = 64
def cltv_invalidate(tx):
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 492772d5e3..d87eabaa6d 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -30,6 +30,15 @@ class ConfArgsTest(BitcoinTestFramework):
self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
+ conf.write('server=1\nrpcuser=someuser\nrpcpassword=some#pass')
+ self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided')
+
+ with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
+ conf.write('testnot.datadir=1\n[testnet]\n')
+ self.restart_node(0)
+ self.nodes[0].stop_node(expected_stderr='Warning: Section [testnet] is not recognized.' + os.linesep + 'Warning: Section [testnot] is not recognized.')
+
+ with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('') # clear
def run_test(self):
diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py
index ae1eacf2d7..70d67aa53a 100755
--- a/test/functional/feature_dbcrash.py
+++ b/test/functional/feature_dbcrash.py
@@ -69,6 +69,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
def setup_network(self):
self.add_nodes(self.num_nodes, extra_args=self.extra_args)
self.start_nodes()
+ self.import_deterministic_coinbase_privkeys()
# Leave them unconnected, we'll use submitblock directly in this test
def restart_node(self, node_index, expected_tip):
diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py
index 16272877e7..9cbc1b39bd 100755
--- a/test/functional/feature_dersig.py
+++ b/test/functional/feature_dersig.py
@@ -22,7 +22,6 @@ DERSIG_HEIGHT = 1251
# Reject codes that we might receive in this test
REJECT_INVALID = 16
-REJECT_OBSOLETE = 17
REJECT_NONSTANDARD = 64
# A canonical signature consists of:
diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py
index aaab4279b5..b68e46adbc 100755
--- a/test/functional/feature_fee_estimation.py
+++ b/test/functional/feature_fee_estimation.py
@@ -144,6 +144,9 @@ class EstimateFeeTest(BitcoinTestFramework):
# (68k weight is room enough for 120 or so transactions)
# Node2 is a stingy miner, that
# produces too small blocks (room for only 55 or so transactions)
+ self.start_nodes()
+ self.import_deterministic_coinbase_privkeys()
+ self.stop_nodes()
def transact_and_mine(self, numblocks, mining_node):
min_fee = Decimal("0.00001")
@@ -171,11 +174,6 @@ class EstimateFeeTest(BitcoinTestFramework):
newmem.append(utx)
self.memutxo = newmem
- def import_deterministic_coinbase_privkeys(self):
- self.start_nodes()
- super().import_deterministic_coinbase_privkeys()
- self.stop_nodes()
-
def run_test(self):
self.log.info("This test is time consuming, please be patient")
self.log.info("Splitting inputs so we can generate tx's")
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index 772151dc4b..c162f46d63 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -63,6 +63,8 @@ class PruneTest(BitcoinTestFramework):
def setup_nodes(self):
self.add_nodes(self.num_nodes, self.extra_args)
self.start_nodes()
+ for n in self.nodes:
+ n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase', rescan=False)
def create_big_chain(self):
# Start by creating some coinbases we can spend later
@@ -247,7 +249,7 @@ class PruneTest(BitcoinTestFramework):
return index
def prune(index, expected_ret=None):
- ret = node.pruneblockchain(height(index))
+ ret = node.pruneblockchain(height=height(index))
# Check the return value. When use_timestamp is True, just check
# that the return value is less than or equal to the expected
# value, because when more than one block is generated per second,
diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py
new file mode 100755
index 0000000000..e3d7b0655d
--- /dev/null
+++ b/test/functional/interface_rpc.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Tests some generic aspects of the RPC interface."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+class RPCInterfaceTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def test_batch_request(self):
+ self.log.info("Testing basic JSON-RPC batch request...")
+
+ results = self.nodes[0].batch([
+ # A basic request that will work fine.
+ {"method": "getblockcount", "id": 1},
+ # Request that will fail. The whole batch request should still
+ # work fine.
+ {"method": "invalidmethod", "id": 2},
+ # Another call that should succeed.
+ {"method": "getbestblockhash", "id": 3},
+ ])
+
+ result_by_id = {}
+ for res in results:
+ result_by_id[res["id"]] = res
+
+ assert_equal(result_by_id[1]['error'], None)
+ assert_equal(result_by_id[1]['result'], 0)
+
+ assert_equal(result_by_id[2]['error']['code'], -32601)
+ assert_equal(result_by_id[2]['result'], None)
+
+ assert_equal(result_by_id[3]['error'], None)
+ assert result_by_id[3]['result'] is not None
+
+ def run_test(self):
+ self.test_batch_request()
+
+
+if __name__ == '__main__':
+ RPCInterfaceTest().main()
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 48136a0108..94fea37090 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -69,6 +69,7 @@ class ZMQTest (BitcoinTestFramework):
]
self.add_nodes(self.num_nodes, self.extra_args)
self.start_nodes()
+ self.import_deterministic_coinbase_privkeys()
def run_test(self):
try:
@@ -121,10 +122,10 @@ class ZMQTest (BitcoinTestFramework):
self.log.info("Test the getzmqnotifications RPC")
assert_equal(self.nodes[0].getzmqnotifications(), [
- {"type": "pubhashblock", "address": ADDRESS},
- {"type": "pubhashtx", "address": ADDRESS},
- {"type": "pubrawblock", "address": ADDRESS},
- {"type": "pubrawtx", "address": ADDRESS},
+ {"type": "pubhashblock", "address": ADDRESS, "hwm": 1000},
+ {"type": "pubhashtx", "address": ADDRESS, "hwm": 1000},
+ {"type": "pubrawblock", "address": ADDRESS, "hwm": 1000},
+ {"type": "pubrawtx", "address": ADDRESS, "hwm": 1000},
])
assert_equal(self.nodes[1].getzmqnotifications(), [])
diff --git a/test/functional/mempool_resurrect.py b/test/functional/mempool_resurrect.py
index d035ca907a..845beb551e 100755
--- a/test/functional/mempool_resurrect.py
+++ b/test/functional/mempool_resurrect.py
@@ -47,12 +47,11 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
tx = self.nodes[0].gettransaction(txid)
assert(tx["confirmations"] > 0)
- # Use invalidateblock to re-org back; all transactions should
- # end up unconfirmed and back in the mempool
+ # Use invalidateblock to re-org back
for node in self.nodes:
node.invalidateblock(blocks[0])
- # mempool should be empty, all txns confirmed
+ # All txns should be back in mempool with 0 confirmations
assert_equal(set(self.nodes[0].getrawmempool()), set(spends1_id+spends2_id))
for txid in spends1_id+spends2_id:
tx = self.nodes[0].gettransaction(txid)
diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py
index ff55ea5528..9f01be0646 100755
--- a/test/functional/mining_basic.py
+++ b/test/functional/mining_basic.py
@@ -30,9 +30,10 @@ from test_framework.util import (
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'})
+ rsp = node.getblocktemplate(template_request={'data': b2x(block.serialize()), 'mode': 'proposal'})
assert_equal(rsp, expect)
+
class MiningTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py
index 67f24d6bff..1b11a2a294 100755
--- a/test/functional/p2p_disconnect_ban.py
+++ b/test/functional/p2p_disconnect_ban.py
@@ -22,7 +22,7 @@ class DisconnectBanTest(BitcoinTestFramework):
self.log.info("setban: successfully ban single IP address")
assert_equal(len(self.nodes[1].getpeerinfo()), 2) # node1 should have 2 connections to node0 at this point
- self.nodes[1].setban("127.0.0.1", "add")
+ self.nodes[1].setban(subnet="127.0.0.1", command="add")
wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 0, timeout=10)
assert_equal(len(self.nodes[1].getpeerinfo()), 0) # all nodes must be disconnected at this point
assert_equal(len(self.nodes[1].listbanned()), 1)
diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py
index 0678b1a651..1e0b876593 100755
--- a/test/functional/p2p_invalid_block.py
+++ b/test/functional/p2p_invalid_block.py
@@ -77,9 +77,9 @@ class InvalidBlockRequestTest(BitcoinTestFramework):
block2.vtx.append(tx2)
assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root())
assert_equal(orig_hash, block2.rehash())
- assert(block2_orig.vtx != block2.vtx)
+ assert block2_orig.vtx != block2.vtx
- node.p2p.send_blocks_and_test([block2], node, success=False, request_block=False, reject_reason='bad-txns-duplicate')
+ node.p2p.send_blocks_and_test([block2], node, success=False, reject_reason='bad-txns-duplicate')
# Check transactions for duplicate inputs
self.log.info("Test duplicate input block.")
@@ -89,7 +89,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework):
block2_orig.hashMerkleRoot = block2_orig.calc_merkle_root()
block2_orig.rehash()
block2_orig.solve()
- node.p2p.send_blocks_and_test([block2_orig], node, success=False, request_block=False, reject_reason='bad-txns-inputs-duplicate')
+ node.p2p.send_blocks_and_test([block2_orig], node, success=False, reject_reason='bad-txns-inputs-duplicate')
self.log.info("Test very broken block.")
@@ -102,7 +102,8 @@ class InvalidBlockRequestTest(BitcoinTestFramework):
block3.rehash()
block3.solve()
- node.p2p.send_blocks_and_test([block3], node, success=False, request_block=False, reject_reason='bad-cb-amount')
+ node.p2p.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount')
+
if __name__ == '__main__':
InvalidBlockRequestTest().main()
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
new file mode 100755
index 0000000000..3ee67980a4
--- /dev/null
+++ b/test/functional/p2p_invalid_messages.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2018 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test node responses to invalid network messages."""
+import struct
+
+from test_framework import messages
+from test_framework.mininode import P2PDataStore
+from test_framework.test_framework import BitcoinTestFramework
+
+
+class msg_unrecognized:
+ """Nonsensical message. Modeled after similar types in test_framework.messages."""
+
+ command = b'badmsg'
+
+ def __init__(self, str_data):
+ self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data
+
+ def serialize(self):
+ return messages.ser_string(self.str_data)
+
+ def __repr__(self):
+ return "{}(data={})".format(self.command, self.str_data)
+
+
+class msg_nametoolong(msg_unrecognized):
+
+ command = b'thisnameiswayyyyyyyyytoolong'
+
+
+class InvalidMessagesTest(BitcoinTestFramework):
+
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def run_test(self):
+ """
+ 0. Send a bunch of large (4MB) messages of an unrecognized type. Check to see
+ that it isn't an effective DoS against the node.
+
+ 1. Send an oversized (4MB+) message and check that we're disconnected.
+
+ 2. Send a few messages with an incorrect data size in the header, ensure the
+ messages are ignored.
+
+ 3. Send an unrecognized message with a command name longer than 12 characters.
+
+ """
+ node = self.nodes[0]
+ self.node = node
+ node.add_p2p_connection(P2PDataStore())
+ conn2 = node.add_p2p_connection(P2PDataStore())
+
+ msg_limit = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH
+ valid_data_limit = msg_limit - 5 # Account for the 4-byte length prefix
+
+ #
+ # 0.
+ #
+ # Send as large a message as is valid, ensure we aren't disconnected but
+ # also can't exhaust resources.
+ #
+ msg_at_size = msg_unrecognized("b" * valid_data_limit)
+ assert len(msg_at_size.serialize()) == msg_limit
+
+ with node.assert_memory_usage_stable(increase_allowed=0.5):
+ self.log.info(
+ "Sending a bunch of large, junk messages to test "
+ "memory exhaustion. May take a bit...")
+
+ # Run a bunch of times to test for memory exhaustion.
+ for _ in range(80):
+ node.p2p.send_message(msg_at_size)
+
+ # Check that, even though the node is being hammered by nonsense from one
+ # connection, it can still service other peers in a timely way.
+ for _ in range(20):
+ conn2.sync_with_ping(timeout=2)
+
+ # Peer 1, despite serving up a bunch of nonsense, should still be connected.
+ self.log.info("Waiting for node to drop junk messages.")
+ node.p2p.sync_with_ping(timeout=30)
+ assert node.p2p.is_connected
+
+ #
+ # 1.
+ #
+ # Send an oversized message, ensure we're disconnected.
+ #
+ msg_over_size = msg_unrecognized("b" * (valid_data_limit + 1))
+ assert len(msg_over_size.serialize()) == (msg_limit + 1)
+
+ with node.assert_debug_log(["Oversized message from peer=0, disconnecting"]):
+ # An unknown message type (or *any* message type) over
+ # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect.
+ node.p2p.send_message(msg_over_size)
+ node.p2p.wait_for_disconnect(timeout=4)
+
+ node.disconnect_p2ps()
+ conn = node.add_p2p_connection(P2PDataStore())
+ conn.wait_for_verack()
+
+ #
+ # 2.
+ #
+ # Send messages with an incorrect data size in the header.
+ #
+ actual_size = 100
+ msg = msg_unrecognized("b" * actual_size)
+
+ # TODO: handle larger-than cases. I haven't been able to pin down what behavior to expect.
+ for wrong_size in (2, 77, 78, 79):
+ self.log.info("Sending a message with incorrect size of {}".format(wrong_size))
+
+ # Unmodified message should submit okay.
+ node.p2p.send_and_ping(msg)
+
+ # A message lying about its data size results in a disconnect when the incorrect
+ # data size is less than the actual size.
+ #
+ # TODO: why does behavior change at 78 bytes?
+ #
+ node.p2p.send_raw_message(self._tweak_msg_data_size(msg, wrong_size))
+
+ # For some reason unknown to me, we sometimes have to push additional data to the
+ # peer in order for it to realize a disconnect.
+ try:
+ node.p2p.send_message(messages.msg_ping(nonce=123123))
+ except IOError:
+ pass
+
+ node.p2p.wait_for_disconnect(timeout=10)
+ node.disconnect_p2ps()
+ node.add_p2p_connection(P2PDataStore())
+
+ #
+ # 3.
+ #
+ # Send a message with a too-long command name.
+ #
+ node.p2p.send_message(msg_nametoolong("foobar"))
+ node.p2p.wait_for_disconnect(timeout=4)
+
+ # Node is still up.
+ conn = node.add_p2p_connection(P2PDataStore())
+ conn.sync_with_ping()
+
+
+ def _tweak_msg_data_size(self, message, wrong_size):
+ """
+ Return a raw message based on another message but with an incorrect data size in
+ the message header.
+ """
+ raw_msg = self.node.p2p.build_message(message)
+
+ bad_size_bytes = struct.pack("<I", wrong_size)
+ num_header_bytes_before_size = 4 + 12
+
+ # Replace the correct data size in the message with an incorrect one.
+ raw_msg_with_wrong_size = (
+ raw_msg[:num_header_bytes_before_size] +
+ bad_size_bytes +
+ raw_msg[(num_header_bytes_before_size + len(bad_size_bytes)):]
+ )
+ assert len(raw_msg) == len(raw_msg_with_wrong_size)
+
+ return raw_msg_with_wrong_size
+
+
+
+if __name__ == '__main__':
+ InvalidMessagesTest().main()
diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py
new file mode 100755
index 0000000000..dc4d475b2d
--- /dev/null
+++ b/test/functional/p2p_leak_tx.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+# Copyright (c) 2017-2018 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 that we don't leak txs to inbound peers that we haven't yet announced to"""
+
+from test_framework.messages import msg_getdata, CInv
+from test_framework.mininode import P2PDataStore
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+)
+
+
+class P2PNode(P2PDataStore):
+ def on_inv(self, msg):
+ pass
+
+
+class P2PLeakTxTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def run_test(self):
+ gen_node = self.nodes[0] # The block and tx generating node
+ gen_node.generate(1)
+
+ inbound_peer = self.nodes[0].add_p2p_connection(P2PNode()) # An "attacking" inbound peer
+
+ MAX_REPEATS = 100
+ self.log.info("Running test up to {} times.".format(MAX_REPEATS))
+ for i in range(MAX_REPEATS):
+ self.log.info('Run repeat {}'.format(i + 1))
+ txid = gen_node.sendtoaddress(gen_node.getnewaddress(), 0.01)
+
+ want_tx = msg_getdata()
+ want_tx.inv.append(CInv(t=1, h=int(txid, 16)))
+ inbound_peer.last_message.pop('notfound', None)
+ inbound_peer.send_message(want_tx)
+ inbound_peer.sync_with_ping()
+
+ if inbound_peer.last_message.get('notfound'):
+ self.log.debug('tx {} was not yet announced to us.'.format(txid))
+ self.log.debug("node has responded with a notfound message. End test.")
+ assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(txid, 16))
+ inbound_peer.last_message.pop('notfound')
+ break
+ else:
+ self.log.debug('tx {} was already announced to us. Try test again.'.format(txid))
+ assert int(txid, 16) in [inv.hash for inv in inbound_peer.last_message['inv'].inv]
+
+
+if __name__ == '__main__':
+ P2PLeakTxTest().main()
diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py
index ec3d336dc1..359880506e 100755
--- a/test/functional/p2p_node_network_limited.py
+++ b/test/functional/p2p_node_network_limited.py
@@ -43,8 +43,8 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
disconnect_nodes(self.nodes[1], 2)
def setup_network(self):
- super(NodeNetworkLimitedTest, self).setup_network()
- self.disconnect_all()
+ self.add_nodes(self.num_nodes, self.extra_args)
+ self.start_nodes()
def run_test(self):
node = self.nodes[0].add_p2p_connection(P2PIgnoreInv())
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 92b690176d..31e60f1cea 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -133,7 +133,7 @@ class BlockchainTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Block is not in main chain", self.nodes[0].getchaintxstats, blockhash=blockhash)
self.nodes[0].reconsiderblock(blockhash)
- chaintxstats = self.nodes[0].getchaintxstats(1)
+ chaintxstats = self.nodes[0].getchaintxstats(nblocks=1)
# 200 txs plus genesis tx
assert_equal(chaintxstats['txcount'], 201)
# tx rate should be 1 per 10 minutes, or 1/600
@@ -211,7 +211,7 @@ class BlockchainTest(BitcoinTestFramework):
besthash = node.getbestblockhash()
secondbesthash = node.getblockhash(199)
- header = node.getblockheader(besthash)
+ header = node.getblockheader(blockhash=besthash)
assert_equal(header['hash'], besthash)
assert_equal(header['height'], 200)
@@ -287,7 +287,7 @@ class BlockchainTest(BitcoinTestFramework):
def assert_waitforheight(height, timeout=2):
assert_equal(
- node.waitforblockheight(height, timeout)['height'],
+ node.waitforblockheight(height=height, timeout=timeout)['height'],
current_height)
assert_waitforheight(0)
diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py
index be096af892..78d6e78aed 100755
--- a/test/functional/rpc_help.py
+++ b/test/functional/rpc_help.py
@@ -7,12 +7,18 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error
+import os
+
class HelpRpcTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
def run_test(self):
+ self.test_categories()
+ self.dump_help()
+
+ def test_categories(self):
node = self.nodes[0]
# wrong argument count
@@ -37,6 +43,15 @@ class HelpRpcTest(BitcoinTestFramework):
assert_equal(titles, components)
+ def dump_help(self):
+ dump_dir = os.path.join(self.options.tmpdir, 'rpc_help_dump')
+ os.mkdir(dump_dir)
+ calls = [line.split(' ', 1)[0] for line in self.nodes[0].help().splitlines() if line and not line.startswith('==')]
+ for call in calls:
+ with open(os.path.join(dump_dir, call), 'w', encoding='utf-8') as f:
+ # Make sure the node can generate the help at runtime without crashing
+ f.write(self.nodes[0].help(call))
+
if __name__ == '__main__':
HelpRpcTest().main()
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 1e525214fa..0affddcf05 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -74,12 +74,12 @@ class NetTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
- self.nodes[0].setnetworkactive(False)
+ self.nodes[0].setnetworkactive(state=False)
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], False)
# Wait a bit for all sockets to close
wait_until(lambda: self.nodes[0].getnetworkinfo()['connections'] == 0, timeout=3)
- self.nodes[0].setnetworkactive(True)
+ self.nodes[0].setnetworkactive(state=True)
connect_nodes_bi(self.nodes, 0, 1)
assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True)
assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2)
@@ -88,7 +88,7 @@ class NetTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getaddednodeinfo(), [])
# add a node (node2) to node0
ip_port = "127.0.0.1:{}".format(p2p_port(2))
- self.nodes[0].addnode(ip_port, 'add')
+ self.nodes[0].addnode(node=ip_port, command='add')
# check that the node has indeed been added
added_nodes = self.nodes[0].getaddednodeinfo(ip_port)
assert_equal(len(added_nodes), 1)
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 54dc871448..04d9bb65a6 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -6,7 +6,7 @@
"""
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, assert_raises_rpc_error, find_output
+from test_framework.util import assert_equal, assert_raises_rpc_error, find_output, disconnect_nodes, connect_nodes_bi, sync_blocks
import json
import os
@@ -23,6 +23,45 @@ class PSBTTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ def test_utxo_conversion(self):
+ mining_node = self.nodes[2]
+ offline_node = self.nodes[0]
+ online_node = self.nodes[1]
+
+ # Disconnect offline node from others
+ disconnect_nodes(offline_node, 1)
+ disconnect_nodes(online_node, 0)
+ disconnect_nodes(offline_node, 2)
+ disconnect_nodes(mining_node, 0)
+
+ # Mine a transaction that credits the offline address
+ offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit")
+ online_addr = online_node.getnewaddress(address_type="p2sh-segwit")
+ online_node.importaddress(offline_addr, "", False)
+ mining_node.sendtoaddress(address=offline_addr, amount=1.0)
+ mining_node.generate(nblocks=1)
+ sync_blocks([mining_node, online_node])
+
+ # Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO)
+ utxos = online_node.listunspent(addresses=[offline_addr])
+ raw = online_node.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
+ psbt = online_node.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
+ assert("non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0])
+
+ # Have the offline node sign the PSBT (which will update the UTXO to segwit)
+ signed_psbt = offline_node.walletprocesspsbt(psbt)["psbt"]
+ assert("witness_utxo" in mining_node.decodepsbt(signed_psbt)["inputs"][0])
+
+ # Make sure we can mine the resulting transaction
+ txid = mining_node.sendrawtransaction(mining_node.finalizepsbt(signed_psbt)["hex"])
+ mining_node.generate(1)
+ sync_blocks([mining_node, online_node])
+ assert_equal(online_node.gettxout(txid,0)["confirmations"], 1)
+
+ # Reconnect
+ connect_nodes_bi(self.nodes, 0, 1)
+ connect_nodes_bi(self.nodes, 0, 2)
+
def run_test(self):
# Create and fund a raw tx for sending 10 BTC
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']
@@ -107,6 +146,9 @@ class PSBTTest(BitcoinTestFramework):
# Make sure that a psbt with signatures cannot be converted
signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])
assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'])
+ assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False)
+ # Unless we allow it to convert and strip signatures
+ self.nodes[0].converttopsbt(signedtx['hex'], True)
# Explicitly allow converting non-empty txs
new_psbt = self.nodes[0].converttopsbt(rawtx['hex'])
@@ -168,6 +210,13 @@ class PSBTTest(BitcoinTestFramework):
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
assert_equal(decoded_psbt["tx"]["locktime"], 0)
+ # Regression test for 14473 (mishandling of already-signed witness transaction):
+ psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
+ complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"])
+ double_processed_psbt = self.nodes[0].walletprocesspsbt(complete_psbt["psbt"])
+ assert_equal(complete_psbt, double_processed_psbt)
+ # We don't care about the decode result, but decoding must succeed.
+ self.nodes[0].decodepsbt(double_processed_psbt["psbt"])
# BIP 174 Test Vectors
@@ -224,6 +273,16 @@ class PSBTTest(BitcoinTestFramework):
extracted = self.nodes[2].finalizepsbt(extractor['extract'], True)['hex']
assert_equal(extracted, extractor['result'])
+ # Unload extra wallets
+ for i, signer in enumerate(signers):
+ self.nodes[2].unloadwallet("wallet{}".format(i))
+
+ self.test_utxo_conversion()
+
+ # Test that psbts with p2pkh outputs are created properly
+ p2pkh = self.nodes[0].getnewaddress(address_type='legacy')
+ psbt = self.nodes[1].walletcreatefundedpsbt([], [{p2pkh : 1}], 0, {"includeWatching" : True}, True)
+ self.nodes[0].decodepsbt(psbt['psbt'])
if __name__ == '__main__':
PSBTTest().main()
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index 35004fb588..81cce1167b 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -169,7 +169,7 @@ def get_legacy_sigopcount_tx(tx, accurate=True):
return count
def witness_script(use_p2wsh, pubkey):
- """Create a scriptPubKey for a pay-to-wtiness TxOut.
+ """Create a scriptPubKey for a pay-to-witness TxOut.
This is either a P2WPKH output for the given pubkey, or a P2WSH output of a
1-of-1 multisig for the given pubkey. Returns the hex encoding of the
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 8e9372767d..c72cb8835c 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -35,7 +35,6 @@ MY_VERSION = 70014 # past bip-31 for ping/pong
MY_SUBVERSION = b"/python-mininode-tester:0.0.3/"
MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37)
-MAX_INV_SZ = 50000
MAX_LOCATOR_SZ = 101
MAX_BLOCK_BASE_SIZE = 1000000
@@ -58,9 +57,6 @@ MSG_TYPE_MASK = 0xffffffff >> 2
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))
@@ -887,13 +883,12 @@ class BlockTransactions:
class CPartialMerkleTree:
- __slots__ = ("fBad", "nTransactions", "vBits", "vHash")
+ __slots__ = ("nTransactions", "vBits", "vHash")
def __init__(self):
self.nTransactions = 0
self.vHash = []
self.vBits = []
- self.fBad = False
def deserialize(self, f):
self.nTransactions = struct.unpack("<i", f.read(4))[0]
@@ -1232,6 +1227,23 @@ class msg_mempool:
return "msg_mempool()"
+class msg_notfound:
+ __slots__ = ("vec", )
+ command = b"notfound"
+
+ def __init__(self, vec=None):
+ self.vec = vec or []
+
+ def deserialize(self, f):
+ self.vec = deser_vector(f, CInv)
+
+ def serialize(self):
+ return ser_vector(self.vec)
+
+ def __repr__(self):
+ return "msg_notfound(vec=%s)" % (repr(self.vec))
+
+
class msg_sendheaders:
__slots__ = ()
command = b"sendheaders"
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index 6864e8a838..ca5734d67d 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -21,7 +21,38 @@ import struct
import sys
import threading
-from test_framework.messages import CBlockHeader, MIN_VERSION_SUPPORTED, msg_addr, msg_block, MSG_BLOCK, msg_blocktxn, msg_cmpctblock, msg_feefilter, msg_getaddr, msg_getblocks, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_mempool, msg_ping, msg_pong, msg_reject, msg_sendcmpct, msg_sendheaders, msg_tx, MSG_TX, MSG_TYPE_MASK, msg_verack, msg_version, NODE_NETWORK, NODE_WITNESS, sha256
+from test_framework.messages import (
+ CBlockHeader,
+ MIN_VERSION_SUPPORTED,
+ msg_addr,
+ msg_block,
+ MSG_BLOCK,
+ msg_blocktxn,
+ msg_cmpctblock,
+ msg_feefilter,
+ msg_getaddr,
+ msg_getblocks,
+ msg_getblocktxn,
+ msg_getdata,
+ msg_getheaders,
+ msg_headers,
+ msg_inv,
+ msg_mempool,
+ msg_notfound,
+ msg_ping,
+ msg_pong,
+ msg_reject,
+ msg_sendcmpct,
+ msg_sendheaders,
+ msg_tx,
+ MSG_TX,
+ MSG_TYPE_MASK,
+ msg_verack,
+ msg_version,
+ NODE_NETWORK,
+ NODE_WITNESS,
+ sha256,
+)
from test_framework.util import wait_until
logger = logging.getLogger("TestFramework.mininode")
@@ -40,6 +71,7 @@ MESSAGEMAP = {
b"headers": msg_headers,
b"inv": msg_inv,
b"mempool": msg_mempool,
+ b"notfound": msg_notfound,
b"ping": msg_ping,
b"pong": msg_pong,
b"reject": msg_reject,
@@ -175,10 +207,13 @@ class P2PConnection(asyncio.Protocol):
This method takes a P2P payload, builds the P2P header and adds
the message to the send buffer to be sent over the socket."""
+ tmsg = self.build_message(message)
+ self._log_message("send", message)
+ return self.send_raw_message(tmsg)
+
+ def send_raw_message(self, raw_message_bytes):
if not self.is_connected:
raise IOError('Not connected')
- self._log_message("send", message)
- tmsg = self._build_message(message)
def maybe_write():
if not self._transport:
@@ -188,12 +223,12 @@ class P2PConnection(asyncio.Protocol):
# Python 3.4 versions.
if hasattr(self._transport, 'is_closing') and self._transport.is_closing():
return
- self._transport.write(tmsg)
+ self._transport.write(raw_message_bytes)
NetworkThread.network_event_loop.call_soon_threadsafe(maybe_write)
# Class utility methods
- def _build_message(self, message):
+ def build_message(self, message):
"""Build a serialized P2P message"""
command = message.command
data = message.serialize()
@@ -295,6 +330,7 @@ class P2PInterface(P2PConnection):
def on_getheaders(self, message): pass
def on_headers(self, message): pass
def on_mempool(self, message): pass
+ def on_notfound(self, message): pass
def on_pong(self, message): pass
def on_reject(self, message): pass
def on_sendcmpct(self, message): pass
@@ -313,7 +349,7 @@ class P2PInterface(P2PConnection):
self.send_message(msg_pong(message.nonce))
def on_verack(self, message):
- self.verack_received = True
+ pass
def on_version(self, message):
assert message.nVersion >= MIN_VERSION_SUPPORTED, "Version {} received. Test framework only supports versions greater than {}".format(message.nVersion, MIN_VERSION_SUPPORTED)
@@ -376,9 +412,9 @@ class P2PInterface(P2PConnection):
# Message sending helper functions
- def send_and_ping(self, message):
+ def send_and_ping(self, message, timeout=60):
self.send_message(message)
- self.sync_with_ping()
+ self.sync_with_ping(timeout=timeout)
# Sync up with the node
def sync_with_ping(self, timeout=60):
@@ -475,14 +511,14 @@ class P2PDataStore(P2PInterface):
if response is not None:
self.send_message(response)
- def send_blocks_and_test(self, blocks, node, *, success=True, request_block=True, reject_reason=None, expect_disconnect=False, timeout=60):
+ def send_blocks_and_test(self, blocks, node, *, success=True, force_send=False, reject_reason=None, expect_disconnect=False, timeout=60):
"""Send blocks to test node and test whether the tip advances.
- add all blocks to our block_store
- send a headers message for the final block
- the on_getheaders handler will ensure that any getheaders are responded to
- - if request_block is True: wait for getdata for each of the blocks. The on_getdata handler will
- ensure that any getdata messages are responded to
+ - if force_send is False: wait for getdata for each of the blocks. The on_getdata handler will
+ ensure that any getdata messages are responded to. Otherwise send the full block unsolicited.
- if success is True: assert that the node's tip advances to the most recent block
- if success is False: assert that the node's tip doesn't advance
- if reject_reason is set: assert that the correct reject message is logged"""
@@ -494,9 +530,11 @@ class P2PDataStore(P2PInterface):
reject_reason = [reject_reason] if reject_reason else []
with node.assert_debug_log(expected_msgs=reject_reason):
- self.send_message(msg_headers([CBlockHeader(blocks[-1])]))
-
- if request_block:
+ if force_send:
+ for b in blocks:
+ self.send_message(msg_block(block=b))
+ else:
+ self.send_message(msg_headers([CBlockHeader(blocks[-1])]))
wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock)
if expect_disconnect:
diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py
index dd0f209268..a21c864e75 100644
--- a/test/functional/test_framework/socks5.py
+++ b/test/functional/test_framework/socks5.py
@@ -54,10 +54,9 @@ class Socks5Command():
return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self.cmd, self.atyp, self.addr, self.port, self.username, self.password)
class Socks5Connection():
- def __init__(self, serv, conn, peer):
+ def __init__(self, serv, conn):
self.serv = serv
self.conn = conn
- self.peer = peer
def handle(self):
"""Handle socks5 request according to RFC192."""
@@ -137,9 +136,9 @@ class Socks5Server():
def run(self):
while self.running:
- (sockconn, peer) = self.s.accept()
+ (sockconn, _) = self.s.accept()
if self.running:
- conn = Socks5Connection(self, sockconn, peer)
+ conn = Socks5Connection(self, sockconn)
thread = threading.Thread(None, conn.handle)
thread.daemon = True
thread.start()
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 7e2ec673df..44fc185e6d 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -168,7 +168,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.skip_test_if_missing_module()
self.setup_chain()
self.setup_network()
- self.import_deterministic_coinbase_privkeys()
self.run_test()
success = TestStatus.PASSED
except JSONRPCException as e:
@@ -261,11 +260,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
extra_args = self.extra_args
self.add_nodes(self.num_nodes, extra_args)
self.start_nodes()
+ self.import_deterministic_coinbase_privkeys()
def import_deterministic_coinbase_privkeys(self):
- if self.setup_clean_chain:
- return
-
for n in self.nodes:
try:
n.getwalletinfo()
@@ -273,7 +270,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
assert str(e).startswith('Method not found')
continue
- n.importprivkey(n.get_deterministic_priv_key().key)
+ n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase')
def run_test(self):
"""Tests must override this method to define test logic"""
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index cc1bfabbfa..27f99c259c 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -115,6 +115,25 @@ class TestNode():
]
return PRIV_KEYS[self.index]
+ def get_mem_rss_kilobytes(self):
+ """Get the memory usage (RSS) per `ps`.
+
+ Returns None if `ps` is unavailable.
+ """
+ assert self.running
+
+ try:
+ return int(subprocess.check_output(
+ ["ps", "h", "-o", "rss", "{}".format(self.process.pid)],
+ stderr=subprocess.DEVNULL).split()[-1])
+
+ # Avoid failing on platforms where ps isn't installed.
+ #
+ # We could later use something like `psutils` to work across platforms.
+ except (FileNotFoundError, subprocess.SubprocessError):
+ self.log.exception("Unable to get memory usage")
+ return None
+
def _node_msg(self, msg: str) -> str:
"""Return a modified msg that identifies this node by its index as a debugging aid."""
return "[node %d] %s" % (self.index, msg)
@@ -199,21 +218,6 @@ class TestNode():
def generate(self, nblocks, maxtries=1000000):
self.log.debug("TestNode.generate() dispatches `generate` call to `generatetoaddress`")
- # Try to import the node's deterministic private key. This is a no-op if the private key
- # has already been imported.
- try:
- self.rpc.importprivkey(privkey=self.get_deterministic_priv_key().key, label='coinbase', rescan=False)
- except JSONRPCException as e:
- # This may fail if:
- # - wallet is disabled ('Method not found')
- # - there are multiple wallets to import to ('Wallet file not specified')
- # - wallet is locked ('Error: Please enter the wallet passphrase with walletpassphrase first')
- # Just ignore those errors. We can make this tidier by importing the privkey during TestFramework.setup_nodes
- # TODO: tidy up deterministic privkey import.
- assert str(e).startswith('Method not found') or \
- str(e).startswith('Wallet file not specified') or \
- str(e).startswith('Error: Please enter the wallet passphrase with walletpassphrase first')
-
return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries)
def get_wallet_rpc(self, wallet_name):
@@ -286,6 +290,33 @@ class TestNode():
if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None:
self._raise_assertion_error('Expected message "{}" does not partially match log:\n\n{}\n\n'.format(expected_msg, print_log))
+ @contextlib.contextmanager
+ def assert_memory_usage_stable(self, *, increase_allowed=0.03):
+ """Context manager that allows the user to assert that a node's memory usage (RSS)
+ hasn't increased beyond some threshold percentage.
+
+ Args:
+ increase_allowed (float): the fractional increase in memory allowed until failure;
+ e.g. `0.12` for up to 12% increase allowed.
+ """
+ before_memory_usage = self.get_mem_rss_kilobytes()
+
+ yield
+
+ after_memory_usage = self.get_mem_rss_kilobytes()
+
+ if not (before_memory_usage and after_memory_usage):
+ self.log.warning("Unable to detect memory usage (RSS) - skipping memory check.")
+ return
+
+ perc_increase_memory_usage = (after_memory_usage / before_memory_usage) - 1
+
+ if perc_increase_memory_usage > increase_allowed:
+ self._raise_assertion_error(
+ "Memory usage increased over threshold of {:.3f}% from {} to {} ({:.3f}%)".format(
+ increase_allowed * 100, before_memory_usage, after_memory_usage,
+ perc_increase_memory_usage * 100))
+
def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, match=ErrorMatch.FULL_TEXT, *args, **kwargs):
"""Attempt to start the node and expect it to raise an error.
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index c1dcc46e23..da55a3a156 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -68,9 +68,6 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393):
TEST_EXIT_PASSED = 0
TEST_EXIT_SKIPPED = 77
-# 20 minutes represented in seconds
-TRAVIS_TIMEOUT_DURATION = 20 * 60
-
BASE_SCRIPTS = [
# Scripts that are run by the travis build process.
# Longest test should go first, to favor running tests in parallel
@@ -123,6 +120,7 @@ BASE_SCRIPTS = [
'wallet_disableprivatekeys.py',
'wallet_disableprivatekeys.py --usecli',
'interface_http.py',
+ 'interface_rpc.py',
'rpc_psbt.py',
'rpc_users.py',
'feature_proxy.py',
@@ -139,6 +137,7 @@ BASE_SCRIPTS = [
'mining_prioritisetransaction.py',
'p2p_invalid_locator.py',
'p2p_invalid_block.py',
+ 'p2p_invalid_messages.py',
'p2p_invalid_tx.py',
'feature_assumevalid.py',
'example_test.py',
@@ -152,10 +151,12 @@ BASE_SCRIPTS = [
'feature_versionbits_warning.py',
'rpc_preciousblock.py',
'wallet_importprunedfunds.py',
+ 'p2p_leak_tx.py',
'rpc_signmessage.py',
'feature_nulldummy.py',
'mempool_accept.py',
'wallet_import_rescan.py',
+ 'wallet_import_with_label.py',
'rpc_bind.py --ipv4',
'rpc_bind.py --ipv6',
'rpc_bind.py --nonloopback',
@@ -213,15 +214,16 @@ def main():
epilog='''
Help text and arguments for individual test script:''',
formatter_class=argparse.RawTextHelpFormatter)
- parser.add_argument('--combinedlogslen', '-c', type=int, default=0, help='print a combined log (of length n lines) from all test nodes and test framework to the console on failure.')
+ parser.add_argument('--combinedlogslen', '-c', type=int, default=0, metavar='n', help='On failure, print a log (of length n lines) to the console, combined from the test framework and all test nodes.')
parser.add_argument('--coverage', action='store_true', help='generate a basic coverage report for the RPC interface')
+ parser.add_argument('--ci', action='store_true', help='Run checks and code that are usually only enabled in a continuous integration environment')
parser.add_argument('--exclude', '-x', help='specify a comma-separated-list of scripts to exclude.')
parser.add_argument('--extended', action='store_true', help='run the extended test suite in addition to the basic tests')
parser.add_argument('--force', '-f', action='store_true', help='run tests even on platforms where they are disabled by default (e.g. windows).')
parser.add_argument('--help', '-h', '-?', action='store_true', help='print help text and exit')
parser.add_argument('--jobs', '-j', type=int, default=4, help='how many test scripts to run in parallel. Default=4.')
parser.add_argument('--keepcache', '-k', action='store_true', help='the default behavior is to flush the cache directory on startup. --keepcache retains the cache from the previous testrun.')
- parser.add_argument('--quiet', '-q', action='store_true', help='only print results summary and failure logs')
+ parser.add_argument('--quiet', '-q', action='store_true', help='only print dots, results summary and failure logs')
parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs")
parser.add_argument('--failfast', action='store_true', help='stop execution after the first test failure')
args, unknown_args = parser.parse_known_args()
@@ -305,26 +307,26 @@ def main():
subprocess.check_call([sys.executable, os.path.join(config["environment"]["SRCDIR"], 'test', 'functional', test_list[0].split()[0]), '-h'])
sys.exit(0)
- check_script_list(config["environment"]["SRCDIR"])
+ check_script_list(src_dir=config["environment"]["SRCDIR"], fail_on_warn=args.ci)
check_script_prefixes()
if not args.keepcache:
shutil.rmtree("%s/test/cache" % config["environment"]["BUILDDIR"], ignore_errors=True)
run_tests(
- test_list,
- config["environment"]["SRCDIR"],
- config["environment"]["BUILDDIR"],
- tmpdir,
+ test_list=test_list,
+ src_dir=config["environment"]["SRCDIR"],
+ build_dir=config["environment"]["BUILDDIR"],
+ tmpdir=tmpdir,
jobs=args.jobs,
enable_coverage=args.coverage,
args=passon_args,
combined_logs_len=args.combinedlogslen,
failfast=args.failfast,
- level=logging_level
+ runs_ci=args.ci,
)
-def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, level=logging.DEBUG):
+def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, runs_ci):
args = args or []
# Warn if bitcoind is already running (unix only)
@@ -359,7 +361,14 @@ def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=Fal
raise
#Run Tests
- job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags, level)
+ job_queue = TestHandler(
+ num_tests_parallel=jobs,
+ tests_dir=tests_dir,
+ tmpdir=tmpdir,
+ test_list=test_list,
+ flags=flags,
+ timeout_duration=40 * 60 if runs_ci else float('inf'), # in seconds
+ )
start_time = time.time()
test_results = []
@@ -440,14 +449,14 @@ class TestHandler:
Trigger the test scripts passed in via the list.
"""
- def __init__(self, num_tests_parallel, tests_dir, tmpdir, test_list=None, flags=None, logging_level=logging.DEBUG):
- assert(num_tests_parallel >= 1)
+ def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, timeout_duration):
+ assert num_tests_parallel >= 1
self.num_jobs = num_tests_parallel
self.tests_dir = tests_dir
self.tmpdir = tmpdir
+ self.timeout_duration = timeout_duration
self.test_list = test_list
self.flags = flags
- self.logging_level = logging_level
self.num_running = 0
self.jobs = []
@@ -480,7 +489,7 @@ class TestHandler:
time.sleep(.5)
for job in self.jobs:
(name, start_time, proc, testdir, log_out, log_err) = job
- if os.getenv('TRAVIS') == 'true' and int(time.time() - start_time) > TRAVIS_TIMEOUT_DURATION:
+ if int(time.time() - start_time) > self.timeout_duration:
# In travis, timeout individual tests (to stop tests hanging and not providing useful output).
proc.send_signal(signal.SIGINT)
if proc.poll() is not None:
@@ -495,14 +504,12 @@ class TestHandler:
status = "Failed"
self.num_running -= 1
self.jobs.remove(job)
- if self.logging_level == logging.DEBUG:
- clearline = '\r' + (' ' * dot_count) + '\r'
- print(clearline, end='', flush=True)
- dot_count = 0
+ clearline = '\r' + (' ' * dot_count) + '\r'
+ print(clearline, end='', flush=True)
+ dot_count = 0
return TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr
- if self.logging_level == logging.DEBUG:
- print('.', end='', flush=True)
- dot_count += 1
+ print('.', end='', flush=True)
+ dot_count += 1
def kill_and_join(self):
"""Send SIGKILL to all jobs and block until all have ended."""
@@ -560,7 +567,7 @@ def check_script_prefixes():
raise AssertionError("Some tests are not following naming convention!")
-def check_script_list(src_dir):
+def check_script_list(*, src_dir, fail_on_warn):
"""Check scripts directory.
Check that there are no scripts in the functional tests directory which are
@@ -570,10 +577,11 @@ def check_script_list(src_dir):
missed_tests = list(python_files - set(map(lambda x: x.split()[0], ALL_SCRIPTS + NON_SCRIPTS)))
if len(missed_tests) != 0:
print("%sWARNING!%s The following scripts are not being run: %s. Check the test lists in test_runner.py." % (BOLD[1], BOLD[0], str(missed_tests)))
- if os.getenv('TRAVIS') == 'true':
+ if fail_on_warn:
# On travis this warning is an error to prevent merging incomplete commits into master
sys.exit(1)
+
class RPCCoverage():
"""
Coverage reporting utilities for test_runner.
@@ -629,7 +637,7 @@ class RPCCoverage():
with open(coverage_ref_filename, 'r', encoding="utf8") as coverage_ref_file:
all_cmds.update([line.strip() for line in coverage_ref_file.readlines()])
- for root, dirs, files in os.walk(self.dir):
+ for root, _, files in os.walk(self.dir):
for filename in files:
if filename.startswith(coverage_file_prefix):
coverage_filenames.add(os.path.join(root, filename))
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 4de3356d79..c9b40905f0 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -28,10 +28,9 @@ class WalletTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def setup_network(self):
- self.add_nodes(4)
- self.start_node(0)
- self.start_node(1)
- self.start_node(2)
+ self.setup_nodes()
+ # Only need nodes 0-2 running at start of test
+ self.stop_node(3)
connect_nodes_bi(self.nodes, 0, 1)
connect_nodes_bi(self.nodes, 1, 2)
connect_nodes_bi(self.nodes, 0, 2)
@@ -480,7 +479,7 @@ class WalletTest(BitcoinTestFramework):
# Verify nothing new in wallet
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
- # Test getaddressinfo. Note that these addresses are taken from disablewallet.py
+ # Test getaddressinfo on external address. Note that these addresses are taken from disablewallet.py
assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")
address_info = self.nodes[0].getaddressinfo("mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ")
assert_equal(address_info['address'], "mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ")
@@ -488,6 +487,22 @@ class WalletTest(BitcoinTestFramework):
assert not address_info["ismine"]
assert not address_info["iswatchonly"]
assert not address_info["isscript"]
+ assert not address_info["ischange"]
+
+ # Test getaddressinfo 'ischange' field on change address.
+ self.nodes[0].generate(1)
+ destination = self.nodes[1].getnewaddress()
+ txid = self.nodes[0].sendtoaddress(destination, 0.123)
+ tx = self.nodes[0].decoderawtransaction(self.nodes[0].getrawtransaction(txid))
+ output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]]
+ assert len(output_addresses) > 1
+ for address in output_addresses:
+ ischange = self.nodes[0].getaddressinfo(address)['ischange']
+ assert_equal(ischange, address != destination)
+ if ischange:
+ change = address
+ self.nodes[0].setlabel(change, 'foobar')
+ assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False)
if __name__ == '__main__':
WalletTest().main()
diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py
index ab9ebed8d4..c514b7e0b4 100755
--- a/test/functional/wallet_encryption.py
+++ b/test/functional/wallet_encryption.py
@@ -31,12 +31,18 @@ class WalletEncryptionTest(BitcoinTestFramework):
privkey = self.nodes[0].dumpprivkey(address)
assert_equal(privkey[:1], "c")
assert_equal(len(privkey), 52)
+ assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrase was called", self.nodes[0].walletpassphrase, 'ff', 1)
+ assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.", self.nodes[0].walletpassphrasechange, 'ff', 'ff')
# Encrypt the wallet
+ assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].encryptwallet, '')
self.nodes[0].encryptwallet(passphrase)
# Test that the wallet is encrypted
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)
+ assert_raises_rpc_error(-15, "Error: running with an encrypted wallet, but encryptwallet was called.", self.nodes[0].encryptwallet, 'ff')
+ assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrase, '', 1)
+ assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrasechange, '', 'ff')
# Check that walletpassphrase works
self.nodes[0].walletpassphrase(passphrase, 2)
diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py
index f043a544fc..46462a16f3 100755
--- a/test/functional/wallet_import_rescan.py
+++ b/test/functional/wallet_import_rescan.py
@@ -46,11 +46,11 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")):
if self.call == Call.single:
if self.data == Data.address:
- response = self.try_rpc(self.node.importaddress, address=self.address["address"], rescan=rescan)
+ response = self.try_rpc(self.node.importaddress, address=self.address["address"], label=self.label, rescan=rescan)
elif self.data == Data.pub:
- response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], rescan=rescan)
+ response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], label=self.label, rescan=rescan)
elif self.data == Data.priv:
- response = self.try_rpc(self.node.importprivkey, privkey=self.key, rescan=rescan)
+ response = self.try_rpc(self.node.importprivkey, privkey=self.key, label=self.label, rescan=rescan)
assert_equal(response, None)
elif self.call in (Call.multiaddress, Call.multiscript):
@@ -61,18 +61,32 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")):
"timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
"keys": [self.key] if self.data == Data.priv else [],
+ "label": self.label,
"watchonly": self.data != Data.priv
}], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
assert_equal(response, [{"success": True}])
def check(self, txid=None, amount=None, confirmations=None):
- """Verify that listreceivedbyaddress returns expected values."""
+ """Verify that listtransactions/listreceivedbyaddress return expected values."""
+
+ txs = self.node.listtransactions(label=self.label, count=10000, include_watchonly=True)
+ assert_equal(len(txs), self.expected_txs)
addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address'])
if self.expected_txs:
assert_equal(len(addresses[0]["txids"]), self.expected_txs)
if txid is not None:
+ tx, = [tx for tx in txs if tx["txid"] == txid]
+ assert_equal(tx["label"], self.label)
+ assert_equal(tx["address"], self.address["address"])
+ assert_equal(tx["amount"], amount)
+ assert_equal(tx["category"], "receive")
+ assert_equal(tx["label"], self.label)
+ assert_equal(tx["txid"], txid)
+ assert_equal(tx["confirmations"], confirmations)
+ assert_equal("trusted" not in tx, True)
+
address, = [ad for ad in addresses if txid in ad["txids"]]
assert_equal(address["address"], self.address["address"])
assert_equal(address["amount"], self.expected_balance)
@@ -122,21 +136,20 @@ class ImportRescanTest(BitcoinTestFramework):
# Import keys with pruning disabled
self.start_nodes(extra_args=[[]] * self.num_nodes)
- super().import_deterministic_coinbase_privkeys()
+ for n in self.nodes:
+ n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase')
self.stop_nodes()
self.start_nodes()
for i in range(1, self.num_nodes):
connect_nodes(self.nodes[i], 0)
- def import_deterministic_coinbase_privkeys(self):
- pass
-
def run_test(self):
# Create one transaction on node 0 with a unique amount for
# each possible type of wallet import RPC.
for i, variant in enumerate(IMPORT_VARIANTS):
- variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())
+ variant.label = "label {} {}".format(i, variant)
+ variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(variant.label))
variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
variant.initial_amount = 1 - (i + 1) / 64
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
diff --git a/test/functional/wallet_import_with_label.py b/test/functional/wallet_import_with_label.py
new file mode 100755
index 0000000000..95acaa752e
--- /dev/null
+++ b/test/functional/wallet_import_with_label.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 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 behavior of RPC importprivkey on set and unset labels of
+addresses.
+
+It tests different cases in which an address is imported with importaddress
+with or without a label and then its private key is imported with importprivkey
+with and without a label.
+"""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+
+class ImportWithLabel(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+ self.setup_clean_chain = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def run_test(self):
+ """Main test logic"""
+
+ self.log.info(
+ "Test importaddress with label and importprivkey without label."
+ )
+ self.log.info("Import a watch-only address with a label.")
+ address = self.nodes[0].getnewaddress()
+ label = "Test Label"
+ self.nodes[1].importaddress(address, label)
+ address_assert = self.nodes[1].getaddressinfo(address)
+
+ assert_equal(address_assert["iswatchonly"], True)
+ assert_equal(address_assert["ismine"], False)
+ assert_equal(address_assert["label"], label)
+
+ self.log.info(
+ "Import the watch-only address's private key without a "
+ "label and the address should keep its label."
+ )
+ priv_key = self.nodes[0].dumpprivkey(address)
+ self.nodes[1].importprivkey(priv_key)
+
+ assert_equal(label, self.nodes[1].getaddressinfo(address)["label"])
+
+ self.log.info(
+ "Test importaddress without label and importprivkey with label."
+ )
+ self.log.info("Import a watch-only address without a label.")
+ address2 = self.nodes[0].getnewaddress()
+ self.nodes[1].importaddress(address2)
+ address_assert2 = self.nodes[1].getaddressinfo(address2)
+
+ assert_equal(address_assert2["iswatchonly"], True)
+ assert_equal(address_assert2["ismine"], False)
+ assert_equal(address_assert2["label"], "")
+
+ self.log.info(
+ "Import the watch-only address's private key with a "
+ "label and the address should have its label updated."
+ )
+ priv_key2 = self.nodes[0].dumpprivkey(address2)
+ label2 = "Test Label 2"
+ self.nodes[1].importprivkey(priv_key2, label2)
+
+ assert_equal(label2, self.nodes[1].getaddressinfo(address2)["label"])
+
+ self.log.info("Test importaddress with label and importprivkey with label.")
+ self.log.info("Import a watch-only address with a label.")
+ address3 = self.nodes[0].getnewaddress()
+ label3_addr = "Test Label 3 for importaddress"
+ self.nodes[1].importaddress(address3, label3_addr)
+ address_assert3 = self.nodes[1].getaddressinfo(address3)
+
+ assert_equal(address_assert3["iswatchonly"], True)
+ assert_equal(address_assert3["ismine"], False)
+ assert_equal(address_assert3["label"], label3_addr)
+
+ self.log.info(
+ "Import the watch-only address's private key with a "
+ "label and the address should have its label updated."
+ )
+ priv_key3 = self.nodes[0].dumpprivkey(address3)
+ label3_priv = "Test Label 3 for importprivkey"
+ self.nodes[1].importprivkey(priv_key3, label3_priv)
+
+ assert_equal(label3_priv, self.nodes[1].getaddressinfo(address3)["label"])
+
+ self.log.info(
+ "Test importprivkey won't label new dests with the same "
+ "label as others labeled dests for the same key."
+ )
+ self.log.info("Import a watch-only legacy address with a label.")
+ address4 = self.nodes[0].getnewaddress()
+ label4_addr = "Test Label 4 for importaddress"
+ self.nodes[1].importaddress(address4, label4_addr)
+ address_assert4 = self.nodes[1].getaddressinfo(address4)
+
+ assert_equal(address_assert4["iswatchonly"], True)
+ assert_equal(address_assert4["ismine"], False)
+ assert_equal(address_assert4["label"], label4_addr)
+
+ self.log.info("Asserts address has no embedded field with dests.")
+
+ assert_equal(address_assert4.get("embedded"), None)
+
+ self.log.info(
+ "Import the watch-only address's private key without a "
+ "label and new destinations for the key should have an "
+ "empty label while the 'old' destination should keep "
+ "its label."
+ )
+ priv_key4 = self.nodes[0].dumpprivkey(address4)
+ self.nodes[1].importprivkey(priv_key4)
+ address_assert4 = self.nodes[1].getaddressinfo(address4)
+
+ assert address_assert4.get("embedded")
+
+ bcaddress_assert = self.nodes[1].getaddressinfo(
+ address_assert4["embedded"]["address"]
+ )
+
+ assert_equal(address_assert4["label"], label4_addr)
+ assert_equal(bcaddress_assert["label"], "")
+
+ self.stop_nodes()
+
+
+if __name__ == "__main__":
+ ImportWithLabel().main()
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index f78ff19791..5c789b1c03 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -11,8 +11,14 @@ from test_framework.util import (
assert_greater_than,
assert_raises_rpc_error,
bytes_to_hex_str,
+ hex_str_to_bytes
)
-
+from test_framework.script import (
+ CScript,
+ OP_0,
+ hash160
+)
+from test_framework.messages import sha256
class ImportMultiTest(BitcoinTestFramework):
def set_test_params(self):
@@ -48,7 +54,7 @@ class ImportMultiTest(BitcoinTestFramework):
# RPC importmulti -----------------------------------------------
- # Bitcoin Address
+ # Bitcoin Address (implicit non-internal)
self.log.info("Should import an address")
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
result = self.nodes[1].importmulti([{
@@ -62,6 +68,7 @@ class ImportMultiTest(BitcoinTestFramework):
assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['timestamp'], timestamp)
+ assert_equal(address_assert['ischange'], False)
watchonly_address = address['address']
watchonly_timestamp = timestamp
@@ -89,6 +96,20 @@ class ImportMultiTest(BitcoinTestFramework):
assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['timestamp'], timestamp)
+ assert_equal(address_assert['ischange'], True)
+
+ # ScriptPubKey + internal + label
+ self.log.info("Should not allow a label to be specified when internal is true")
+ address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": address['scriptPubKey'],
+ "timestamp": "now",
+ "internal": True,
+ "label": "Example label"
+ }])
+ assert_equal(result[0]['success'], False)
+ assert_equal(result[0]['error']['code'], -8)
+ assert_equal(result[0]['error']['message'], 'Internal addresses should not have a label')
# Nonstandard scriptPubKey + !internal
self.log.info("Should not import a nonstandard scriptPubKey without internal flag")
@@ -107,7 +128,7 @@ class ImportMultiTest(BitcoinTestFramework):
assert_equal('timestamp' in address_assert, False)
- # Address + Public key + !Internal
+ # Address + Public key + !Internal(explicit)
self.log.info("Should import an address with public key")
address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
result = self.nodes[1].importmulti([{
@@ -115,7 +136,8 @@ class ImportMultiTest(BitcoinTestFramework):
"address": address['address']
},
"timestamp": "now",
- "pubkeys": [ address['pubkey'] ]
+ "pubkeys": [ address['pubkey'] ],
+ "internal": False
}])
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(address['address'])
@@ -133,7 +155,7 @@ class ImportMultiTest(BitcoinTestFramework):
"pubkeys": [ address['pubkey'] ],
"internal": True
}]
- result = self.nodes[1].importmulti(request)
+ result = self.nodes[1].importmulti(requests=request)
assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], True)
@@ -148,7 +170,7 @@ class ImportMultiTest(BitcoinTestFramework):
"timestamp": "now",
"pubkeys": [ address['pubkey'] ]
}]
- result = self.nodes[1].importmulti(request)
+ result = self.nodes[1].importmulti(requests=request)
assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -8)
assert_equal(result[0]['error']['message'], 'Internal must be set to true for nonstandard scriptPubKey imports.')
@@ -198,7 +220,7 @@ class ImportMultiTest(BitcoinTestFramework):
}])
assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -8)
- assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys')
+ assert_equal(result[0]['error']['message'], 'Watch-only addresses should not include private keys')
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False)
@@ -339,7 +361,7 @@ class ImportMultiTest(BitcoinTestFramework):
}])
assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -8)
- assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys')
+ assert_equal(result[0]['error']['message'], 'Watch-only addresses should not include private keys')
# Address + Public key + !Internal + Wrong pubkey
@@ -355,7 +377,7 @@ class ImportMultiTest(BitcoinTestFramework):
}])
assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -5)
- assert_equal(result[0]['error']['message'], 'Consistency check failed')
+ assert_equal(result[0]['error']['message'], 'Key does not match address destination')
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False)
@@ -375,7 +397,7 @@ class ImportMultiTest(BitcoinTestFramework):
result = self.nodes[1].importmulti(request)
assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -5)
- assert_equal(result[0]['error']['message'], 'Consistency check failed')
+ assert_equal(result[0]['error']['message'], 'Key does not match address destination')
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False)
@@ -395,7 +417,7 @@ class ImportMultiTest(BitcoinTestFramework):
}])
assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -5)
- assert_equal(result[0]['error']['message'], 'Consistency check failed')
+ assert_equal(result[0]['error']['message'], 'Key does not match address destination')
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False)
@@ -414,7 +436,7 @@ class ImportMultiTest(BitcoinTestFramework):
}])
assert_equal(result[0]['success'], False)
assert_equal(result[0]['error']['code'], -5)
- assert_equal(result[0]['error']['message'], 'Consistency check failed')
+ assert_equal(result[0]['error']['message'], 'Key does not match address destination')
address_assert = self.nodes[1].getaddressinfo(address['address'])
assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False)
@@ -458,6 +480,147 @@ class ImportMultiTest(BitcoinTestFramework):
"timestamp": "",
}])
+ # Import P2WPKH address as watch only
+ self.log.info("Should import a P2WPKH address as watch only")
+ address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": address['address']
+ },
+ "timestamp": "now",
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(address['address'])
+ assert_equal(address_assert['iswatchonly'], True)
+ assert_equal(address_assert['solvable'], False)
+
+ # Import P2WPKH address with public key but no private key
+ self.log.info("Should import a P2WPKH address and public key as solvable but not spendable")
+ address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": address['address']
+ },
+ "timestamp": "now",
+ "pubkeys": [ address['pubkey'] ]
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(address['address'])
+ assert_equal(address_assert['ismine'], False)
+ assert_equal(address_assert['solvable'], True)
+
+ # Import P2WPKH address with key and check it is spendable
+ self.log.info("Should import a P2WPKH address with key")
+ address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32"))
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": address['address']
+ },
+ "timestamp": "now",
+ "keys": [self.nodes[0].dumpprivkey(address['address'])]
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(address['address'])
+ assert_equal(address_assert['iswatchonly'], False)
+ assert_equal(address_assert['ismine'], True)
+
+ # P2WSH multisig address without scripts or keys
+ sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
+ sig_address_2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
+ multi_sig_script = self.nodes[0].addmultisigaddress(2, [sig_address_1['pubkey'], sig_address_2['pubkey']], "", "bech32")
+ self.log.info("Should import a p2wsh multisig as watch only without respective redeem script and private keys")
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": multi_sig_script['address']
+ },
+ "timestamp": "now"
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
+ assert_equal(address_assert['solvable'], False)
+
+ # Same P2WSH multisig address as above, but now with witnessscript + private keys
+ self.log.info("Should import a p2wsh with respective redeem script and private keys")
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": multi_sig_script['address']
+ },
+ "timestamp": "now",
+ "witnessscript": multi_sig_script['redeemScript'],
+ "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address']) ]
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
+ assert_equal(address_assert['solvable'], True)
+ assert_equal(address_assert['ismine'], True)
+ assert_equal(address_assert['sigsrequired'], 2)
+
+ # P2SH-P2WPKH address with no redeemscript or public or private key
+ sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="p2sh-segwit"))
+ pubkeyhash = hash160(hex_str_to_bytes(sig_address_1['pubkey']))
+ pkscript = CScript([OP_0, pubkeyhash])
+ self.log.info("Should import a p2sh-p2wpkh without redeem script or keys")
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": sig_address_1['address']
+ },
+ "timestamp": "now"
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
+ assert_equal(address_assert['solvable'], False)
+ assert_equal(address_assert['ismine'], False)
+
+ # P2SH-P2WPKH address + redeemscript + public key with no private key
+ self.log.info("Should import a p2sh-p2wpkh with respective redeem script and pubkey as solvable")
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": sig_address_1['address']
+ },
+ "timestamp": "now",
+ "redeemscript": bytes_to_hex_str(pkscript),
+ "pubkeys": [ sig_address_1['pubkey'] ]
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
+ assert_equal(address_assert['solvable'], True)
+ assert_equal(address_assert['ismine'], False)
+
+ # P2SH-P2WPKH address + redeemscript + private key
+ sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="p2sh-segwit"))
+ pubkeyhash = hash160(hex_str_to_bytes(sig_address_1['pubkey']))
+ pkscript = CScript([OP_0, pubkeyhash])
+ self.log.info("Should import a p2sh-p2wpkh with respective redeem script and private keys")
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": sig_address_1['address']
+ },
+ "timestamp": "now",
+ "redeemscript": bytes_to_hex_str(pkscript),
+ "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address'])]
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(sig_address_1['address'])
+ assert_equal(address_assert['solvable'], True)
+ assert_equal(address_assert['ismine'], True)
+
+ # P2SH-P2WSH 1-of-1 multisig + redeemscript with no private key
+ sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
+ multi_sig_script = self.nodes[0].addmultisigaddress(1, [sig_address_1['pubkey']], "", "p2sh-segwit")
+ scripthash = sha256(hex_str_to_bytes(multi_sig_script['redeemScript']))
+ redeem_script = CScript([OP_0, scripthash])
+ self.log.info("Should import a p2sh-p2wsh with respective redeem script but no private key")
+ result = self.nodes[1].importmulti([{
+ "scriptPubKey": {
+ "address": multi_sig_script['address']
+ },
+ "timestamp": "now",
+ "redeemscript": bytes_to_hex_str(redeem_script),
+ "witnessscript": multi_sig_script['redeemScript']
+ }])
+ assert_equal(result[0]['success'], True)
+ address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address'])
+ assert_equal(address_assert['solvable'], True)
if __name__ == '__main__':
ImportMultiTest ().main ()
diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py
index 26b181db33..78426018ef 100755
--- a/test/functional/wallet_importprunedfunds.py
+++ b/test/functional/wallet_importprunedfunds.py
@@ -81,7 +81,7 @@ class ImportPrunedFundsTest(BitcoinTestFramework):
# Import with affiliated address with no rescan
self.nodes[1].importaddress(address=address2, rescan=False)
- self.nodes[1].importprunedfunds(rawtxn2, proof2)
+ self.nodes[1].importprunedfunds(rawtransaction=rawtxn2, txoutproof=proof2)
assert [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2]
# Import with private key with no rescan
diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py
index 9e8667c600..011975e371 100755
--- a/test/functional/wallet_listreceivedby.py
+++ b/test/functional/wallet_listreceivedby.py
@@ -18,11 +18,6 @@ class ReceivedByTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
- def import_deterministic_coinbase_privkeys(self):
- assert_equal(0, len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True)))
- super().import_deterministic_coinbase_privkeys()
- self.num_cb_reward_addresses = len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True))
-
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -31,6 +26,9 @@ class ReceivedByTest(BitcoinTestFramework):
self.nodes[0].generate(1)
sync_blocks(self.nodes)
+ # save the number of coinbase reward addresses so far
+ num_cb_reward_addresses = len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True))
+
self.log.info("listreceivedbyaddress Test")
# Send from node 0 to 1
@@ -76,7 +74,7 @@ class ReceivedByTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "address_filter parameter was invalid", self.nodes[1].listreceivedbyaddress, minconf=0, include_empty=True, include_watchonly=True, address_filter="bamboozling")
# Another address receive money
res = self.nodes[1].listreceivedbyaddress(0, True, True)
- assert_equal(len(res), 2 + self.num_cb_reward_addresses) # Right now 2 entries
+ assert_equal(len(res), 2 + num_cb_reward_addresses) # Right now 2 entries
other_addr = self.nodes[1].getnewaddress()
txid2 = self.nodes[0].sendtoaddress(other_addr, 0.1)
self.nodes[0].generate(1)
@@ -93,7 +91,7 @@ class ReceivedByTest(BitcoinTestFramework):
assert_equal(len(res), 1)
# Should be two entries though without filter
res = self.nodes[1].listreceivedbyaddress(0, True, True)
- assert_equal(len(res), 3 + self.num_cb_reward_addresses) # Became 3 entries
+ assert_equal(len(res), 3 + num_cb_reward_addresses) # Became 3 entries
# Not on random addr
other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
index 5a17395abd..8ca0387268 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -97,9 +97,10 @@ class ListTransactionsTest(BitcoinTestFramework):
txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
self.nodes[1].generate(1)
self.sync_all()
- assert not [tx for tx in self.nodes[0].listtransactions(dummy="*", count=100, skip=0, include_watchonly=False) if "label" in tx and tx["label"] == "watchonly"]
- txs = [tx for tx in self.nodes[0].listtransactions(dummy="*", count=100, skip=0, include_watchonly=True) if "label" in tx and tx['label'] == 'watchonly']
- assert_array_result(txs, {"category": "receive", "amount": Decimal("0.1")}, {"txid": txid})
+ assert len(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=False)) == 0
+ assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True),
+ {"category": "receive", "amount": Decimal("0.1")},
+ {"txid": txid, "label": "watchonly"})
self.run_rbf_opt_in_test()
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index 4f663c82c7..8ab569a3c3 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -223,6 +223,9 @@ class MultiWalletTest(BitcoinTestFramework):
# Fail to load duplicate wallets
assert_raises_rpc_error(-4, 'Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0])
+ # Fail to load duplicate wallets by different ways (directory and filepath)
+ assert_raises_rpc_error(-4, "Wallet file verification failed: Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat')
+
# Fail to load if one wallet is a copy of another
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
index 3972baed1d..be67cbed31 100755
--- a/test/lint/lint-circular-dependencies.sh
+++ b/test/lint/lint-circular-dependencies.sh
@@ -9,7 +9,7 @@
export LC_ALL=C
EXPECTED_CIRCULAR_DEPENDENCIES=(
- "chainparamsbase -> util -> chainparamsbase"
+ "chainparamsbase -> util/system -> chainparamsbase"
"checkpoints -> validation -> checkpoints"
"index/txindex -> validation -> index/txindex"
"policy/fees -> txmempool -> policy/fees"
@@ -29,7 +29,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"validation -> validationinterface -> validation"
"wallet/coincontrol -> wallet/wallet -> wallet/coincontrol"
"wallet/fees -> wallet/wallet -> wallet/fees"
- "wallet/rpcwallet -> wallet/wallet -> wallet/rpcwallet"
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
"policy/fees -> policy/policy -> validation -> policy/fees"
"policy/rbf -> txmempool -> validation -> policy/rbf"
diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py
index 2fb35fd8ca..5caebf3739 100755
--- a/test/lint/lint-format-strings.py
+++ b/test/lint/lint-format-strings.py
@@ -16,8 +16,8 @@ FALSE_POSITIVES = [
("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"),
("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"),
("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"),
- ("src/util.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"),
- ("src/util.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"),
+ ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"),
+ ("src/util/system.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"),
("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"),
("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"),
("src/logging.h", "LogPrintf(const char* fmt, const Args&... args)"),
diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh
index cbee437c91..44170a6b5a 100755
--- a/test/lint/lint-locale-dependence.sh
+++ b/test/lint/lint-locale-dependence.sh
@@ -2,7 +2,6 @@
export LC_ALL=C
KNOWN_VIOLATIONS=(
- "src/base58.cpp:.*isspace"
"src/bitcoin-tx.cpp.*stoul"
"src/bitcoin-tx.cpp.*trim_right"
"src/bitcoin-tx.cpp:.*atoi"
@@ -18,20 +17,17 @@ KNOWN_VIOLATIONS=(
"src/test/getarg_tests.cpp.*split"
"src/torcontrol.cpp:.*atoi"
"src/torcontrol.cpp:.*strtol"
- "src/uint256.cpp:.*isspace"
"src/uint256.cpp:.*tolower"
- "src/util.cpp:.*atoi"
- "src/util.cpp:.*fprintf"
- "src/util.cpp:.*tolower"
- "src/utilmoneystr.cpp:.*isdigit"
- "src/utilmoneystr.cpp:.*isspace"
- "src/utilstrencodings.cpp:.*atoi"
- "src/utilstrencodings.cpp:.*isspace"
- "src/utilstrencodings.cpp:.*strtol"
- "src/utilstrencodings.cpp:.*strtoll"
- "src/utilstrencodings.cpp:.*strtoul"
- "src/utilstrencodings.cpp:.*strtoull"
- "src/utilstrencodings.h:.*atoi"
+ "src/util/system.cpp:.*atoi"
+ "src/util/system.cpp:.*fprintf"
+ "src/util/system.cpp:.*tolower"
+ "src/util/moneystr.cpp:.*isdigit"
+ "src/util/strencodings.cpp:.*atoi"
+ "src/util/strencodings.cpp:.*strtol"
+ "src/util/strencodings.cpp:.*strtoll"
+ "src/util/strencodings.cpp:.*strtoul"
+ "src/util/strencodings.cpp:.*strtoull"
+ "src/util/strencodings.h:.*atoi"
)
REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="^src/(crypto/ctaes/|leveldb/|secp256k1/|tinyformat.h|univalue/)"
diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh
new file mode 100755
index 0000000000..3341f794f9
--- /dev/null
+++ b/test/lint/lint-python-dead-code.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (c) 2018 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+# Find dead Python code.
+
+export LC_ALL=C
+
+if ! command -v vulture > /dev/null; then
+ echo "Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\""
+ exit 0
+fi
+
+vulture \
+ --min-confidence 60 \
+ --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,get_ecdh_key,get_privkey,is_compressed,is_fullyvalid,msg_generic,on_*,optionxform,restype,set_privkey" \
+ $(git ls-files -- "*.py" ":(exclude)contrib/")
diff --git a/test/lint/lint-rpc-help.sh b/test/lint/lint-rpc-help.sh
new file mode 100755
index 0000000000..faac5d43e2
--- /dev/null
+++ b/test/lint/lint-rpc-help.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2018 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+# Check that all RPC help texts are generated by RPCHelpMan.
+
+export LC_ALL=C
+
+EXIT_CODE=0
+
+# Assume that all multiline strings passed into a runtime_error are help texts.
+# This is potentially fragile, but the linter is only temporary and can safely
+# be removed early 2019.
+
+non_autogenerated_help=$(grep --perl-regexp --null-data --only-matching 'runtime_error\(\n\s*".*\\n"\n' $(git ls-files -- "*.cpp"))
+if [[ ${non_autogenerated_help} != "" ]]; then
+ echo "Must use RPCHelpMan to generate the help for the following RPC methods:"
+ echo "${non_autogenerated_help}"
+ echo
+ EXIT_CODE=1
+fi
+exit ${EXIT_CODE}
diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan
new file mode 100644
index 0000000000..209c46f096
--- /dev/null
+++ b/test/sanitizer_suppressions/tsan
@@ -0,0 +1,24 @@
+# ThreadSanitizer suppressions
+# ============================
+
+# fChecked is theoretically racy, practically only in unit tests
+race:CheckBlock
+
+# WalletBatch (unidentified deadlock)
+deadlock:WalletBatch
+
+# Intentional deadlock in tests
+deadlock:TestPotentialDeadLockDetected
+
+# Wildcard for all gui tests, should be replaced with non-wildcard suppressions
+race:src/qt/test/*
+deadlock:src/qt/test/*
+
+# WIP: Unidentified suppressions to run the functional tests
+#race:zmqpublishnotifier.cpp
+#
+#deadlock:CreateWalletFromFile
+#deadlock:importprivkey
+#deadlock:walletdb.h
+#deadlock:walletdb.cpp
+#deadlock:wallet/db.cpp
diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan
new file mode 100644
index 0000000000..e90d5c2ac0
--- /dev/null
+++ b/test/sanitizer_suppressions/ubsan
@@ -0,0 +1,36 @@
+alignment:move.h
+alignment:prevector.h
+bool:wallet/wallet.cpp
+float-divide-by-zero:policy/fees.cpp
+float-divide-by-zero:validation.cpp
+float-divide-by-zero:wallet/wallet.cpp
+nonnull-attribute:support/cleanse.cpp
+unsigned-integer-overflow:arith_uint256.h
+unsigned-integer-overflow:basic_string.h
+unsigned-integer-overflow:bench/bench.h
+unsigned-integer-overflow:bitcoin-tx.cpp
+unsigned-integer-overflow:bloom.cpp
+unsigned-integer-overflow:chain.cpp
+unsigned-integer-overflow:chain.h
+unsigned-integer-overflow:coded_stream.h
+unsigned-integer-overflow:core_write.cpp
+unsigned-integer-overflow:crypto/chacha20.cpp
+unsigned-integer-overflow:crypto/ctaes/ctaes.c
+unsigned-integer-overflow:crypto/ripemd160.cpp
+unsigned-integer-overflow:crypto/sha1.cpp
+unsigned-integer-overflow:crypto/sha256.cpp
+unsigned-integer-overflow:crypto/sha512.cpp
+unsigned-integer-overflow:hash.cpp
+unsigned-integer-overflow:leveldb/db/log_reader.cc
+unsigned-integer-overflow:leveldb/util/bloom.cc
+unsigned-integer-overflow:leveldb/util/crc32c.h
+unsigned-integer-overflow:leveldb/util/hash.cc
+unsigned-integer-overflow:policy/fees.cpp
+unsigned-integer-overflow:prevector.h
+unsigned-integer-overflow:script/interpreter.cpp
+unsigned-integer-overflow:stl_bvector.h
+unsigned-integer-overflow:streams.h
+unsigned-integer-overflow:txmempool.cpp
+unsigned-integer-overflow:util/strencodings.cpp
+unsigned-integer-overflow:validation.cpp
+vptr:fs.cpp
diff --git a/test/util/rpcauth-test.py b/test/util/rpcauth-test.py
index 46e9fbc739..53058dc394 100755
--- a/test/util/rpcauth-test.py
+++ b/test/util/rpcauth-test.py
@@ -24,8 +24,8 @@ class TestRPCAuth(unittest.TestCase):
self.rpcauth = importlib.import_module('rpcauth')
def test_generate_salt(self):
- self.assertLessEqual(len(self.rpcauth.generate_salt()), 32)
- self.assertGreaterEqual(len(self.rpcauth.generate_salt()), 16)
+ for i in range(16, 32 + 1):
+ self.assertEqual(len(self.rpcauth.generate_salt(i)), i * 2)
def test_generate_password(self):
password = self.rpcauth.generate_password()
@@ -34,7 +34,7 @@ class TestRPCAuth(unittest.TestCase):
self.assertEqual(expected_password, password)
def test_check_password_hmac(self):
- salt = self.rpcauth.generate_salt()
+ salt = self.rpcauth.generate_salt(16)
password = self.rpcauth.generate_password()
password_hmac = self.rpcauth.password_to_hmac(salt, password)