aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rwxr-xr-xtest/functional/interface_rest.py28
-rwxr-xr-xtest/functional/interface_usdt_mempool.py343
-rwxr-xr-xtest/functional/mempool_sigoplimit.py154
-rwxr-xr-xtest/functional/mining_getblocktemplate_longpoll.py14
-rwxr-xr-xtest/functional/p2p_invalid_messages.py7
-rwxr-xr-xtest/functional/rpc_blockchain.py8
-rwxr-xr-xtest/functional/rpc_generate.py7
-rwxr-xr-xtest/functional/rpc_getblockfrompeer.py49
-rwxr-xr-xtest/functional/rpc_invalid_address_message.py18
-rwxr-xr-xtest/functional/rpc_psbt.py45
-rwxr-xr-xtest/functional/test_framework/p2p.py2
-rw-r--r--test/functional/test_framework/psbt.py10
-rwxr-xr-xtest/functional/test_framework/test_framework.py7
-rwxr-xr-xtest/functional/test_framework/test_node.py2
-rwxr-xr-xtest/functional/test_runner.py2
-rwxr-xr-xtest/functional/wallet_basic.py2
-rwxr-xr-xtest/functional/wallet_importdescriptors.py25
-rwxr-xr-xtest/functional/wallet_listdescriptors.py8
-rwxr-xr-xtest/functional/wallet_transactiontime_rescan.py25
19 files changed, 691 insertions, 65 deletions
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index 0dddc8946a..aafee7e87c 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -347,6 +347,34 @@ class RESTTest (BitcoinTestFramework):
assert_equal(json_obj[tx]['spentby'], txs[i + 1:i + 2])
assert_equal(json_obj[tx]['depends'], txs[i - 1:i])
+ # Check the mempool response for explicit parameters
+ json_obj = self.test_rest_request("/mempool/contents", query_params={"verbose": "true", "mempool_sequence": "false"})
+ assert_equal(json_obj, raw_mempool_verbose)
+
+ # Check the mempool response for not verbose
+ json_obj = self.test_rest_request("/mempool/contents", query_params={"verbose": "false"})
+ raw_mempool = self.nodes[0].getrawmempool(verbose=False)
+
+ assert_equal(json_obj, raw_mempool)
+
+ # Check the mempool response for sequence
+ json_obj = self.test_rest_request("/mempool/contents", query_params={"verbose": "false", "mempool_sequence": "true"})
+ raw_mempool = self.nodes[0].getrawmempool(verbose=False, mempool_sequence=True)
+
+ assert_equal(json_obj, raw_mempool)
+
+ # Check for error response if verbose=true and mempool_sequence=true
+ resp = self.test_rest_request("/mempool/contents", ret_type=RetType.OBJ, status=400, query_params={"verbose": "true", "mempool_sequence": "true"})
+ assert_equal(resp.read().decode('utf-8').strip(), 'Verbose results cannot contain mempool sequence values. (hint: set "verbose=false")')
+
+ # Check for error response if verbose is not "true" or "false"
+ resp = self.test_rest_request("/mempool/contents", ret_type=RetType.OBJ, status=400, query_params={"verbose": "TRUE"})
+ assert_equal(resp.read().decode('utf-8').strip(), 'The "verbose" query parameter must be either "true" or "false".')
+
+ # Check for error response if mempool_sequence is not "true" or "false"
+ resp = self.test_rest_request("/mempool/contents", ret_type=RetType.OBJ, status=400, query_params={"verbose": "false", "mempool_sequence": "TRUE"})
+ assert_equal(resp.read().decode('utf-8').strip(), 'The "mempool_sequence" query parameter must be either "true" or "false".')
+
# Now mine the transactions
newblockhash = self.generate(self.nodes[1], 1)
diff --git a/test/functional/interface_usdt_mempool.py b/test/functional/interface_usdt_mempool.py
new file mode 100755
index 0000000000..ec2f9e4e77
--- /dev/null
+++ b/test/functional/interface_usdt_mempool.py
@@ -0,0 +1,343 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 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 the mempool:* tracepoint API interface.
+ See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-mempool
+"""
+
+from decimal import Decimal
+
+# Test will be skipped if we don't have bcc installed
+try:
+ from bcc import BPF, USDT # type: ignore[import]
+except ImportError:
+ pass
+
+from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.messages import COIN, DEFAULT_MEMPOOL_EXPIRY_HOURS
+from test_framework.p2p import P2PDataStore
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+from test_framework.wallet import MiniWallet
+
+MEMPOOL_TRACEPOINTS_PROGRAM = """
+# include <uapi/linux/ptrace.h>
+
+// The longest rejection reason is 118 chars and is generated in case of SCRIPT_ERR_EVAL_FALSE by
+// strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))
+#define MAX_REJECT_REASON_LENGTH 118
+// The longest string returned by RemovalReasonToString() is 'sizelimit'
+#define MAX_REMOVAL_REASON_LENGTH 9
+#define HASH_LENGTH 32
+
+struct added_event
+{
+ u8 hash[HASH_LENGTH];
+ u64 vsize;
+ s64 fee;
+};
+
+struct removed_event
+{
+ u8 hash[HASH_LENGTH];
+ char reason[MAX_REMOVAL_REASON_LENGTH];
+ u64 vsize;
+ s64 fee;
+ u64 entry_time;
+};
+
+struct rejected_event
+{
+ u8 hash[HASH_LENGTH];
+ char reason[MAX_REJECT_REASON_LENGTH];
+};
+
+struct replaced_event
+{
+ u8 replaced_hash[HASH_LENGTH];
+ u64 replaced_vsize;
+ s64 replaced_fee;
+ u64 replaced_entry_time;
+ u8 replacement_hash[HASH_LENGTH];
+ u64 replacement_vsize;
+ s64 replacement_fee;
+};
+
+// BPF perf buffer to push the data to user space.
+BPF_PERF_OUTPUT(added_events);
+BPF_PERF_OUTPUT(removed_events);
+BPF_PERF_OUTPUT(rejected_events);
+BPF_PERF_OUTPUT(replaced_events);
+
+int trace_added(struct pt_regs *ctx) {
+ struct added_event added = {};
+
+ bpf_usdt_readarg_p(1, ctx, &added.hash, HASH_LENGTH);
+ bpf_usdt_readarg(2, ctx, &added.vsize);
+ bpf_usdt_readarg(3, ctx, &added.fee);
+
+ added_events.perf_submit(ctx, &added, sizeof(added));
+ return 0;
+}
+
+int trace_removed(struct pt_regs *ctx) {
+ struct removed_event removed = {};
+
+ bpf_usdt_readarg_p(1, ctx, &removed.hash, HASH_LENGTH);
+ bpf_usdt_readarg_p(2, ctx, &removed.reason, MAX_REMOVAL_REASON_LENGTH);
+ bpf_usdt_readarg(3, ctx, &removed.vsize);
+ bpf_usdt_readarg(4, ctx, &removed.fee);
+ bpf_usdt_readarg(5, ctx, &removed.entry_time);
+
+ removed_events.perf_submit(ctx, &removed, sizeof(removed));
+ return 0;
+}
+
+int trace_rejected(struct pt_regs *ctx) {
+ struct rejected_event rejected = {};
+
+ bpf_usdt_readarg_p(1, ctx, &rejected.hash, HASH_LENGTH);
+ bpf_usdt_readarg_p(2, ctx, &rejected.reason, MAX_REJECT_REASON_LENGTH);
+
+ rejected_events.perf_submit(ctx, &rejected, sizeof(rejected));
+ return 0;
+}
+
+int trace_replaced(struct pt_regs *ctx) {
+ struct replaced_event replaced = {};
+
+ bpf_usdt_readarg_p(1, ctx, &replaced.replaced_hash, HASH_LENGTH);
+ bpf_usdt_readarg(2, ctx, &replaced.replaced_vsize);
+ bpf_usdt_readarg(3, ctx, &replaced.replaced_fee);
+ bpf_usdt_readarg(4, ctx, &replaced.replaced_entry_time);
+ bpf_usdt_readarg_p(5, ctx, &replaced.replacement_hash, HASH_LENGTH);
+ bpf_usdt_readarg(6, ctx, &replaced.replacement_vsize);
+ bpf_usdt_readarg(7, ctx, &replaced.replacement_fee);
+
+ replaced_events.perf_submit(ctx, &replaced, sizeof(replaced));
+ return 0;
+}
+"""
+
+
+class MempoolTracepointTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_platform_not_linux()
+ self.skip_if_no_bitcoind_tracepoints()
+ self.skip_if_no_python_bcc()
+ self.skip_if_no_bpf_permissions()
+
+ def added_test(self):
+ """Add a transaction to the mempool and make sure the tracepoint returns
+ the expected txid, vsize, and fee."""
+
+ EXPECTED_ADDED_EVENTS = 1
+ handled_added_events = 0
+
+ self.log.info("Hooking into mempool:added tracepoint...")
+ node = self.nodes[0]
+ ctx = USDT(pid=node.process.pid)
+ ctx.enable_probe(probe="mempool:added", fn_name="trace_added")
+ bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0)
+
+ def handle_added_event(_, data, __):
+ nonlocal handled_added_events
+ event = bpf["added_events"].event(data)
+ assert_equal(txid, bytes(event.hash)[::-1].hex())
+ assert_equal(vsize, event.vsize)
+ assert_equal(fee, event.fee)
+ handled_added_events += 1
+
+ bpf["added_events"].open_perf_buffer(handle_added_event)
+
+ self.log.info("Sending transaction...")
+ fee = Decimal(31200)
+ tx = self.wallet.send_self_transfer(from_node=node, fee=fee / COIN)
+ # expected data
+ txid = tx["txid"]
+ vsize = tx["tx"].get_vsize()
+
+ self.log.info("Polling buffer...")
+ bpf.perf_buffer_poll(timeout=200)
+
+ self.log.info("Cleaning up mempool...")
+ self.generate(node, 1)
+
+ bpf.cleanup()
+
+ self.log.info("Ensuring mempool:added event was handled successfully...")
+ assert_equal(EXPECTED_ADDED_EVENTS, handled_added_events)
+
+ def removed_test(self):
+ """Expire a transaction from the mempool and make sure the tracepoint returns
+ the expected txid, expiry reason, vsize, and fee."""
+
+ EXPECTED_REMOVED_EVENTS = 1
+ handled_removed_events = 0
+
+ self.log.info("Hooking into mempool:removed tracepoint...")
+ node = self.nodes[0]
+ ctx = USDT(pid=node.process.pid)
+ ctx.enable_probe(probe="mempool:removed", fn_name="trace_removed")
+ bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0)
+
+ def handle_removed_event(_, data, __):
+ nonlocal handled_removed_events
+ event = bpf["removed_events"].event(data)
+ assert_equal(txid, bytes(event.hash)[::-1].hex())
+ assert_equal(reason, event.reason.decode("UTF-8"))
+ assert_equal(vsize, event.vsize)
+ assert_equal(fee, event.fee)
+ assert_equal(entry_time, event.entry_time)
+ handled_removed_events += 1
+
+ bpf["removed_events"].open_perf_buffer(handle_removed_event)
+
+ self.log.info("Sending transaction...")
+ fee = Decimal(31200)
+ tx = self.wallet.send_self_transfer(from_node=node, fee=fee / COIN)
+ # expected data
+ txid = tx["txid"]
+ reason = "expiry"
+ vsize = tx["tx"].get_vsize()
+
+ self.log.info("Fast-forwarding time to mempool expiry...")
+ entry_time = node.getmempoolentry(txid)["time"]
+ expiry_time = entry_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5
+ node.setmocktime(expiry_time)
+
+ self.log.info("Triggering expiry...")
+ self.wallet.get_utxo(txid=txid)
+ self.wallet.send_self_transfer(from_node=node)
+
+ self.log.info("Polling buffer...")
+ bpf.perf_buffer_poll(timeout=200)
+
+ bpf.cleanup()
+
+ self.log.info("Ensuring mempool:removed event was handled successfully...")
+ assert_equal(EXPECTED_REMOVED_EVENTS, handled_removed_events)
+
+ def replaced_test(self):
+ """Replace one and two transactions in the mempool and make sure the tracepoint
+ returns the expected txids, vsizes, and fees."""
+
+ EXPECTED_REPLACED_EVENTS = 1
+ handled_replaced_events = 0
+
+ self.log.info("Hooking into mempool:replaced tracepoint...")
+ node = self.nodes[0]
+ ctx = USDT(pid=node.process.pid)
+ ctx.enable_probe(probe="mempool:replaced", fn_name="trace_replaced")
+ bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0)
+
+ def handle_replaced_event(_, data, __):
+ nonlocal handled_replaced_events
+ event = bpf["replaced_events"].event(data)
+ assert_equal(replaced_txid, bytes(event.replaced_hash)[::-1].hex())
+ assert_equal(replaced_vsize, event.replaced_vsize)
+ assert_equal(replaced_fee, event.replaced_fee)
+ assert_equal(replaced_entry_time, event.replaced_entry_time)
+ assert_equal(replacement_txid, bytes(event.replacement_hash)[::-1].hex())
+ assert_equal(replacement_vsize, event.replacement_vsize)
+ assert_equal(replacement_fee, event.replacement_fee)
+ handled_replaced_events += 1
+
+ bpf["replaced_events"].open_perf_buffer(handle_replaced_event)
+
+ self.log.info("Sending RBF transaction...")
+ utxo = self.wallet.get_utxo(mark_as_spent=True)
+ original_fee = Decimal(40000)
+ original_tx = self.wallet.send_self_transfer(
+ from_node=node, utxo_to_spend=utxo, fee=original_fee / COIN
+ )
+ entry_time = node.getmempoolentry(original_tx["txid"])["time"]
+
+ self.log.info("Sending replacement transaction...")
+ replacement_fee = Decimal(45000)
+ replacement_tx = self.wallet.send_self_transfer(
+ from_node=node, utxo_to_spend=utxo, fee=replacement_fee / COIN
+ )
+
+ # expected data
+ replaced_txid = original_tx["txid"]
+ replaced_vsize = original_tx["tx"].get_vsize()
+ replaced_fee = original_fee
+ replaced_entry_time = entry_time
+ replacement_txid = replacement_tx["txid"]
+ replacement_vsize = replacement_tx["tx"].get_vsize()
+
+ self.log.info("Polling buffer...")
+ bpf.perf_buffer_poll(timeout=200)
+
+ bpf.cleanup()
+
+ self.log.info("Ensuring mempool:replaced event was handled successfully...")
+ assert_equal(EXPECTED_REPLACED_EVENTS, handled_replaced_events)
+
+ def rejected_test(self):
+ """Create an invalid transaction and make sure the tracepoint returns
+ the expected txid, rejection reason, peer id, and peer address."""
+
+ EXPECTED_REJECTED_EVENTS = 1
+ handled_rejected_events = 0
+
+ self.log.info("Adding P2P connection...")
+ node = self.nodes[0]
+ node.add_p2p_connection(P2PDataStore())
+
+ self.log.info("Hooking into mempool:rejected tracepoint...")
+ ctx = USDT(pid=node.process.pid)
+ ctx.enable_probe(probe="mempool:rejected", fn_name="trace_rejected")
+ bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0)
+
+ def handle_rejected_event(_, data, __):
+ nonlocal handled_rejected_events
+ event = bpf["rejected_events"].event(data)
+ assert_equal(txid, bytes(event.hash)[::-1].hex())
+ assert_equal(reason, event.reason.decode("UTF-8"))
+ handled_rejected_events += 1
+
+ bpf["rejected_events"].open_perf_buffer(handle_rejected_event)
+
+ self.log.info("Sending invalid transaction...")
+ tx = self.wallet.create_self_transfer(fee_rate=Decimal(0))
+ node.p2ps[0].send_txs_and_test([tx["tx"]], node, success=False)
+
+ # expected data
+ txid = tx["tx"].hash
+ reason = "min relay fee not met"
+
+ self.log.info("Polling buffer...")
+ bpf.perf_buffer_poll(timeout=200)
+
+ bpf.cleanup()
+
+ self.log.info("Ensuring mempool:rejected event was handled successfully...")
+ assert_equal(EXPECTED_REJECTED_EVENTS, handled_rejected_events)
+
+ def run_test(self):
+ """Tests the mempool:added, mempool:removed, mempool:replaced,
+ and mempool:rejected tracepoints."""
+
+ # Create some coinbase transactions and mature them so they can be spent
+ node = self.nodes[0]
+ self.wallet = MiniWallet(node)
+ self.generate(self.wallet, 4)
+ self.generate(node, COINBASE_MATURITY)
+
+ # Test individual tracepoints
+ self.added_test()
+ self.removed_test()
+ self.replaced_test()
+ self.rejected_test()
+
+
+if __name__ == "__main__":
+ MempoolTracepointTest().main()
diff --git a/test/functional/mempool_sigoplimit.py b/test/functional/mempool_sigoplimit.py
new file mode 100755
index 0000000000..b178b9feda
--- /dev/null
+++ b/test/functional/mempool_sigoplimit.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+# Copyright (c) 2023 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 sigop limit mempool policy (`-bytespersigop` parameter)"""
+from math import ceil
+
+from test_framework.messages import (
+ COutPoint,
+ CTransaction,
+ CTxIn,
+ CTxInWitness,
+ CTxOut,
+ WITNESS_SCALE_FACTOR,
+ tx_from_hex,
+)
+from test_framework.script import (
+ CScript,
+ OP_CHECKMULTISIG,
+ OP_CHECKSIG,
+ OP_ENDIF,
+ OP_FALSE,
+ OP_IF,
+ OP_RETURN,
+ OP_TRUE,
+)
+from test_framework.script_util import (
+ script_to_p2wsh_script,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ assert_greater_than_or_equal,
+)
+from test_framework.wallet import MiniWallet
+
+
+DEFAULT_BYTES_PER_SIGOP = 20 # default setting
+
+
+class BytesPerSigOpTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ # allow large datacarrier output to pad transactions
+ self.extra_args = [['-datacarriersize=100000']]
+
+ def create_p2wsh_spending_tx(self, witness_script, output_script):
+ """Create a 1-input-1-output P2WSH spending transaction with only the
+ witness script in the witness stack and the given output script."""
+ # create P2WSH address and fund it via MiniWallet first
+ txid, vout = self.wallet.send_to(
+ from_node=self.nodes[0],
+ scriptPubKey=script_to_p2wsh_script(witness_script),
+ amount=1000000,
+ )
+
+ # create spending transaction
+ tx = CTransaction()
+ tx.vin = [CTxIn(COutPoint(int(txid, 16), vout))]
+ tx.wit.vtxinwit = [CTxInWitness()]
+ tx.wit.vtxinwit[0].scriptWitness.stack = [bytes(witness_script)]
+ tx.vout = [CTxOut(500000, output_script)]
+ return tx
+
+ def test_sigops_limit(self, bytes_per_sigop, num_sigops):
+ sigop_equivalent_vsize = ceil(num_sigops * bytes_per_sigop / WITNESS_SCALE_FACTOR)
+ self.log.info(f"- {num_sigops} sigops (equivalent size of {sigop_equivalent_vsize} vbytes)")
+
+ # create a template tx with the specified sigop cost in the witness script
+ # (note that the sigops count even though being in a branch that's not executed)
+ num_multisigops = num_sigops // 20
+ num_singlesigops = num_sigops % 20
+ witness_script = CScript(
+ [OP_FALSE, OP_IF] +
+ [OP_CHECKMULTISIG]*num_multisigops +
+ [OP_CHECKSIG]*num_singlesigops +
+ [OP_ENDIF, OP_TRUE]
+ )
+ # use a 256-byte data-push as lower bound in the output script, in order
+ # to avoid having to compensate for tx size changes caused by varying
+ # length serialization sizes (both for scriptPubKey and data-push lengths)
+ tx = self.create_p2wsh_spending_tx(witness_script, CScript([OP_RETURN, b'X'*256]))
+
+ # bump the tx to reach the sigop-limit equivalent size by padding the datacarrier output
+ assert_greater_than_or_equal(sigop_equivalent_vsize, tx.get_vsize())
+ vsize_to_pad = sigop_equivalent_vsize - tx.get_vsize()
+ tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad)])
+ assert_equal(sigop_equivalent_vsize, tx.get_vsize())
+
+ res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
+ assert_equal(res['allowed'], True)
+ assert_equal(res['vsize'], sigop_equivalent_vsize)
+
+ # increase the tx's vsize to be right above the sigop-limit equivalent size
+ # => tx's vsize in mempool should also grow accordingly
+ tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad+1)])
+ res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
+ assert_equal(res['allowed'], True)
+ assert_equal(res['vsize'], sigop_equivalent_vsize+1)
+
+ # decrease the tx's vsize to be right below the sigop-limit equivalent size
+ # => tx's vsize in mempool should stick at the sigop-limit equivalent
+ # bytes level, as it is higher than the tx's serialized vsize
+ # (the maximum of both is taken)
+ tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'X'*(256+vsize_to_pad-1)])
+ res = self.nodes[0].testmempoolaccept([tx.serialize().hex()])[0]
+ assert_equal(res['allowed'], True)
+ assert_equal(res['vsize'], sigop_equivalent_vsize)
+
+ # check that the ancestor and descendant size calculations in the mempool
+ # also use the same max(sigop_equivalent_vsize, serialized_vsize) logic
+ # (to keep it simple, we only test the case here where the sigop vsize
+ # is much larger than the serialized vsize, i.e. we create a small child
+ # tx by getting rid of the large padding output)
+ tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'test123'])
+ assert_greater_than(sigop_equivalent_vsize, tx.get_vsize())
+ self.nodes[0].sendrawtransaction(hexstring=tx.serialize().hex(), maxburnamount='1.0')
+
+ # fetch parent tx, which doesn't contain any sigops
+ parent_txid = tx.vin[0].prevout.hash.to_bytes(32, 'big').hex()
+ parent_tx = tx_from_hex(self.nodes[0].getrawtransaction(txid=parent_txid))
+
+ entry_child = self.nodes[0].getmempoolentry(tx.rehash())
+ assert_equal(entry_child['descendantcount'], 1)
+ assert_equal(entry_child['descendantsize'], sigop_equivalent_vsize)
+ assert_equal(entry_child['ancestorcount'], 2)
+ assert_equal(entry_child['ancestorsize'], sigop_equivalent_vsize + parent_tx.get_vsize())
+
+ entry_parent = self.nodes[0].getmempoolentry(parent_tx.rehash())
+ assert_equal(entry_parent['ancestorcount'], 1)
+ assert_equal(entry_parent['ancestorsize'], parent_tx.get_vsize())
+ assert_equal(entry_parent['descendantcount'], 2)
+ assert_equal(entry_parent['descendantsize'], parent_tx.get_vsize() + sigop_equivalent_vsize)
+
+ def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
+
+ for bytes_per_sigop in (DEFAULT_BYTES_PER_SIGOP, 43, 81, 165, 327, 649, 1072):
+ if bytes_per_sigop == DEFAULT_BYTES_PER_SIGOP:
+ self.log.info(f"Test default sigops limit setting ({bytes_per_sigop} bytes per sigop)...")
+ else:
+ bytespersigop_parameter = f"-bytespersigop={bytes_per_sigop}"
+ self.log.info(f"Test sigops limit setting {bytespersigop_parameter}...")
+ self.restart_node(0, extra_args=[bytespersigop_parameter] + self.extra_args[0])
+
+ for num_sigops in (69, 101, 142, 183, 222):
+ self.test_sigops_limit(bytes_per_sigop, num_sigops)
+
+ self.generate(self.wallet, 1)
+
+
+if __name__ == '__main__':
+ BytesPerSigOpTest().main()
diff --git a/test/functional/mining_getblocktemplate_longpoll.py b/test/functional/mining_getblocktemplate_longpoll.py
index ec492f9e72..53182eb79e 100755
--- a/test/functional/mining_getblocktemplate_longpoll.py
+++ b/test/functional/mining_getblocktemplate_longpoll.py
@@ -4,7 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test longpolling with getblocktemplate."""
-from decimal import Decimal
import random
import threading
@@ -47,9 +46,9 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
thr.join(5) # wait 5 seconds or until thread exits
assert thr.is_alive()
- miniwallets = [MiniWallet(node) for node in self.nodes]
+ self.miniwallet = MiniWallet(self.nodes[0])
self.log.info("Test that longpoll will terminate if another node generates a block")
- self.generate(miniwallets[1], 1) # generate a block on another node
+ self.generate(self.nodes[1], 1) # generate a block on another node
# check that thread will exit now that new transaction entered mempool
thr.join(5) # wait 5 seconds or until thread exits
assert not thr.is_alive()
@@ -57,18 +56,15 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
self.log.info("Test that longpoll will terminate if we generate a block ourselves")
thr = LongpollThread(self.nodes[0])
thr.start()
- self.generate(miniwallets[0], 1) # generate a block on own node
+ self.generate(self.nodes[0], 1) # generate a block on own node
thr.join(5) # wait 5 seconds or until thread exits
assert not thr.is_alive()
self.log.info("Test that introducing a new transaction into the mempool will terminate the longpoll")
thr = LongpollThread(self.nodes[0])
thr.start()
- # generate a random transaction and submit it
- min_relay_fee = self.nodes[0].getnetworkinfo()["relayfee"]
- fee_rate = min_relay_fee + Decimal('0.00000010') * random.randint(0,20)
- miniwallets[0].send_self_transfer(from_node=random.choice(self.nodes),
- fee_rate=fee_rate)
+ # generate a transaction and submit it
+ self.miniwallet.send_self_transfer(from_node=random.choice(self.nodes))
# after one minute, every 10 seconds the mempool is probed, so in 80 seconds it should have returned
thr.join(60 + 20)
assert not thr.is_alive()
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
index ea4999a965..644abda914 100755
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -80,6 +80,11 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_buffer(self):
self.log.info("Test message with header split across two buffers is received")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
+ # After add_p2p_connection both sides have the verack processed.
+ # However the pong from conn in reply to the ping from the node has not
+ # been processed and recorded in totalbytesrecv.
+ # Flush the pong from conn by sending a ping from conn.
+ conn.sync_with_ping(timeout=1)
# Create valid message
msg = conn.build_message(msg_ping(nonce=12345))
cut_pos = 12 # Chosen at an arbitrary position within the header
@@ -89,8 +94,6 @@ class InvalidMessagesTest(BitcoinTestFramework):
# Wait until node has processed the first half of the message
self.wait_until(lambda: self.nodes[0].getnettotals()['totalbytesrecv'] != before)
middle = self.nodes[0].getnettotals()['totalbytesrecv']
- # If this assert fails, we've hit an unlikely race
- # where the test framework sent a message in between the two halves
assert_equal(middle, before + cut_pos)
conn.send_raw_message(msg[cut_pos:])
conn.sync_with_ping(timeout=1)
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 7a0cedb1f5..6022042c11 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -69,6 +69,7 @@ class BlockchainTest(BitcoinTestFramework):
def run_test(self):
self.wallet = MiniWallet(self.nodes[0])
+ self._test_prune_disk_space()
self.mine_chain()
self._test_max_future_block_time()
self.restart_node(
@@ -100,6 +101,13 @@ class BlockchainTest(BitcoinTestFramework):
self.generate(self.wallet, 1)
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], HEIGHT)
+ def _test_prune_disk_space(self):
+ self.log.info("Test that a manually pruned node does not run into "
+ "integer overflow on first start up")
+ self.restart_node(0, extra_args=["-prune=1"])
+ self.log.info("Avoid warning when assumed chain size is enough")
+ self.restart_node(0, extra_args=["-prune=123456789"])
+
def _test_max_future_block_time(self):
self.stop_node(0)
self.log.info("A block tip of more than MAX_FUTURE_BLOCK_TIME in the future raises an error")
diff --git a/test/functional/rpc_generate.py b/test/functional/rpc_generate.py
index 8948ccb48d..20f62079fd 100755
--- a/test/functional/rpc_generate.py
+++ b/test/functional/rpc_generate.py
@@ -29,8 +29,13 @@ class RPCGenerateTest(BitcoinTestFramework):
node = self.nodes[0]
miniwallet = MiniWallet(node)
- self.log.info('Generate an empty block to address')
+ self.log.info('Mine an empty block to address and return the hex')
address = miniwallet.get_address()
+ generated_block = self.generateblock(node, output=address, transactions=[], submit=False)
+ node.submitblock(hexdata=generated_block['hex'])
+ assert_equal(generated_block['hash'], node.getbestblockhash())
+
+ self.log.info('Generate an empty block to address')
hash = self.generateblock(node, output=address, transactions=[])['hash']
block = node.getblock(blockhash=hash, verbose=2)
assert_equal(len(block['tx']), 1)
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
index dddc779763..2f093bebff 100755
--- a/test/functional/rpc_getblockfrompeer.py
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -24,14 +24,19 @@ from test_framework.util import (
class GetBlockFromPeerTest(BitcoinTestFramework):
def set_test_params(self):
- self.num_nodes = 2
+ self.num_nodes = 3
+ self.extra_args = [
+ [],
+ [],
+ ["-fastprune", "-prune=1"]
+ ]
def setup_network(self):
self.setup_nodes()
- def check_for_block(self, hash):
+ def check_for_block(self, node, hash):
try:
- self.nodes[0].getblock(hash)
+ self.nodes[node].getblock(hash)
return True
except JSONRPCException:
return False
@@ -48,7 +53,7 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
self.log.info("Connect nodes to sync headers")
self.connect_nodes(0, 1)
- self.sync_blocks()
+ self.sync_blocks(self.nodes[0:2])
self.log.info("Node 0 should only have the header for node 1's block 3")
x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips()))
@@ -81,7 +86,7 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
self.log.info("Successful fetch")
result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
- self.wait_until(lambda: self.check_for_block(short_tip), timeout=1)
+ self.wait_until(lambda: self.check_for_block(node=0, hash=short_tip), timeout=1)
assert_equal(result, {})
self.log.info("Don't fetch blocks we already have")
@@ -111,6 +116,40 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
error_msg = "In prune mode, only blocks that the node has already synced previously can be fetched from a peer"
assert_raises_rpc_error(-1, error_msg, self.nodes[1].getblockfrompeer, blockhash, node1_interface_id)
+ self.log.info("Connect pruned node")
+ # We need to generate more blocks to be able to prune
+ self.connect_nodes(0, 2)
+ pruned_node = self.nodes[2]
+ self.generate(self.nodes[0], 400, sync_fun=self.no_op)
+ self.sync_blocks([self.nodes[0], pruned_node])
+ pruneheight = pruned_node.pruneblockchain(300)
+ assert_equal(pruneheight, 248)
+ # Ensure the block is actually pruned
+ pruned_block = self.nodes[0].getblockhash(2)
+ assert_raises_rpc_error(-1, "Block not available (pruned data)", pruned_node.getblock, pruned_block)
+
+ self.log.info("Fetch pruned block")
+ peers = pruned_node.getpeerinfo()
+ assert_equal(len(peers), 1)
+ pruned_node_peer_0_id = peers[0]["id"]
+ result = pruned_node.getblockfrompeer(pruned_block, pruned_node_peer_0_id)
+ self.wait_until(lambda: self.check_for_block(node=2, hash=pruned_block), timeout=1)
+ assert_equal(result, {})
+
+ self.log.info("Fetched block persists after next pruning event")
+ self.generate(self.nodes[0], 250, sync_fun=self.no_op)
+ self.sync_blocks([self.nodes[0], pruned_node])
+ pruneheight += 251
+ assert_equal(pruned_node.pruneblockchain(700), pruneheight)
+ assert_equal(pruned_node.getblock(pruned_block)["hash"], "36c56c5b5ebbaf90d76b0d1a074dcb32d42abab75b7ec6fa0ffd9b4fbce8f0f7")
+
+ self.log.info("Fetched block can be pruned again when prune height exceeds the height of the tip at the time when the block was fetched")
+ self.generate(self.nodes[0], 250, sync_fun=self.no_op)
+ self.sync_blocks([self.nodes[0], pruned_node])
+ pruneheight += 250
+ assert_equal(pruned_node.pruneblockchain(1000), pruneheight)
+ assert_raises_rpc_error(-1, "Block not available (pruned data)", pruned_node.getblock, pruned_block)
+
if __name__ == '__main__':
GetBlockFromPeerTest().main()
diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py
index 0c29efb85a..fd282a9bc1 100755
--- a/test/functional/rpc_invalid_address_message.py
+++ b/test/functional/rpc_invalid_address_message.py
@@ -64,7 +64,7 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
def test_validateaddress(self):
# Invalid Bech32
self.check_invalid(BECH32_INVALID_SIZE, 'Invalid Bech32 address data size')
- self.check_invalid(BECH32_INVALID_PREFIX, 'Not a valid Bech32 or Base58 encoding')
+ self.check_invalid(BECH32_INVALID_PREFIX, 'Invalid or unsupported Segwit (Bech32) or Base58 encoding.')
self.check_invalid(BECH32_INVALID_BECH32, 'Version 1+ witness address must use Bech32m checksum')
self.check_invalid(BECH32_INVALID_BECH32M, 'Version 0 witness address must use Bech32 checksum')
self.check_invalid(BECH32_INVALID_VERSION, 'Invalid Bech32 address witness version')
@@ -84,16 +84,16 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
self.check_valid(BECH32_VALID_MULTISIG)
# Invalid Base58
- self.check_invalid(BASE58_INVALID_PREFIX, 'Invalid prefix for Base58-encoded address')
- self.check_invalid(BASE58_INVALID_CHECKSUM, 'Invalid checksum or length of Base58 address')
- self.check_invalid(BASE58_INVALID_LENGTH, 'Invalid checksum or length of Base58 address')
+ self.check_invalid(BASE58_INVALID_PREFIX, 'Invalid or unsupported Base58-encoded address.')
+ self.check_invalid(BASE58_INVALID_CHECKSUM, 'Invalid checksum or length of Base58 address (P2PKH or P2SH)')
+ self.check_invalid(BASE58_INVALID_LENGTH, 'Invalid checksum or length of Base58 address (P2PKH or P2SH)')
# Valid Base58
self.check_valid(BASE58_VALID)
# Invalid address format
- self.check_invalid(INVALID_ADDRESS, 'Not a valid Bech32 or Base58 encoding')
- self.check_invalid(INVALID_ADDRESS_2, 'Not a valid Bech32 or Base58 encoding')
+ self.check_invalid(INVALID_ADDRESS, 'Invalid or unsupported Segwit (Bech32) or Base58 encoding.')
+ self.check_invalid(INVALID_ADDRESS_2, 'Invalid or unsupported Segwit (Bech32) or Base58 encoding.')
node = self.nodes[0]
@@ -106,9 +106,9 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
node = self.nodes[0]
assert_raises_rpc_error(-5, "Invalid Bech32 address data size", node.getaddressinfo, BECH32_INVALID_SIZE)
- assert_raises_rpc_error(-5, "Not a valid Bech32 or Base58 encoding", node.getaddressinfo, BECH32_INVALID_PREFIX)
- assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", node.getaddressinfo, BASE58_INVALID_PREFIX)
- assert_raises_rpc_error(-5, "Not a valid Bech32 or Base58 encoding", node.getaddressinfo, INVALID_ADDRESS)
+ assert_raises_rpc_error(-5, "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", node.getaddressinfo, BECH32_INVALID_PREFIX)
+ assert_raises_rpc_error(-5, "Invalid or unsupported Base58-encoded address.", node.getaddressinfo, BASE58_INVALID_PREFIX)
+ assert_raises_rpc_error(-5, "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", node.getaddressinfo, INVALID_ADDRESS)
def run_test(self):
self.test_validateaddress()
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 58a80e37a2..0fc0c0df8b 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -4,7 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the Partially Signed Transaction RPCs.
"""
-
from decimal import Decimal
from itertools import product
@@ -27,6 +26,7 @@ from test_framework.psbt import (
PSBT_IN_SHA256,
PSBT_IN_HASH160,
PSBT_IN_HASH256,
+ PSBT_IN_NON_WITNESS_UTXO,
PSBT_IN_WITNESS_UTXO,
PSBT_OUT_TAP_TREE,
)
@@ -59,13 +59,16 @@ class PSBTTest(BitcoinTestFramework):
["-walletrbf=0", "-changetype=legacy"],
[]
]
+ # whitelist peers to speed up tx relay / mempool sync
+ for args in self.extra_args:
+ args.append("-whitelist=noban@127.0.0.1")
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
- # TODO: Re-enable this test with segwit v1
def test_utxo_conversion(self):
+ self.log.info("Check that non-witness UTXOs are removed for segwit v1+ inputs")
mining_node = self.nodes[2]
offline_node = self.nodes[0]
online_node = self.nodes[1]
@@ -77,34 +80,41 @@ class PSBTTest(BitcoinTestFramework):
# Create watchonly on online_node
online_node.createwallet(wallet_name='wonline', disable_private_keys=True)
wonline = online_node.get_wallet_rpc('wonline')
- w2 = online_node.get_wallet_rpc('')
+ w2 = online_node.get_wallet_rpc(self.default_wallet_name)
# Mine a transaction that credits the offline address
- offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit")
- online_addr = w2.getnewaddress(address_type="p2sh-segwit")
+ offline_addr = offline_node.getnewaddress(address_type="bech32m")
+ online_addr = w2.getnewaddress(address_type="bech32m")
wonline.importaddress(offline_addr, "", False)
- mining_node.sendtoaddress(address=offline_addr, amount=1.0)
- self.generate(mining_node, nblocks=1)
+ mining_wallet = mining_node.get_wallet_rpc(self.default_wallet_name)
+ mining_wallet.sendtoaddress(address=offline_addr, amount=1.0)
+ self.generate(mining_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, mining_node]))
- # Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO)
+ # Construct an unsigned PSBT on the online node
utxos = wonline.listunspent(addresses=[offline_addr])
raw = wonline.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
psbt = wonline.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
- assert "non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]
+ assert not "not_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]
+
+ # add non-witness UTXO manually
+ psbt_new = PSBT.from_base64(psbt)
+ prev_tx = wonline.gettransaction(utxos[0]["txid"])["hex"]
+ psbt_new.i[0].map[PSBT_IN_NON_WITNESS_UTXO] = bytes.fromhex(prev_tx)
+ assert "non_witness_utxo" in mining_node.decodepsbt(psbt_new.to_base64())["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]
+ # Have the offline node sign the PSBT (which will remove the non-witness UTXO)
+ signed_psbt = offline_node.walletprocesspsbt(psbt_new.to_base64())["psbt"]
+ assert not "non_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"])
- self.generate(mining_node, 1)
+ self.generate(mining_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, mining_node]))
assert_equal(online_node.gettxout(txid,0)["confirmations"], 1)
wonline.unloadwallet()
# Reconnect
- self.connect_nodes(0, 1)
+ self.connect_nodes(1, 0)
self.connect_nodes(0, 2)
def test_input_confs_control(self):
@@ -571,8 +581,8 @@ class PSBTTest(BitcoinTestFramework):
for i, signer in enumerate(signers):
self.nodes[2].unloadwallet("wallet{}".format(i))
- # TODO: Re-enable this for segwit v1
- # self.test_utxo_conversion()
+ if self.options.descriptors:
+ self.test_utxo_conversion()
self.test_input_confs_control()
@@ -931,6 +941,9 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(self.nodes[0].finalizepsbt(psbt.to_base64()),
{'hex': '0200000001dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd0000000000000000000100000000000000000000000000', 'complete': True})
+ self.log.info("Test we don't crash when making a 0-value funded transaction at 0 fee without forcing an input selection")
+ assert_raises_rpc_error(-4, "Transaction requires one destination of non-0 value, a non-0 feerate, or a pre-selected input", self.nodes[0].walletcreatefundedpsbt, [], [{"data": "deadbeef"}], 0, {"fee_rate": "0"})
+
if __name__ == '__main__':
PSBTTest().main()
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index c5768177bd..2433e52671 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -464,7 +464,7 @@ class P2PInterface(P2PConnection):
def wait_for_connect(self, timeout=60):
test_function = lambda: self.is_connected
- wait_until_helper(test_function, timeout=timeout, lock=p2p_lock)
+ self.wait_until(test_function, timeout=timeout, check_connected=False)
def wait_for_disconnect(self, timeout=60):
test_function = lambda: not self.is_connected
diff --git a/test/functional/test_framework/psbt.py b/test/functional/test_framework/psbt.py
index 3a5b4ec74d..1eff4a250e 100644
--- a/test/functional/test_framework/psbt.py
+++ b/test/functional/test_framework/psbt.py
@@ -105,8 +105,8 @@ class PSBT:
def deserialize(self, f):
assert f.read(5) == b"psbt\xff"
self.g = from_binary(PSBTMap, f)
- assert 0 in self.g.map
- self.tx = from_binary(CTransaction, self.g.map[0])
+ assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
+ self.tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
self.i = [from_binary(PSBTMap, f) for _ in self.tx.vin]
self.o = [from_binary(PSBTMap, f) for _ in self.tx.vout]
return self
@@ -115,8 +115,8 @@ class PSBT:
assert isinstance(self.g, PSBTMap)
assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i)
assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o)
- assert 0 in self.g.map
- tx = from_binary(CTransaction, self.g.map[0])
+ assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
+ tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
assert len(tx.vin) == len(self.i)
assert len(tx.vout) == len(self.o)
@@ -130,7 +130,7 @@ class PSBT:
for m in self.i + self.o:
m.map.clear()
- self.g = PSBTMap(map={0: self.g.map[0]})
+ self.g = PSBTMap(map={PSBT_GLOBAL_UNSIGNED_TX: self.g.map[PSBT_GLOBAL_UNSIGNED_TX]})
def to_base64(self):
return base64.b64encode(self.serialize()).decode("utf8")
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 9620951a16..66a23b443c 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -120,8 +120,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.disable_autoconnect = True
self.set_test_params()
assert self.wallet_names is None or len(self.wallet_names) <= self.num_nodes
- if self.options.timeout_factor == 0 :
- self.options.timeout_factor = 99999
self.rpc_timeout = int(self.rpc_timeout * self.options.timeout_factor) # optionally, increase timeout by a factor
def main(self):
@@ -193,7 +191,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown. valgrind 3.14 or later required. Forces --nosandbox.")
parser.add_argument("--randomseed", type=int,
help="set a random seed for deterministically reproducing a previous test run")
- parser.add_argument('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts')
+ parser.add_argument("--timeout-factor", dest="timeout_factor", type=float, help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts")
self.add_options(parser)
# Running TestShell in a Jupyter notebook causes an additional -f argument
@@ -201,6 +199,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# source: https://stackoverflow.com/questions/48796169/how-to-fix-ipykernel-launcher-py-error-unrecognized-arguments-in-jupyter/56349168#56349168
parser.add_argument("-f", "--fff", help="a dummy argument to fool ipython", default="1")
self.options = parser.parse_args()
+ if self.options.timeout_factor == 0:
+ self.options.timeout_factor = 99999
+ self.options.timeout_factor = self.options.timeout_factor or (4 if self.options.valgrind else 1)
self.options.previous_releases_path = previous_releases_path
config = configparser.ConfigParser()
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 882f82e0f2..56abe5f26a 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -469,7 +469,7 @@ class TestNode():
return
if time.time() >= time_end:
- print_log = " - " + "\n - ".join(log.splitlines())
+ print_log = " - " + "\n - ".join(log.decode("utf8", errors="replace").splitlines())
break
# No sleep here because we want to detect the message fragment as fast as
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 1d560706c2..bcedc0c9af 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -195,6 +195,7 @@ BASE_SCRIPTS = [
'interface_http.py',
'interface_rpc.py',
'interface_usdt_coinselection.py',
+ 'interface_usdt_mempool.py',
'interface_usdt_net.py',
'interface_usdt_utxocache.py',
'interface_usdt_validation.py',
@@ -323,6 +324,7 @@ BASE_SCRIPTS = [
'mempool_compatibility.py',
'mempool_accept_wtxid.py',
'mempool_dust.py',
+ 'mempool_sigoplimit.py',
'rpc_deriveaddresses.py',
'rpc_deriveaddresses.py --usecli',
'p2p_ping.py',
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 52022a2eee..53ac01686a 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -632,7 +632,7 @@ class WalletTest(BitcoinTestFramework):
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
# Test getaddressinfo on external address. Note that these addresses are taken from disablewallet.py
- assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")
+ assert_raises_rpc_error(-5, "Invalid or unsupported Base58-encoded address.", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")
address_info = self.nodes[0].getaddressinfo("mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ")
assert_equal(address_info['address'], "mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ")
assert_equal(address_info["scriptPubKey"], "76a9144e3854046c7bd1594ac904e4793b6a45b36dea0988ac")
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index e66eb2c289..4f2db2018a 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -15,6 +15,9 @@ variants.
- `test_address()` is called to call getaddressinfo for an address on node1
and test the values returned."""
+import concurrent.futures
+
+from test_framework.authproxy import JSONRPCException
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.descriptors import descsum_create
@@ -687,11 +690,25 @@ class ImportDescriptorsTest(BitcoinTestFramework):
descriptor["timestamp"] = 0
descriptor["next_index"] = 0
- batch = []
- batch.append(encrypted_wallet.walletpassphrase.get_request("passphrase", 3))
- batch.append(encrypted_wallet.importdescriptors.get_request([descriptor]))
+ encrypted_wallet.walletpassphrase("passphrase", 99999)
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread:
+ with self.nodes[0].assert_debug_log(expected_msgs=["Rescan started from block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206... (slow variant inspecting all blocks)"], timeout=5):
+ importing = thread.submit(encrypted_wallet.importdescriptors, requests=[descriptor])
+
+ # Set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan
+ self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletpassphrase("passphrase", 1)
+
+ try:
+ self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletlock()
+ except JSONRPCException as e:
+ assert e.error["code"] == -4 and "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before locking the wallet." in e.error["message"]
+
+ try:
+ self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletpassphrasechange("passphrase", "newpassphrase")
+ except JSONRPCException as e:
+ assert e.error["code"] == -4 and "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before changing the passphrase." in e.error["message"]
- encrypted_wallet.batch(batch)
+ assert_equal(importing.result(), [{"success": True}])
assert_equal(temp_wallet.getbalance(), encrypted_wallet.getbalance())
diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py
index fb2156bda1..c5479089c6 100755
--- a/test/functional/wallet_listdescriptors.py
+++ b/test/functional/wallet_listdescriptors.py
@@ -54,7 +54,7 @@ class ListDescriptorsTest(BitcoinTestFramework):
assert_equal(4, len([d for d in result['descriptors'] if d['internal']]))
for item in result['descriptors']:
assert item['desc'] != ''
- assert item['next'] == 0
+ assert item['next_index'] == 0
assert item['range'] == [0, 0]
assert item['timestamp'] is not None
@@ -78,7 +78,8 @@ class ListDescriptorsTest(BitcoinTestFramework):
'timestamp': TIME_GENESIS_BLOCK,
'active': False,
'range': [0, 0],
- 'next': 0},
+ 'next': 0,
+ 'next_index': 0},
],
}
assert_equal(expected, wallet.listdescriptors())
@@ -92,7 +93,8 @@ class ListDescriptorsTest(BitcoinTestFramework):
'timestamp': TIME_GENESIS_BLOCK,
'active': False,
'range': [0, 0],
- 'next': 0},
+ 'next': 0,
+ 'next_index': 0},
],
}
assert_equal(expected_private, wallet.listdescriptors(True))
diff --git a/test/functional/wallet_transactiontime_rescan.py b/test/functional/wallet_transactiontime_rescan.py
index 904013cdef..ea99992084 100755
--- a/test/functional/wallet_transactiontime_rescan.py
+++ b/test/functional/wallet_transactiontime_rescan.py
@@ -5,8 +5,10 @@
"""Test transaction time during old block rescanning
"""
+import concurrent.futures
import time
+from test_framework.authproxy import JSONRPCException
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -196,14 +198,27 @@ class TransactionTimeRescanTest(BitcoinTestFramework):
minernode.createwallet("encrypted_wallet", blank=True, passphrase="passphrase", descriptors=False)
encrypted_wallet = minernode.get_wallet_rpc("encrypted_wallet")
- encrypted_wallet.walletpassphrase("passphrase", 1)
+ encrypted_wallet.walletpassphrase("passphrase", 99999)
encrypted_wallet.sethdseed(seed=hd_seed)
- batch = []
- batch.append(encrypted_wallet.walletpassphrase.get_request("passphrase", 3))
- batch.append(encrypted_wallet.rescanblockchain.get_request())
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread:
+ with minernode.assert_debug_log(expected_msgs=["Rescan started from block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206... (slow variant inspecting all blocks)"], timeout=5):
+ rescanning = thread.submit(encrypted_wallet.rescanblockchain)
- encrypted_wallet.batch(batch)
+ # set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan
+ minernode.cli("-rpcwallet=encrypted_wallet").walletpassphrase("passphrase", 1)
+
+ try:
+ minernode.cli("-rpcwallet=encrypted_wallet").walletlock()
+ except JSONRPCException as e:
+ assert e.error["code"] == -4 and "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before locking the wallet." in e.error["message"]
+
+ try:
+ minernode.cli("-rpcwallet=encrypted_wallet").walletpassphrasechange("passphrase", "newpassphrase")
+ except JSONRPCException as e:
+ assert e.error["code"] == -4 and "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before changing the passphrase." in e.error["message"]
+
+ assert_equal(rescanning.result(), {"start_height": 0, "stop_height": 803})
assert_equal(encrypted_wallet.getbalance(), temp_wallet.getbalance())