aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_config_args.py3
-rwxr-xr-xtest/functional/feature_proxy.py21
-rwxr-xr-xtest/functional/mempool_accept.py2
-rwxr-xr-xtest/functional/mempool_expiry.py2
-rwxr-xr-xtest/functional/p2p_headers_sync_with_minchainwork.py4
-rwxr-xr-xtest/functional/p2p_leak.py5
-rwxr-xr-xtest/functional/rpc_blockchain.py10
-rwxr-xr-xtest/functional/rpc_estimatefee.py8
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py116
-rwxr-xr-xtest/functional/rpc_getblockfrompeer.py4
-rwxr-xr-xtest/functional/rpc_getdescriptorinfo.py2
-rwxr-xr-xtest/functional/rpc_invalidateblock.py5
-rwxr-xr-xtest/functional/rpc_rawtransaction.py6
-rwxr-xr-xtest/functional/test_framework/messages.py1
-rwxr-xr-xtest/functional/test_runner.py8
-rwxr-xr-xtest/functional/wallet_basic.py63
-rwxr-xr-xtest/functional/wallet_groups.py5
-rwxr-xr-xtest/functional/wallet_listsinceblock.py3
-rwxr-xr-xtest/functional/wallet_resendwallettransactions.py68
-rwxr-xr-xtest/functional/wallet_sendall.py32
-rw-r--r--test/sanitizer_suppressions/tsan2
21 files changed, 298 insertions, 72 deletions
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 6c51a5ac31..eb31bca29a 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -186,11 +186,12 @@ class ConfArgsTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log(expected_msgs=[
"Loaded 0 addresses from peers.dat",
"DNS seeding disabled",
- "Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n",
+ "Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet), -addnode is not provided and all -seednode(s) attempted\n",
]):
self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1'])
assert time.time() - start < 60
self.stop_node(0)
+ self.nodes[0].assert_start_raises_init_error(['-dnsseed=1', '-onlynet=i2p', '-i2psam=127.0.0.1:7656'], "Error: Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6")
# No peers.dat exists and dns seeds are disabled.
# We expect the node will not add fixed seeds when explicitly disabled.
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index dd3cdc96ca..d02d56d068 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -332,20 +332,27 @@ class ProxyTest(BitcoinTestFramework):
msg = "Error: Invalid -i2psam address or hostname: 'def:xyz'"
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+ self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error")
msg = (
"Error: Outbound connections restricted to Tor (-onlynet=onion) but "
- "the proxy for reaching the Tor network is not provided (no -proxy= "
- "and no -onion= given) or it is explicitly forbidden (-onion=0)"
+ "the proxy for reaching the Tor network is explicitly forbidden: -onion=0"
)
- self.log.info("Test passing -onlynet=onion without -proxy or -onion raises expected init error")
- self.nodes[1].extra_args = ["-onlynet=onion"]
- self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
-
- self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error")
for arg in ["-onion=0", "-noonion"]:
self.nodes[1].extra_args = ["-onlynet=onion", arg]
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+ self.log.info("Test passing -onlynet=onion without -proxy, -onion or -listenonion raises expected init error")
+ self.nodes[1].extra_args = ["-onlynet=onion", "-listenonion=0"]
+ msg = (
+ "Error: Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
+ "reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given"
+ )
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing -onlynet=onion without -proxy or -onion but with -listenonion=1 is ok")
+ self.start_node(1, extra_args=["-onlynet=onion", "-listenonion=1"])
+ self.stop_node(1)
+
self.log.info("Test passing unknown network to -onlynet raises expected init error")
self.nodes[1].extra_args = ["-onlynet=abc"]
msg = "Error: Unknown network specified in -onlynet: 'abc'"
diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py
index 65b37a4975..02ec18140c 100755
--- a/test/functional/mempool_accept.py
+++ b/test/functional/mempool_accept.py
@@ -65,7 +65,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
self.log.info('Should not accept garbage to testmempoolaccept')
- assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar'))
+ assert_raises_rpc_error(-3, 'JSON value of type string is not of expected type array', lambda: node.testmempoolaccept(rawtxs='ff00baar'))
assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26))
assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=[]))
assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar']))
diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py
index 47ae0c762b..21721177e6 100755
--- a/test/functional/mempool_expiry.py
+++ b/test/functional/mempool_expiry.py
@@ -13,6 +13,7 @@ definable expiry timeout via the '-mempoolexpiry=<n>' command line argument
from datetime import timedelta
from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -20,7 +21,6 @@ from test_framework.util import (
)
from test_framework.wallet import MiniWallet
-DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours
CUSTOM_MEMPOOL_EXPIRY = 10 # hours
diff --git a/test/functional/p2p_headers_sync_with_minchainwork.py b/test/functional/p2p_headers_sync_with_minchainwork.py
index 016375222d..991e3348ed 100755
--- a/test/functional/p2p_headers_sync_with_minchainwork.py
+++ b/test/functional/p2p_headers_sync_with_minchainwork.py
@@ -49,7 +49,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
def test_chains_sync_when_long_enough(self):
self.log.info("Generate blocks on the node with no required chainwork, and verify nodes 1 and 2 have no new headers in their headers tree")
- with self.nodes[1].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]):
+ with self.nodes[1].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 14"]):
self.generate(self.nodes[0], NODE1_BLOCKS_REQUIRED-1, sync_fun=self.no_op)
# Node3 should always allow headers due to noban permissions
@@ -78,7 +78,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
} in chaintips
self.log.info("Generate more blocks to satisfy node1's minchainwork requirement, and verify node2 still has no new headers in headers tree")
- with self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=15)"]):
+ with self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=15)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 15"]):
self.generate(self.nodes[0], NODE1_BLOCKS_REQUIRED - self.nodes[0].getblockcount(), sync_fun=self.no_op)
self.sync_blocks(self.nodes[0:2]) # node3 will sync headers (noban permissions) but not blocks (due to minchainwork)
diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py
index af8e45d578..936c22197c 100755
--- a/test/functional/p2p_leak.py
+++ b/test/functional/p2p_leak.py
@@ -138,6 +138,9 @@ class P2PLeakTest(BitcoinTestFramework):
# Give the node enough time to possibly leak out a message
time.sleep(PEER_TIMEOUT + 2)
+ self.log.info("Connect peer to ensure the net thread runs the disconnect logic at least once")
+ self.nodes[0].add_p2p_connection(P2PInterface())
+
# Make sure only expected messages came in
assert not no_version_idle_peer.unexpected_msg
assert not no_version_idle_peer.got_wtxidrelay
@@ -169,7 +172,7 @@ class P2PLeakTest(BitcoinTestFramework):
self.log.info('Check that old peers are disconnected')
p2p_old_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False)
- with self.nodes[0].assert_debug_log(['peer=4 using obsolete version 31799; disconnecting']):
+ with self.nodes[0].assert_debug_log(["using obsolete version 31799; disconnecting"]):
p2p_old_peer.send_message(self.create_old_version(31799))
p2p_old_peer.wait_for_disconnect()
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 227e255536..d8147b3355 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -38,6 +38,7 @@ from test_framework.messages import (
msg_block,
)
from test_framework.p2p import P2PInterface
+from test_framework.script import hash256
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -485,6 +486,10 @@ class BlockchainTest(BitcoinTestFramework):
self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
blockhash = self.generate(node, 1)[0]
+ def assert_hexblock_hashes(verbosity):
+ block = node.getblock(blockhash, verbosity)
+ assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex())
+
def assert_fee_not_in_block(verbosity):
block = node.getblock(blockhash, verbosity)
assert 'fee' not in block['tx'][1]
@@ -519,8 +524,13 @@ class BlockchainTest(BitcoinTestFramework):
for vin in tx["vin"]:
assert "prevout" not in vin
+ self.log.info("Test that getblock with verbosity 0 hashes to expected value")
+ assert_hexblock_hashes(0)
+ assert_hexblock_hashes(False)
+
self.log.info("Test that getblock with verbosity 1 doesn't include fee")
assert_fee_not_in_block(1)
+ assert_fee_not_in_block(True)
self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee')
assert_fee_in_block(2)
diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py
index 51b7efb4c3..b057400887 100755
--- a/test/functional/rpc_estimatefee.py
+++ b/test/functional/rpc_estimatefee.py
@@ -22,15 +22,15 @@ class EstimateFeeTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee)
# wrong type for conf_target
- assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimatesmartfee, 'foo')
- assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimatesmartfee, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 'foo')
# wrong type for estimatesmartfee(estimate_mode)
- assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].estimatesmartfee, 1, 1)
+ assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].estimatesmartfee, 1, 1)
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', self.nodes[0].estimatesmartfee, 1, 'foo')
# wrong type for estimaterawfee(threshold)
- assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 1, 'foo')
# extra params
assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee, 1, 'ECONOMICAL', 1)
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 2c33617750..a963bb5e2d 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -106,6 +106,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.generate(self.nodes[2], 1)
self.generate(self.nodes[0], 121)
+ self.test_add_inputs_default_value()
self.test_weight_calculation()
self.test_change_position()
self.test_simple()
@@ -1073,6 +1074,121 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[2].unloadwallet("extfund")
+ def test_add_inputs_default_value(self):
+ self.log.info("Test 'add_inputs' default value")
+
+ # Create and fund the wallet with 5 BTC
+ self.nodes[2].createwallet("test_preset_inputs")
+ wallet = self.nodes[2].get_wallet_rpc("test_preset_inputs")
+ addr1 = wallet.getnewaddress(address_type="bech32")
+ self.nodes[0].sendtoaddress(addr1, 5)
+ self.generate(self.nodes[0], 1)
+
+ # Covered cases:
+ # 1. Default add_inputs value with no preset inputs (add_inputs=true):
+ # Expect: automatically add coins from the wallet to the tx.
+ # 2. Default add_inputs value with preset inputs (add_inputs=false):
+ # Expect: disallow automatic coin selection.
+ # 3. Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount).
+ # Expect: include inputs from the wallet.
+ # 4. Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount).
+ # Expect: only preset inputs are used.
+ # 5. Explicit add_inputs=true, no preset inputs (same as (1) but with an explicit set):
+ # Expect: include inputs from the wallet.
+
+ # Case (1), 'send' command
+ # 'add_inputs' value is true unless "inputs" are specified, in such case, add_inputs=false.
+ # So, the wallet will automatically select coins and create the transaction if only the outputs are provided.
+ tx = wallet.send(outputs=[{addr1: 3}])
+ assert tx["complete"]
+
+ # Case (2), 'send' command
+ # Select an input manually, which doesn't cover the entire output amount and
+ # verify that the dynamically set 'add_inputs=false' value works.
+
+ # Fund wallet with 2 outputs, 5 BTC each.
+ addr2 = wallet.getnewaddress(address_type="bech32")
+ source_tx = self.nodes[0].send(outputs=[{addr1: 5}, {addr2: 5}], options={"change_position": 0})
+ self.generate(self.nodes[0], 1)
+
+ # Select only one input.
+ options = {
+ "inputs": [
+ {
+ "txid": source_tx["txid"],
+ "vout": 1 # change position was hardcoded to index 0
+ }
+ ]
+ }
+ assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{addr1: 8}], options=options)
+
+ # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount)
+ options["add_inputs"] = True
+ options["add_to_wallet"] = False
+ tx = wallet.send(outputs=[{addr1: 8}], options=options)
+ assert tx["complete"]
+
+ # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount)
+ options["inputs"].append({
+ "txid": source_tx["txid"],
+ "vout": 2 # change position was hardcoded to index 0
+ })
+ tx = wallet.send(outputs=[{addr1: 8}], options=options)
+ assert tx["complete"]
+ # Check that only the preset inputs were added to the tx
+ decoded_psbt_inputs = self.nodes[0].decodepsbt(tx["psbt"])['tx']['vin']
+ assert_equal(len(decoded_psbt_inputs), 2)
+ for input in decoded_psbt_inputs:
+ assert_equal(input["txid"], source_tx["txid"])
+
+ # Case (5), assert that inputs are added to the tx by explicitly setting add_inputs=true
+ options = {"add_inputs": True, "add_to_wallet": True}
+ tx = wallet.send(outputs=[{addr1: 8}], options=options)
+ assert tx["complete"]
+
+ ################################################
+
+ # Case (1), 'walletcreatefundedpsbt' command
+ # Default add_inputs value with no preset inputs (add_inputs=true)
+ inputs = []
+ outputs = {self.nodes[1].getnewaddress(): 8}
+ assert "psbt" in wallet.walletcreatefundedpsbt(inputs=inputs, outputs=outputs)
+
+ # Case (2), 'walletcreatefundedpsbt' command
+ # Default add_inputs value with preset inputs (add_inputs=false).
+ inputs = [{
+ "txid": source_tx["txid"],
+ "vout": 1 # change position was hardcoded to index 0
+ }]
+ outputs = {self.nodes[1].getnewaddress(): 8}
+ assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, inputs=inputs, outputs=outputs)
+
+ # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount)
+ options["add_inputs"] = True
+ options["add_to_wallet"] = False
+ assert "psbt" in wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options)
+
+ # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount)
+ inputs.append({
+ "txid": source_tx["txid"],
+ "vout": 2 # change position was hardcoded to index 0
+ })
+ psbt_tx = wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options)
+ # Check that only the preset inputs were added to the tx
+ decoded_psbt_inputs = self.nodes[0].decodepsbt(psbt_tx["psbt"])['tx']['vin']
+ assert_equal(len(decoded_psbt_inputs), 2)
+ for input in decoded_psbt_inputs:
+ assert_equal(input["txid"], source_tx["txid"])
+
+ # Case (5), 'walletcreatefundedpsbt' command
+ # Explicit add_inputs=true, no preset inputs
+ options = {
+ "add_inputs": True
+ }
+ assert "psbt" in wallet.walletcreatefundedpsbt(inputs=[], outputs=outputs, options=options)
+
+ self.nodes[2].unloadwallet("test_preset_inputs")
+
def test_weight_calculation(self):
self.log.info("Test weight calculation with external inputs")
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
index 41e430d87e..278a343b2b 100755
--- a/test/functional/rpc_getblockfrompeer.py
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -56,8 +56,8 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
self.log.info("Arguments must be valid")
assert_raises_rpc_error(-8, "hash must be of length 64 (not 4, for '1234')", self.nodes[0].getblockfrompeer, "1234", peer_0_peer_1_id)
- assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id)
- assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].getblockfrompeer, short_tip, "0")
+ assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id)
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getblockfrompeer, short_tip, "0")
self.log.info("We must already have the header")
assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0)
diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py
index 5e6fd66aab..1b0f411e52 100755
--- a/test/functional/rpc_getdescriptorinfo.py
+++ b/test/functional/rpc_getdescriptorinfo.py
@@ -29,7 +29,7 @@ class DescriptorTest(BitcoinTestFramework):
def run_test(self):
assert_raises_rpc_error(-1, 'getdescriptorinfo', self.nodes[0].getdescriptorinfo)
- assert_raises_rpc_error(-3, 'Expected type string', self.nodes[0].getdescriptorinfo, 1)
+ assert_raises_rpc_error(-3, 'JSON value of type number is not of expected type string', self.nodes[0].getdescriptorinfo, 1)
assert_raises_rpc_error(-5, "'' is not a valid descriptor function", self.nodes[0].getdescriptorinfo, "")
# P2PK output with the specified public key.
diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py
index f1c2537ef9..1e33e7ca9c 100755
--- a/test/functional/rpc_invalidateblock.py
+++ b/test/functional/rpc_invalidateblock.py
@@ -8,6 +8,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR
from test_framework.util import (
assert_equal,
+ assert_raises_rpc_error,
)
@@ -83,6 +84,10 @@ class InvalidateTest(BitcoinTestFramework):
# Should be back at the tip by now
assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
+ self.log.info("Verify that invalidating an unknown block throws an error")
+ assert_raises_rpc_error(-5, "Block not found", self.nodes[1].invalidateblock, "00" * 32)
+ assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
+
if __name__ == '__main__':
InvalidateTest().main()
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index a858292dd4..f1fae13b4a 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -180,7 +180,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo')
# Test `createrawtransaction` invalid `inputs`
- assert_raises_rpc_error(-3, "Expected type array", self.nodes[0].createrawtransaction, 'foo', {})
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, 'foo', {})
assert_raises_rpc_error(-1, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {})
assert_raises_rpc_error(-1, "JSON value of type null is not of expected type string", self.nodes[0].createrawtransaction, [{}], {})
assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].createrawtransaction, [{'txid': 'foo'}], {})
@@ -226,12 +226,12 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 0, 'sequence': MAX_BIP125_RBF_SEQUENCE+1}], {}, 0, True)
# Test `createrawtransaction` invalid `locktime`
- assert_raises_rpc_error(-3, "Expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo')
assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1)
assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, 4294967296)
# Test `createrawtransaction` invalid `replaceable`
- assert_raises_rpc_error(-3, "Expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo')
+ assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo')
# Test that createrawtransaction accepts an array and object as outputs
# One output
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 4e757b64ca..8a928a1e50 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -71,6 +71,7 @@ DEFAULT_DESCENDANT_LIMIT = 25 # default max number of in-mempool descendants
# Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
MAX_OP_RETURN_RELAY = 83
+DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours
def sha256(s):
return hashlib.sha256(s).digest()
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index c4f56e131d..d78c1c634f 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -554,14 +554,14 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
while i < test_count:
if failfast and not all_passed:
break
- for test_result, testdir, stdout, stderr in job_queue.get_next():
+ for test_result, testdir, stdout, stderr, skip_reason in job_queue.get_next():
test_results.append(test_result)
i += 1
done_str = "{}/{} - {}{}{}".format(i, test_count, BOLD[1], test_result.name, BOLD[0])
if test_result.status == "Passed":
logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time))
elif test_result.status == "Skipped":
- logging.debug("%s skipped" % (done_str))
+ logging.debug(f"{done_str} skipped ({skip_reason})")
else:
all_passed = False
print("%s failed, Duration: %s s\n" % (done_str, test_result.time))
@@ -685,10 +685,12 @@ class TestHandler:
log_out.seek(0), log_err.seek(0)
[stdout, stderr] = [log_file.read().decode('utf-8') for log_file in (log_out, log_err)]
log_out.close(), log_err.close()
+ skip_reason = None
if proc.returncode == TEST_EXIT_PASSED and stderr == "":
status = "Passed"
elif proc.returncode == TEST_EXIT_SKIPPED:
status = "Skipped"
+ skip_reason = re.search(r"Test Skipped: (.*)", stdout).group(1)
else:
status = "Failed"
self.num_running -= 1
@@ -697,7 +699,7 @@ class TestHandler:
clearline = '\r' + (' ' * dot_count) + '\r'
print(clearline, end='', flush=True)
dot_count = 0
- ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr))
+ ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr, skip_reason))
if ret:
return ret
if self.use_term_control:
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 5d3d78c2dc..917721fef8 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -695,37 +695,38 @@ class WalletTest(BitcoinTestFramework):
txid_feeReason_four = self.nodes[2].sendmany(dummy='', amounts={address: 5}, verbose=False)
assert_equal(self.nodes[2].gettransaction(txid_feeReason_four)['txid'], txid_feeReason_four)
- self.log.info("Testing 'listunspent' outputs the parent descriptor(s) of coins")
- # Create two multisig descriptors, and send a UTxO each.
- multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))")
- multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))")
- addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0]
- addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0]
- txid_a = self.nodes[0].sendtoaddress(addr_a, 0.01)
- txid_b = self.nodes[0].sendtoaddress(addr_b, 0.01)
- self.generate(self.nodes[0], 1, sync_fun=self.no_op)
- # Now import the descriptors, make sure we can identify on which descriptor each coin was received.
- self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True)
- wo_wallet = self.nodes[0].get_wallet_rpc("wo")
- wo_wallet.importdescriptors([
- {
- "desc": multi_a,
- "active": False,
- "timestamp": "now",
- },
- {
- "desc": multi_b,
- "active": False,
- "timestamp": "now",
- },
- ])
- coins = wo_wallet.listunspent(minconf=0)
- assert_equal(len(coins), 2)
- coin_a = next(c for c in coins if c["txid"] == txid_a)
- assert_equal(coin_a["parent_descs"][0], multi_a)
- coin_b = next(c for c in coins if c["txid"] == txid_b)
- assert_equal(coin_b["parent_descs"][0], multi_b)
- self.nodes[0].unloadwallet("wo")
+ if self.options.descriptors:
+ self.log.info("Testing 'listunspent' outputs the parent descriptor(s) of coins")
+ # Create two multisig descriptors, and send a UTxO each.
+ multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))")
+ multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))")
+ addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0]
+ addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0]
+ txid_a = self.nodes[0].sendtoaddress(addr_a, 0.01)
+ txid_b = self.nodes[0].sendtoaddress(addr_b, 0.01)
+ self.generate(self.nodes[0], 1, sync_fun=self.no_op)
+ # Now import the descriptors, make sure we can identify on which descriptor each coin was received.
+ self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True)
+ wo_wallet = self.nodes[0].get_wallet_rpc("wo")
+ wo_wallet.importdescriptors([
+ {
+ "desc": multi_a,
+ "active": False,
+ "timestamp": "now",
+ },
+ {
+ "desc": multi_b,
+ "active": False,
+ "timestamp": "now",
+ },
+ ])
+ coins = wo_wallet.listunspent(minconf=0)
+ assert_equal(len(coins), 2)
+ coin_a = next(c for c in coins if c["txid"] == txid_a)
+ assert_equal(coin_a["parent_descs"][0], multi_a)
+ coin_b = next(c for c in coins if c["txid"] == txid_b)
+ assert_equal(coin_b["parent_descs"][0], multi_b)
+ self.nodes[0].unloadwallet("wo")
if __name__ == '__main__':
diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py
index 9f0f694a3e..e5e4cf03bf 100755
--- a/test/functional/wallet_groups.py
+++ b/test/functional/wallet_groups.py
@@ -26,9 +26,10 @@ class WalletGroupTest(BitcoinTestFramework):
["-maxapsfee=0.00002719"],
["-maxapsfee=0.00002720"],
]
- # whitelist peers to speed up tx relay / mempool sync
+
for args in self.extra_args:
- args.append("-whitelist=noban@127.0.0.1")
+ args.append("-whitelist=noban@127.0.0.1") # whitelist peers to speed up tx relay / mempool sync
+ args.append(f"-paytxfee={20 * 1e3 / 1e8}") # apply feerate of 20 sats/vB across all nodes
self.rpc_timeout = 480
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index 204bd67fb1..f259449bef 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -42,7 +42,8 @@ class ListSinceBlockTest(BitcoinTestFramework):
self.test_double_send()
self.double_spends_filtered()
self.test_targetconfirmations()
- self.test_desc()
+ if self.options.descriptors:
+ self.test_desc()
self.test_send_to_self()
def test_no_blockhash(self):
diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py
index 4ef259efe1..b3d02fbfc9 100755
--- a/test/functional/wallet_resendwallettransactions.py
+++ b/test/functional/wallet_resendwallettransactions.py
@@ -9,10 +9,13 @@ from test_framework.blocktools import (
create_block,
create_coinbase,
)
+from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS
from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal
-
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+)
class ResendWalletTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
@@ -27,13 +30,9 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
peer_first = node.add_p2p_connection(P2PTxInvStore())
self.log.info("Create a new transaction and wait until it's broadcast")
- txid = node.sendtoaddress(node.getnewaddress(), 1)
-
- # Wallet rebroadcast is first scheduled 1 min sec after startup (see
- # nNextResend in ResendWalletTransactions()). Tell scheduler to call
- # MaybeResendWalletTxs now to initialize nNextResend before the first
- # setmocktime call below.
- node.mockscheduler(60)
+ parent_utxo, indep_utxo = node.listunspent()[:2]
+ addr = node.getnewaddress()
+ txid = node.send(outputs=[{addr: 1}], options={"inputs": [parent_utxo]})["txid"]
# Can take a few seconds due to transaction trickling
peer_first.wait_for_broadcast([txid])
@@ -51,7 +50,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
block.solve()
node.submitblock(block.serialize().hex())
- # Set correct m_best_block_time, which is used in ResendWalletTransactions
+ # Set correct m_best_block_time, which is used in ResubmitWalletTransactions
node.syncwithvalidationinterfacequeue()
now = int(time.time())
@@ -66,7 +65,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
self.log.info("Bump time & check that transaction is rebroadcast")
# Transaction should be rebroadcast approximately 24 hours in the future,
# but can range from 12-36. So bump 36 hours to be sure.
- with node.assert_debug_log(['ResendWalletTransactions: resubmit 1 unconfirmed transactions']):
+ with node.assert_debug_log(['resubmit 1 unconfirmed transactions']):
node.setmocktime(now + 36 * 60 * 60)
# Tell scheduler to call MaybeResendWalletTxs now.
node.mockscheduler(60)
@@ -74,6 +73,53 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
node.setmocktime(now + 36 * 60 * 60 + 600)
peer_second.wait_for_broadcast([txid])
+ self.log.info("Chain of unconfirmed not-in-mempool txs are rebroadcast")
+ # This tests that the node broadcasts the parent transaction before the child transaction.
+ # To test that scenario, we need a method to reliably get a child transaction placed
+ # in mapWallet positioned before the parent. We cannot predict the position in mapWallet,
+ # but we can observe it using listreceivedbyaddress and other related RPCs.
+ #
+ # So we will create the child transaction, use listreceivedbyaddress to see what the
+ # ordering of mapWallet is, if the child is not before the parent, we will create a new
+ # child (via bumpfee) and remove the old child (via removeprunedfunds) until we get the
+ # ordering of child before parent.
+ child_txid = node.send(outputs=[{addr: 0.5}], options={"inputs": [{"txid":txid, "vout":0}]})["txid"]
+ while True:
+ txids = node.listreceivedbyaddress(minconf=0, address_filter=addr)[0]["txids"]
+ if txids == [child_txid, txid]:
+ break
+ bumped = node.bumpfee(child_txid)
+ # The scheduler queue creates a copy of the added tx after
+ # send/bumpfee and re-adds it to the wallet (undoing the next
+ # removeprunedfunds). So empty the scheduler queue:
+ node.syncwithvalidationinterfacequeue()
+ node.removeprunedfunds(child_txid)
+ child_txid = bumped["txid"]
+ entry_time = node.getmempoolentry(child_txid)["time"]
+
+ block_time = entry_time + 6 * 60
+ node.setmocktime(block_time)
+ block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockcount() + 1), block_time)
+ block.solve()
+ node.submitblock(block.serialize().hex())
+ # Set correct m_best_block_time, which is used in ResubmitWalletTransactions
+ node.syncwithvalidationinterfacequeue()
+
+ # Evict these txs from the mempool
+ evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5
+ node.setmocktime(evict_time)
+ indep_send = node.send(outputs=[{node.getnewaddress(): 1}], options={"inputs": [indep_utxo]})
+ node.getmempoolentry(indep_send["txid"])
+ assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, txid)
+ assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, child_txid)
+
+ # Rebroadcast and check that parent and child are both in the mempool
+ with node.assert_debug_log(['resubmit 2 unconfirmed transactions']):
+ node.setmocktime(evict_time + 36 * 60 * 60) # 36 hrs is the upper limit of the resend timer
+ node.mockscheduler(60)
+ node.getmempoolentry(txid)
+ node.getmempoolentry(child_txid)
+
if __name__ == '__main__':
ResendWalletTransactionsTest().main()
diff --git a/test/functional/wallet_sendall.py b/test/functional/wallet_sendall.py
index aa8d2a9d2c..db4f32fe16 100755
--- a/test/functional/wallet_sendall.py
+++ b/test/functional/wallet_sendall.py
@@ -264,6 +264,32 @@ class SendallTest(BitcoinTestFramework):
recipients=[self.remainder_target],
options={"inputs": [utxo], "send_max": True})
+ @cleanup
+ def sendall_fails_on_high_fee(self):
+ self.log.info("Test sendall fails if the transaction fee exceeds the maxtxfee")
+ self.add_utxos([21])
+
+ assert_raises_rpc_error(
+ -4,
+ "Fee exceeds maximum configured by user",
+ self.wallet.sendall,
+ recipients=[self.remainder_target],
+ fee_rate=100000)
+
+ # This tests needs to be the last one otherwise @cleanup will fail with "Transaction too large" error
+ def sendall_fails_with_transaction_too_large(self):
+ self.log.info("Test that sendall fails if resulting transaction is too large")
+ # create many inputs
+ outputs = {self.wallet.getnewaddress(): 0.000025 for _ in range(1600)}
+ self.def_wallet.sendmany(amounts=outputs)
+ self.generate(self.nodes[0], 1)
+
+ assert_raises_rpc_error(
+ -4,
+ "Transaction too large.",
+ self.wallet.sendall,
+ recipients=[self.remainder_target])
+
def run_test(self):
self.nodes[0].createwallet("activewallet")
self.wallet = self.nodes[0].get_wallet_rpc("activewallet")
@@ -312,5 +338,11 @@ class SendallTest(BitcoinTestFramework):
# Sendall fails when using send_max while specifying inputs
self.sendall_fails_on_specific_inputs_with_send_max()
+ # Sendall fails when providing a fee that is too high
+ self.sendall_fails_on_high_fee()
+
+ # Sendall fails when many inputs result to too large transaction
+ self.sendall_fails_with_transaction_too_large()
+
if __name__ == '__main__':
SendallTest().main()
diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan
index 3acf575d07..d331991273 100644
--- a/test/sanitizer_suppressions/tsan
+++ b/test/sanitizer_suppressions/tsan
@@ -13,7 +13,7 @@ race:zmq::*
race:bitcoin-qt
# deadlock (TODO fix)
-deadlock:CChainState::ConnectTip
+deadlock:Chainstate::ConnectTip
# Intentional deadlock in tests
deadlock:sync_tests::potential_deadlock_detected