aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_bip68_sequence.py18
-rwxr-xr-xtest/functional/feature_csv_activation.py12
-rwxr-xr-xtest/functional/feature_framework_miniwallet.py49
-rwxr-xr-xtest/functional/feature_framework_unit_tests.py1
-rwxr-xr-xtest/functional/feature_taproot.py16
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py49
-rwxr-xr-xtest/functional/mempool_accept.py2
-rwxr-xr-xtest/functional/mempool_limit.py26
-rwxr-xr-xtest/functional/p2p_mutated_blocks.py2
-rwxr-xr-xtest/functional/p2p_segwit.py4
-rwxr-xr-xtest/functional/rpc_createmultisig.py11
-rwxr-xr-xtest/functional/rpc_packages.py2
-rwxr-xr-xtest/functional/rpc_rawtransaction.py23
-rwxr-xr-xtest/functional/rpc_signrawtransactionwithkey.py10
-rwxr-xr-xtest/functional/test_framework/messages.py16
-rw-r--r--test/functional/test_framework/script.py4
-rwxr-xr-xtest/functional/test_framework/script_util.py25
-rwxr-xr-xtest/functional/test_framework/test_framework.py18
-rw-r--r--test/functional/test_framework/wallet.py24
-rwxr-xr-xtest/functional/test_runner.py32
-rwxr-xr-xtest/functional/wallet_bumpfee.py25
-rwxr-xr-xtest/functional/wallet_create_tx.py4
-rwxr-xr-xtest/functional/wallet_listsinceblock.py26
-rw-r--r--test/lint/README.md15
-rw-r--r--test/lint/test_runner/src/main.rs138
-rw-r--r--test/sanitizer_suppressions/ubsan1
26 files changed, 458 insertions, 95 deletions
diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py
index 8768d4040d..14b92d6733 100755
--- a/test/functional/feature_bip68_sequence.py
+++ b/test/functional/feature_bip68_sequence.py
@@ -78,8 +78,8 @@ class BIP68Test(BitcoinTestFramework):
self.log.info("Activating BIP68 (and 112/113)")
self.activateCSV()
- self.log.info("Verifying nVersion=2 transactions are standard.")
- self.log.info("Note that nVersion=2 transactions are always standard (independent of BIP68 activation status).")
+ self.log.info("Verifying version=2 transactions are standard.")
+ self.log.info("Note that version=2 transactions are always standard (independent of BIP68 activation status).")
self.test_version2_relay()
self.log.info("Passed")
@@ -107,7 +107,7 @@ class BIP68Test(BitcoinTestFramework):
# This transaction will enable sequence-locks, so this transaction should
# fail
tx2 = CTransaction()
- tx2.nVersion = 2
+ tx2.version = 2
sequence_value = sequence_value & 0x7fffffff
tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)]
tx2.wit.vtxinwit = [CTxInWitness()]
@@ -119,7 +119,7 @@ class BIP68Test(BitcoinTestFramework):
# Setting the version back down to 1 should disable the sequence lock,
# so this should be accepted.
- tx2.nVersion = 1
+ tx2.version = 1
self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2.serialize().hex())
@@ -159,7 +159,7 @@ class BIP68Test(BitcoinTestFramework):
using_sequence_locks = False
tx = CTransaction()
- tx.nVersion = 2
+ tx.version = 2
value = 0
for j in range(num_inputs):
sequence_value = 0xfffffffe # this disables sequence locks
@@ -228,7 +228,7 @@ class BIP68Test(BitcoinTestFramework):
# Anyone-can-spend mempool tx.
# Sequence lock of 0 should pass.
tx2 = CTransaction()
- tx2.nVersion = 2
+ tx2.version = 2
tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]
self.wallet.sign_tx(tx=tx2)
@@ -246,7 +246,7 @@ class BIP68Test(BitcoinTestFramework):
sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG
tx = CTransaction()
- tx.nVersion = 2
+ tx.version = 2
tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)]
tx.wit.vtxinwit = [CTxInWitness()]
tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
@@ -360,7 +360,7 @@ class BIP68Test(BitcoinTestFramework):
# Make an anyone-can-spend transaction
tx2 = CTransaction()
- tx2.nVersion = 1
+ tx2.version = 1
tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]
@@ -376,7 +376,7 @@ class BIP68Test(BitcoinTestFramework):
sequence_value = 100 # 100 block relative locktime
tx3 = CTransaction()
- tx3.nVersion = 2
+ tx3.version = 2
tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)]
tx3.wit.vtxinwit = [CTxInWitness()]
tx3.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py
index bc1f9e8f2f..2db9682931 100755
--- a/test/functional/feature_csv_activation.py
+++ b/test/functional/feature_csv_activation.py
@@ -110,7 +110,7 @@ class BIP68_112_113Test(BitcoinTestFramework):
def create_bip112special(self, input, txversion):
tx = self.create_self_transfer_from_utxo(input)
- tx.nVersion = txversion
+ tx.version = txversion
self.miniwallet.sign_tx(tx)
tx.vin[0].scriptSig = CScript([-1, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))
tx.rehash()
@@ -118,7 +118,7 @@ class BIP68_112_113Test(BitcoinTestFramework):
def create_bip112emptystack(self, input, txversion):
tx = self.create_self_transfer_from_utxo(input)
- tx.nVersion = txversion
+ tx.version = txversion
self.miniwallet.sign_tx(tx)
tx.vin[0].scriptSig = CScript([OP_CHECKSEQUENCEVERIFY] + list(CScript(tx.vin[0].scriptSig)))
tx.rehash()
@@ -136,7 +136,7 @@ class BIP68_112_113Test(BitcoinTestFramework):
for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)):
locktime = relative_locktime(sdf, srhb, stf, srlb)
tx = self.create_self_transfer_from_utxo(bip68inputs[i])
- tx.nVersion = txversion
+ tx.version = txversion
tx.vin[0].nSequence = locktime + locktime_delta
self.miniwallet.sign_tx(tx)
txs.append({'tx': tx, 'sdf': sdf, 'stf': stf})
@@ -154,7 +154,7 @@ class BIP68_112_113Test(BitcoinTestFramework):
tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME + locktime_delta
else: # vary nSequence instead, OP_CSV is fixed
tx.vin[0].nSequence = locktime + locktime_delta
- tx.nVersion = txversion
+ tx.version = txversion
self.miniwallet.sign_tx(tx)
if varyOP_CSV:
tx.vin[0].scriptSig = CScript([locktime, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))
@@ -257,10 +257,10 @@ class BIP68_112_113Test(BitcoinTestFramework):
# BIP113 test transaction will be modified before each use to put in appropriate block time
bip113tx_v1 = self.create_self_transfer_from_utxo(bip113input)
bip113tx_v1.vin[0].nSequence = 0xFFFFFFFE
- bip113tx_v1.nVersion = 1
+ bip113tx_v1.version = 1
bip113tx_v2 = self.create_self_transfer_from_utxo(bip113input)
bip113tx_v2.vin[0].nSequence = 0xFFFFFFFE
- bip113tx_v2.nVersion = 2
+ bip113tx_v2.version = 2
# For BIP68 test all 16 relative sequence locktimes
bip68txs_v1 = self.create_bip68txs(bip68inputs, 1)
diff --git a/test/functional/feature_framework_miniwallet.py b/test/functional/feature_framework_miniwallet.py
new file mode 100755
index 0000000000..f108289018
--- /dev/null
+++ b/test/functional/feature_framework_miniwallet.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# Copyright (c) 2024 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 MiniWallet."""
+from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_greater_than_or_equal,
+)
+from test_framework.wallet import (
+ MiniWallet,
+ MiniWalletMode,
+)
+
+
+class FeatureFrameworkMiniWalletTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def test_tx_padding(self):
+ """Verify that MiniWallet's transaction padding (`target_weight` parameter)
+ works accurately enough (i.e. at most 3 WUs higher) with all modes."""
+ for mode_name, wallet in self.wallets:
+ self.log.info(f"Test tx padding with MiniWallet mode {mode_name}...")
+ utxo = wallet.get_utxo(mark_as_spent=False)
+ for target_weight in [1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 4000000,
+ 989, 2001, 4337, 13371, 23219, 49153, 102035, 223419, 3999989]:
+ tx = wallet.create_self_transfer(utxo_to_spend=utxo, target_weight=target_weight)["tx"]
+ self.log.debug(f"-> target weight: {target_weight}, actual weight: {tx.get_weight()}")
+ assert_greater_than_or_equal(tx.get_weight(), target_weight)
+ assert_greater_than_or_equal(target_weight + 3, tx.get_weight())
+
+ def run_test(self):
+ node = self.nodes[0]
+ self.wallets = [
+ ("ADDRESS_OP_TRUE", MiniWallet(node, mode=MiniWalletMode.ADDRESS_OP_TRUE)),
+ ("RAW_OP_TRUE", MiniWallet(node, mode=MiniWalletMode.RAW_OP_TRUE)),
+ ("RAW_P2PK", MiniWallet(node, mode=MiniWalletMode.RAW_P2PK)),
+ ]
+ for _, wallet in self.wallets:
+ self.generate(wallet, 10)
+ self.generate(wallet, COINBASE_MATURITY)
+
+ self.test_tx_padding()
+
+
+if __name__ == '__main__':
+ FeatureFrameworkMiniWalletTest().main()
diff --git a/test/functional/feature_framework_unit_tests.py b/test/functional/feature_framework_unit_tests.py
index f03f084bed..14d83f8a70 100755
--- a/test/functional/feature_framework_unit_tests.py
+++ b/test/functional/feature_framework_unit_tests.py
@@ -27,6 +27,7 @@ TEST_FRAMEWORK_MODULES = [
"crypto.ripemd160",
"crypto.secp256k1",
"script",
+ "script_util",
"segwit_addr",
"wallet_util",
]
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index e7d65b4539..1a0844d240 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -1408,10 +1408,10 @@ class TaprootTest(BitcoinTestFramework):
left = done
while left:
- # Construct CTransaction with random nVersion, nLocktime
+ # Construct CTransaction with random version, nLocktime
tx = CTransaction()
- tx.nVersion = random.choice([1, 2, random.randint(-0x80000000, 0x7fffffff)])
- min_sequence = (tx.nVersion != 1 and tx.nVersion != 0) * 0x80000000 # The minimum sequence number to disable relative locktime
+ tx.version = random.choice([1, 2, random.getrandbits(32)])
+ min_sequence = (tx.version != 1 and tx.version != 0) * 0x80000000 # The minimum sequence number to disable relative locktime
if random.choice([True, False]):
tx.nLockTime = random.randrange(LOCKTIME_THRESHOLD, self.lastblocktime - 7200) # all absolute locktimes in the past
else:
@@ -1502,8 +1502,8 @@ class TaprootTest(BitcoinTestFramework):
is_standard_tx = (
fail_input is None # Must be valid to be standard
and (all(utxo.spender.is_standard for utxo in input_utxos)) # All inputs must be standard
- and tx.nVersion >= 1 # The tx version must be standard
- and tx.nVersion <= 2)
+ and tx.version >= 1 # The tx version must be standard
+ and tx.version <= 2)
tx.rehash()
msg = ','.join(utxo.spender.comment + ("*" if n == fail_input else "") for n, utxo in enumerate(input_utxos))
if is_standard_tx:
@@ -1530,7 +1530,7 @@ class TaprootTest(BitcoinTestFramework):
# Deterministically mine coins to OP_TRUE in block 1
assert_equal(self.nodes[0].getblockcount(), 0)
coinbase = CTransaction()
- coinbase.nVersion = 1
+ coinbase.version = 1
coinbase.vin = [CTxIn(COutPoint(0, 0xffffffff), CScript([OP_1, OP_1]), SEQUENCE_FINAL)]
coinbase.vout = [CTxOut(5000000000, CScript([OP_1]))]
coinbase.nLockTime = 0
@@ -1622,7 +1622,7 @@ class TaprootTest(BitcoinTestFramework):
for i, spk in enumerate(old_spks + tap_spks):
val = 42000000 * (i + 7)
tx = CTransaction()
- tx.nVersion = 1
+ tx.version = 1
tx.vin = [CTxIn(COutPoint(lasttxid, i & 1), CScript([]), SEQUENCE_FINAL)]
tx.vout = [CTxOut(val, spk), CTxOut(amount - val, CScript([OP_1]))]
if i & 1:
@@ -1679,7 +1679,7 @@ class TaprootTest(BitcoinTestFramework):
# Construct a deterministic transaction spending all outputs created above.
tx = CTransaction()
- tx.nVersion = 2
+ tx.version = 2
tx.vin = []
inputs = []
input_spks = [tap_spks[0], tap_spks[1], old_spks[0], tap_spks[2], tap_spks[5], old_spks[2], tap_spks[6], tap_spks[3], tap_spks[4]]
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 83bb5121e5..a6628dcbf3 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -8,6 +8,7 @@ from decimal import Decimal
import re
from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.netutil import test_ipv6_local
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -15,6 +16,7 @@ from test_framework.util import (
assert_raises_process_error,
assert_raises_rpc_error,
get_auth_cookie,
+ rpc_port,
)
import time
@@ -107,6 +109,53 @@ class TestBitcoinCli(BitcoinTestFramework):
self.log.info("Test connecting to a non-existing server")
assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo)
+ self.log.info("Test handling of invalid ports in rpcconnect")
+ assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:notaport", self.nodes[0].cli("-rpcconnect=127.0.0.1:notaport").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:-1", self.nodes[0].cli("-rpcconnect=127.0.0.1:-1").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:0", self.nodes[0].cli("-rpcconnect=127.0.0.1:0").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:65536", self.nodes[0].cli("-rpcconnect=127.0.0.1:65536").echo)
+
+ self.log.info("Checking for IPv6")
+ have_ipv6 = test_ipv6_local()
+ if not have_ipv6:
+ self.log.info("Skipping IPv6 tests")
+
+ if have_ipv6:
+ assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:notaport", self.nodes[0].cli("-rpcconnect=[::1]:notaport").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:-1", self.nodes[0].cli("-rpcconnect=[::1]:-1").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:0", self.nodes[0].cli("-rpcconnect=[::1]:0").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:65536", self.nodes[0].cli("-rpcconnect=[::1]:65536").echo)
+
+ self.log.info("Test handling of invalid ports in rpcport")
+ assert_raises_process_error(1, "Invalid port provided in -rpcport: notaport", self.nodes[0].cli("-rpcport=notaport").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcport: -1", self.nodes[0].cli("-rpcport=-1").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcport: 0", self.nodes[0].cli("-rpcport=0").echo)
+ assert_raises_process_error(1, "Invalid port provided in -rpcport: 65536", self.nodes[0].cli("-rpcport=65536").echo)
+
+ self.log.info("Test port usage preferences")
+ node_rpc_port = rpc_port(self.nodes[0].index)
+ # Prevent bitcoin-cli from using existing rpcport in conf
+ conf_rpcport = "rpcport=" + str(node_rpc_port)
+ self.nodes[0].replace_in_config([(conf_rpcport, "#" + conf_rpcport)])
+ # prefer rpcport over rpcconnect
+ assert_raises_process_error(1, "Could not connect to the server 127.0.0.1:1", self.nodes[0].cli(f"-rpcconnect=127.0.0.1:{node_rpc_port}", "-rpcport=1").echo)
+ if have_ipv6:
+ assert_raises_process_error(1, "Could not connect to the server ::1:1", self.nodes[0].cli(f"-rpcconnect=[::1]:{node_rpc_port}", "-rpcport=1").echo)
+
+ assert_equal(BLOCKS, self.nodes[0].cli("-rpcconnect=127.0.0.1:18999", f'-rpcport={node_rpc_port}').getblockcount())
+ if have_ipv6:
+ assert_equal(BLOCKS, self.nodes[0].cli("-rpcconnect=[::1]:18999", f'-rpcport={node_rpc_port}').getblockcount())
+
+ # prefer rpcconnect port over default
+ assert_equal(BLOCKS, self.nodes[0].cli(f"-rpcconnect=127.0.0.1:{node_rpc_port}").getblockcount())
+ if have_ipv6:
+ assert_equal(BLOCKS, self.nodes[0].cli(f"-rpcconnect=[::1]:{node_rpc_port}").getblockcount())
+
+ # prefer rpcport over default
+ assert_equal(BLOCKS, self.nodes[0].cli(f'-rpcport={node_rpc_port}').getblockcount())
+ # Re-enable rpcport in conf if present
+ self.nodes[0].replace_in_config([("#" + conf_rpcport, conf_rpcport)])
+
self.log.info("Test connecting with non-existing RPC cookie file")
assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli('-rpccookiefile=does-not-exist', '-rpcpassword=').echo)
diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py
index 2de4cf1e10..3d205ffa62 100755
--- a/test/functional/mempool_accept.py
+++ b/test/functional/mempool_accept.py
@@ -287,7 +287,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
self.log.info('Some nonstandard transactions')
tx = tx_from_hex(raw_tx_reference)
- tx.nVersion = 4 # A version currently non-standard
+ tx.version = 4 # A version currently non-standard
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'version'}],
rawtxs=[tx.serialize().hex()],
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
index d46924f4ce..49a0a32c45 100755
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -59,7 +59,7 @@ class MempoolLimitTest(BitcoinTestFramework):
mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"]
tx_A = self.wallet.send_self_transfer(
from_node=node,
- fee=(mempoolmin_feerate / 1000) * (A_weight // 4) + Decimal('0.000001'),
+ fee_rate=mempoolmin_feerate,
target_weight=A_weight,
utxo_to_spend=rbf_utxo,
confirmed_only=True
@@ -77,7 +77,7 @@ class MempoolLimitTest(BitcoinTestFramework):
non_cpfp_carveout_weight = 40001 # EXTRA_DESCENDANT_TX_SIZE_LIMIT + 1
tx_C = self.wallet.create_self_transfer(
target_weight=non_cpfp_carveout_weight,
- fee = (mempoolmin_feerate / 1000) * (non_cpfp_carveout_weight // 4) + Decimal('0.000001'),
+ fee_rate=mempoolmin_feerate,
utxo_to_spend=tx_B["new_utxo"],
confirmed_only=True
)
@@ -109,7 +109,7 @@ class MempoolLimitTest(BitcoinTestFramework):
# happen in the middle of package evaluation, as it can invalidate the coins cache.
mempool_evicted_tx = self.wallet.send_self_transfer(
from_node=node,
- fee=(mempoolmin_feerate / 1000) * (evicted_weight // 4) + Decimal('0.000001'),
+ fee_rate=mempoolmin_feerate,
target_weight=evicted_weight,
confirmed_only=True
)
@@ -135,11 +135,11 @@ class MempoolLimitTest(BitcoinTestFramework):
parent_weight = 100000
num_big_parents = 3
assert_greater_than(parent_weight * num_big_parents, current_info["maxmempool"] - current_info["bytes"])
- parent_fee = (100 * mempoolmin_feerate / 1000) * (parent_weight // 4)
+ parent_feerate = 100 * mempoolmin_feerate
big_parent_txids = []
for i in range(num_big_parents):
- parent = self.wallet.create_self_transfer(fee=parent_fee, target_weight=parent_weight, confirmed_only=True)
+ parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_weight=parent_weight, confirmed_only=True)
parent_utxos.append(parent["new_utxo"])
package_hex.append(parent["hex"])
big_parent_txids.append(parent["txid"])
@@ -314,18 +314,20 @@ class MempoolLimitTest(BitcoinTestFramework):
target_weight_each = 200000
assert_greater_than(target_weight_each * 2, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"])
# Should be a true CPFP: parent's feerate is just below mempool min feerate
- parent_fee = (mempoolmin_feerate / 1000) * (target_weight_each // 4) - Decimal("0.00001")
+ parent_feerate = mempoolmin_feerate - Decimal("0.000001") # 0.1 sats/vbyte below min feerate
# Parent + child is above mempool minimum feerate
- child_fee = (worst_feerate_btcvb) * (target_weight_each // 4) - Decimal("0.00001")
+ child_feerate = (worst_feerate_btcvb * 1000) - Decimal("0.000001") # 0.1 sats/vbyte below worst feerate
# However, when eviction is triggered, these transactions should be at the bottom.
# This assertion assumes parent and child are the same size.
miniwallet.rescan_utxos()
- tx_parent_just_below = miniwallet.create_self_transfer(fee=parent_fee, target_weight=target_weight_each)
- tx_child_just_above = miniwallet.create_self_transfer(utxo_to_spend=tx_parent_just_below["new_utxo"], fee=child_fee, target_weight=target_weight_each)
+ tx_parent_just_below = miniwallet.create_self_transfer(fee_rate=parent_feerate, target_weight=target_weight_each)
+ tx_child_just_above = miniwallet.create_self_transfer(utxo_to_spend=tx_parent_just_below["new_utxo"], fee_rate=child_feerate, target_weight=target_weight_each)
# This package ranks below the lowest descendant package in the mempool
- assert_greater_than(worst_feerate_btcvb, (parent_fee + child_fee) / (tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()))
- assert_greater_than(mempoolmin_feerate, (parent_fee) / (tx_parent_just_below["tx"].get_vsize()))
- assert_greater_than((parent_fee + child_fee) / (tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()), mempoolmin_feerate / 1000)
+ package_fee = tx_parent_just_below["fee"] + tx_child_just_above["fee"]
+ package_vsize = tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()
+ assert_greater_than(worst_feerate_btcvb, package_fee / package_vsize)
+ assert_greater_than(mempoolmin_feerate, tx_parent_just_below["fee"] / (tx_parent_just_below["tx"].get_vsize()))
+ assert_greater_than(package_fee / package_vsize, mempoolmin_feerate / 1000)
res = node.submitpackage([tx_parent_just_below["hex"], tx_child_just_above["hex"]])
for wtxid in [tx_parent_just_below["wtxid"], tx_child_just_above["wtxid"]]:
assert_equal(res["tx-results"][wtxid]["error"], "mempool full")
diff --git a/test/functional/p2p_mutated_blocks.py b/test/functional/p2p_mutated_blocks.py
index 737edaf5bf..64d2fc96a8 100755
--- a/test/functional/p2p_mutated_blocks.py
+++ b/test/functional/p2p_mutated_blocks.py
@@ -55,7 +55,7 @@ class MutatedBlocksTest(BitcoinTestFramework):
# Create mutated version of the block by changing the transaction
# version on the self-transfer.
mutated_block = copy.deepcopy(block)
- mutated_block.vtx[1].nVersion = 4
+ mutated_block.vtx[1].version = 4
# Announce the new block via a compact block through the honest relayer
cmpctblock = HeaderAndShortIDs()
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index b63c2c7a8d..d20cf41a72 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -1164,7 +1164,7 @@ class SegWitTest(BitcoinTestFramework):
if not self.wit.is_null():
flags |= 1
r = b""
- r += self.nVersion.to_bytes(4, "little", signed=True)
+ r += self.version.to_bytes(4, "little")
if flags:
dummy = []
r += ser_vector(dummy)
@@ -1975,7 +1975,7 @@ class SegWitTest(BitcoinTestFramework):
def serialize_with_bogus_witness(tx):
flags = 3
r = b""
- r += tx.nVersion.to_bytes(4, "little", signed=True)
+ r += tx.version.to_bytes(4, "little")
if flags:
dummy = []
r += ser_vector(dummy)
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index fdac3623d3..37656341d2 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -12,6 +12,7 @@ from test_framework.address import address_to_scriptpubkey
from test_framework.descriptors import descsum_create, drop_origins
from test_framework.key import ECPubKey
from test_framework.messages import COIN
+from test_framework.script_util import keys_to_multisig_script
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_raises_rpc_error,
@@ -69,6 +70,16 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
# Check that bech32m is currently not allowed
assert_raises_rpc_error(-5, "createmultisig cannot create bech32m multisig addresses", self.nodes[0].createmultisig, 2, self.pub, "bech32m")
+ self.log.info('Check correct encoding of multisig script for all n (1..20)')
+ for nkeys in range(1, 20+1):
+ keys = [self.pub[0]]*nkeys
+ expected_ms_script = keys_to_multisig_script(keys, k=nkeys) # simply use n-of-n
+ # note that the 'legacy' address type fails for n values larger than 15
+ # due to exceeding the P2SH size limit (520 bytes), so we use 'bech32' instead
+ # (for the purpose of this encoding test, we don't care about the resulting address)
+ res = self.nodes[0].createmultisig(nrequired=nkeys, keys=keys, address_type='bech32')
+ assert_equal(res['redeemScript'], expected_ms_script.hex())
+
def check_addmultisigaddress_errors(self):
if self.options.descriptors:
return
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 113424c0a6..1acd586d2c 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -394,7 +394,7 @@ class RPCPackagesTest(BitcoinTestFramework):
peer = node.add_p2p_connection(P2PTxInvStore())
txs = self.wallet.create_self_transfer_chain(chain_length=2)
bad_child = tx_from_hex(txs[1]["hex"])
- bad_child.nVersion = -1
+ bad_child.version = 0xffffffff
hex_partial_acceptance = [txs[0]["hex"], bad_child.serialize().hex()]
res = node.submitpackage(hex_partial_acceptance)
assert_equal(res["package_msg"], "transaction failed")
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 3978c80dde..f974a05f7b 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -463,20 +463,34 @@ class RawTransactionsTest(BitcoinTestFramework):
self.log.info("Test transaction version numbers")
# Test the minimum transaction version number that fits in a signed 32-bit integer.
- # As transaction version is unsigned, this should convert to its unsigned equivalent.
+ # As transaction version is serialized unsigned, this should convert to its unsigned equivalent.
tx = CTransaction()
- tx.nVersion = -0x80000000
+ tx.version = 0x80000000
rawtx = tx.serialize().hex()
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['version'], 0x80000000)
# Test the maximum transaction version number that fits in a signed 32-bit integer.
tx = CTransaction()
- tx.nVersion = 0x7fffffff
+ tx.version = 0x7fffffff
rawtx = tx.serialize().hex()
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['version'], 0x7fffffff)
+ # Test the minimum transaction version number that fits in an unsigned 32-bit integer.
+ tx = CTransaction()
+ tx.version = 0
+ rawtx = tx.serialize().hex()
+ decrawtx = self.nodes[0].decoderawtransaction(rawtx)
+ assert_equal(decrawtx['version'], 0)
+
+ # Test the maximum transaction version number that fits in an unsigned 32-bit integer.
+ tx = CTransaction()
+ tx.version = 0xffffffff
+ rawtx = tx.serialize().hex()
+ decrawtx = self.nodes[0].decoderawtransaction(rawtx)
+ assert_equal(decrawtx['version'], 0xffffffff)
+
def raw_multisig_transaction_legacy_tests(self):
self.log.info("Test raw multisig transactions (legacy)")
# The traditional multisig workflow does not work with descriptor wallets so these are legacy only.
@@ -585,6 +599,8 @@ class RawTransactionsTest(BitcoinTestFramework):
rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet(rawTx2, inputs)
self.log.debug(rawTxPartialSigned2)
assert_equal(rawTxPartialSigned2['complete'], False) # node2 only has one key, can't comp. sign the tx
+ assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].combinerawtransaction, [rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex'] + "00"])
+ assert_raises_rpc_error(-22, "Missing transactions", self.nodes[0].combinerawtransaction, [])
rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
self.log.debug(rawTxComb)
self.nodes[2].sendrawtransaction(rawTxComb)
@@ -592,6 +608,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.sync_all()
self.generate(self.nodes[0], 1)
assert_equal(self.nodes[0].getbalance(), bal + Decimal('50.00000000') + Decimal('2.19000000')) # block reward + tx
+ assert_raises_rpc_error(-25, "Input not found or already spent", self.nodes[0].combinerawtransaction, [rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
if __name__ == '__main__':
diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py
index 268584331e..f4fec13495 100755
--- a/test/functional/rpc_signrawtransactionwithkey.py
+++ b/test/functional/rpc_signrawtransactionwithkey.py
@@ -126,10 +126,20 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
privkeys = [self.nodes[0].get_deterministic_priv_key().key]
assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[0].signrawtransactionwithkey, tx, privkeys, sighashtype="all")
+ def invalid_private_key_and_tx(self):
+ self.log.info("Test signing transaction with an invalid private key")
+ tx = self.nodes[0].createrawtransaction(INPUTS, OUTPUTS)
+ privkeys = ["123"]
+ assert_raises_rpc_error(-5, "Invalid private key", self.nodes[0].signrawtransactionwithkey, tx, privkeys)
+ self.log.info("Test signing transaction with an invalid tx hex")
+ privkeys = [self.nodes[0].get_deterministic_priv_key().key]
+ assert_raises_rpc_error(-22, "TX decode failed. Make sure the tx has at least one input.", self.nodes[0].signrawtransactionwithkey, tx + "00", privkeys)
+
def run_test(self):
self.successful_signing_test()
self.witness_script_test()
self.invalid_sighashtype_test()
+ self.invalid_private_key_and_tx()
if __name__ == '__main__':
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 4e496a9275..005f7546a8 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -560,12 +560,12 @@ class CTxWitness:
class CTransaction:
- __slots__ = ("hash", "nLockTime", "nVersion", "sha256", "vin", "vout",
+ __slots__ = ("hash", "nLockTime", "version", "sha256", "vin", "vout",
"wit")
def __init__(self, tx=None):
if tx is None:
- self.nVersion = 2
+ self.version = 2
self.vin = []
self.vout = []
self.wit = CTxWitness()
@@ -573,7 +573,7 @@ class CTransaction:
self.sha256 = None
self.hash = None
else:
- self.nVersion = tx.nVersion
+ self.version = tx.version
self.vin = copy.deepcopy(tx.vin)
self.vout = copy.deepcopy(tx.vout)
self.nLockTime = tx.nLockTime
@@ -582,7 +582,7 @@ class CTransaction:
self.wit = copy.deepcopy(tx.wit)
def deserialize(self, f):
- self.nVersion = int.from_bytes(f.read(4), "little", signed=True)
+ self.version = int.from_bytes(f.read(4), "little")
self.vin = deser_vector(f, CTxIn)
flags = 0
if len(self.vin) == 0:
@@ -605,7 +605,7 @@ class CTransaction:
def serialize_without_witness(self):
r = b""
- r += self.nVersion.to_bytes(4, "little", signed=True)
+ r += self.version.to_bytes(4, "little")
r += ser_vector(self.vin)
r += ser_vector(self.vout)
r += self.nLockTime.to_bytes(4, "little")
@@ -617,7 +617,7 @@ class CTransaction:
if not self.wit.is_null():
flags |= 1
r = b""
- r += self.nVersion.to_bytes(4, "little", signed=True)
+ r += self.version.to_bytes(4, "little")
if flags:
dummy = []
r += ser_vector(dummy)
@@ -677,8 +677,8 @@ class CTransaction:
return math.ceil(self.get_weight() / WITNESS_SCALE_FACTOR)
def __repr__(self):
- return "CTransaction(nVersion=%i vin=%s vout=%s wit=%s nLockTime=%i)" \
- % (self.nVersion, repr(self.vin), repr(self.vout), repr(self.wit), self.nLockTime)
+ return "CTransaction(version=%i vin=%s vout=%s wit=%s nLockTime=%i)" \
+ % (self.version, repr(self.vin), repr(self.vout), repr(self.wit), self.nLockTime)
class CBlockHeader:
diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py
index ab3dc2ffb1..97d62f957b 100644
--- a/test/functional/test_framework/script.py
+++ b/test/functional/test_framework/script.py
@@ -738,7 +738,7 @@ def SegwitV0SignatureMsg(script, txTo, inIdx, hashtype, amount):
hashOutputs = uint256_from_str(hash256(serialize_outputs))
ss = bytes()
- ss += txTo.nVersion.to_bytes(4, "little", signed=True)
+ ss += txTo.version.to_bytes(4, "little")
ss += ser_uint256(hashPrevouts)
ss += ser_uint256(hashSequence)
ss += txTo.vin[inIdx].prevout.serialize()
@@ -817,7 +817,7 @@ def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index = 0, scriptpat
in_type = hash_type & SIGHASH_ANYONECANPAY
spk = spent_utxos[input_index].scriptPubKey
ss = bytes([0, hash_type]) # epoch, hash_type
- ss += txTo.nVersion.to_bytes(4, "little", signed=True)
+ ss += txTo.version.to_bytes(4, "little")
ss += txTo.nLockTime.to_bytes(4, "little")
if in_type != SIGHASH_ANYONECANPAY:
ss += BIP341_sha_prevouts(txTo)
diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py
index 62894cc0f4..855f3b8cf5 100755
--- a/test/functional/test_framework/script_util.py
+++ b/test/functional/test_framework/script_util.py
@@ -3,10 +3,13 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Useful Script constants and utils."""
+import unittest
+
from test_framework.script import (
CScript,
- CScriptOp,
OP_0,
+ OP_15,
+ OP_16,
OP_CHECKMULTISIG,
OP_CHECKSIG,
OP_DUP,
@@ -49,10 +52,8 @@ def keys_to_multisig_script(keys, *, k=None):
if k is None: # n-of-n multisig by default
k = n
assert k <= n
- op_k = CScriptOp.encode_op_n(k)
- op_n = CScriptOp.encode_op_n(n)
checked_keys = [check_key(key) for key in keys]
- return CScript([op_k] + checked_keys + [op_n, OP_CHECKMULTISIG])
+ return CScript([k] + checked_keys + [n, OP_CHECKMULTISIG])
def keyhash_to_p2pkh_script(hash):
@@ -125,3 +126,19 @@ def check_script(script):
if isinstance(script, bytes) or isinstance(script, CScript):
return script
assert False
+
+
+class TestFrameworkScriptUtil(unittest.TestCase):
+ def test_multisig(self):
+ fake_pubkey = bytes([0]*33)
+ # check correct encoding of P2MS script with n,k <= 16
+ normal_ms_script = keys_to_multisig_script([fake_pubkey]*16, k=15)
+ self.assertEqual(len(normal_ms_script), 1 + 16*34 + 1 + 1)
+ self.assertTrue(normal_ms_script.startswith(bytes([OP_15])))
+ self.assertTrue(normal_ms_script.endswith(bytes([OP_16, OP_CHECKMULTISIG])))
+
+ # check correct encoding of P2MS script with n,k > 16
+ max_ms_script = keys_to_multisig_script([fake_pubkey]*20, k=19)
+ self.assertEqual(len(max_ms_script), 2 + 20*34 + 2 + 1)
+ self.assertTrue(max_ms_script.startswith(bytes([1, 19]))) # using OP_PUSH1
+ self.assertTrue(max_ms_script.endswith(bytes([1, 20, OP_CHECKMULTISIG])))
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 4231cbf495..9e44a11143 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2014-2022 The Bitcoin Core developers
+# Copyright (c) 2014-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Base class for RPC testing."""
@@ -636,11 +636,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def find_conn(node, peer_subversion, inbound):
return next(filter(lambda peer: peer['subver'] == peer_subversion and peer['inbound'] == inbound, node.getpeerinfo()), None)
- # poll until version handshake complete to avoid race conditions
- # with transaction relaying
- # See comments in net_processing:
- # * Must have a version message before anything else
- # * Must have a verack message before anything else
self.wait_until(lambda: find_conn(from_connection, to_connection_subver, inbound=False) is not None)
self.wait_until(lambda: find_conn(to_connection, from_connection_subver, inbound=True) is not None)
@@ -648,11 +643,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
assert peer is not None, "Error: peer disconnected"
return peer['bytesrecv_per_msg'].pop(msg_type, 0) >= min_bytes_recv
- self.wait_until(lambda: check_bytesrecv(find_conn(from_connection, to_connection_subver, inbound=False), 'verack', 21))
- self.wait_until(lambda: check_bytesrecv(find_conn(to_connection, from_connection_subver, inbound=True), 'verack', 21))
-
- # The message bytes are counted before processing the message, so make
- # sure it was fully processed by waiting for a ping.
+ # Poll until version handshake (fSuccessfullyConnected) is complete to
+ # avoid race conditions, because some message types are blocked from
+ # being sent or received before fSuccessfullyConnected.
+ #
+ # As the flag fSuccessfullyConnected is not exposed, check it by
+ # waiting for a pong, which can only happen after the flag was set.
self.wait_until(lambda: check_bytesrecv(find_conn(from_connection, to_connection_subver, inbound=False), 'pong', 29))
self.wait_until(lambda: check_bytesrecv(find_conn(to_connection, from_connection_subver, inbound=True), 'pong', 29))
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index 4433cbcc55..cb0d291361 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -7,6 +7,7 @@
from copy import deepcopy
from decimal import Decimal
from enum import Enum
+import math
from typing import (
Any,
Optional,
@@ -33,10 +34,13 @@ from test_framework.messages import (
CTxInWitness,
CTxOut,
hash256,
+ ser_compact_size,
+ WITNESS_SCALE_FACTOR,
)
from test_framework.script import (
CScript,
LEAF_VERSION_TAPSCRIPT,
+ OP_1,
OP_NOP,
OP_RETURN,
OP_TRUE,
@@ -52,6 +56,7 @@ from test_framework.script_util import (
from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
+ get_fee,
)
from test_framework.wallet_util import generate_keypair
@@ -119,13 +124,16 @@ class MiniWallet:
"""Pad a transaction with extra outputs until it reaches a target weight (or higher).
returns the tx
"""
- tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, b'a'])))
+ tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN])))
+ # determine number of needed padding bytes by converting weight difference to vbytes
dummy_vbytes = (target_weight - tx.get_weight() + 3) // 4
- tx.vout[-1].scriptPubKey = CScript([OP_RETURN, b'a' * dummy_vbytes])
- # Lower bound should always be off by at most 3
+ # compensate for the increase of the compact-size encoded script length
+ # (note that the length encoding of the unpadded output script needs one byte)
+ dummy_vbytes -= len(ser_compact_size(dummy_vbytes)) - 1
+ tx.vout[-1].scriptPubKey = CScript([OP_RETURN] + [OP_1] * dummy_vbytes)
+ # Actual weight should be at most 3 higher than target weight
assert_greater_than_or_equal(tx.get_weight(), target_weight)
- # Higher bound should always be off by at most 3 + 12 weight (for encoding the length)
- assert_greater_than_or_equal(target_weight + 15, tx.get_weight())
+ assert_greater_than_or_equal(target_weight + 3, tx.get_weight())
def get_balance(self):
return sum(u['value'] for u in self._utxos)
@@ -321,7 +329,7 @@ class MiniWallet:
tx = CTransaction()
tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=seq) for utxo_to_spend, seq in zip(utxos_to_spend, sequence)]
tx.vout = [CTxOut(amount_per_output, bytearray(self._scriptPubKey)) for _ in range(num_outputs)]
- tx.nVersion = version
+ tx.version = version
tx.nLockTime = locktime
self.sign_tx(tx)
@@ -367,6 +375,10 @@ class MiniWallet:
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
else:
assert False
+ if target_weight and not fee: # respect fee_rate if target weight is passed
+ # the actual weight might be off by 3 WUs, so calculate based on that (see self._bulk_tx)
+ max_actual_weight = target_weight + 3
+ fee = get_fee(math.ceil(max_actual_weight / WITNESS_SCALE_FACTOR), fee_rate)
send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000))
# create tx
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index e9632b243e..8475dc5faa 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -15,8 +15,10 @@ For a description of arguments recognized by test scripts, see
import argparse
from collections import deque
import configparser
+import csv
import datetime
import os
+import pathlib
import platform
import time
import shutil
@@ -362,6 +364,7 @@ BASE_SCRIPTS = [
'feature_addrman.py',
'feature_asmap.py',
'feature_fastprune.py',
+ 'feature_framework_miniwallet.py',
'mempool_unbroadcast.py',
'mempool_compatibility.py',
'mempool_accept_wtxid.py',
@@ -439,6 +442,7 @@ def main():
parser.add_argument('--filter', help='filter scripts to run by regular expression')
parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true",
help="Leave bitcoinds and test.* datadir on exit or error")
+ parser.add_argument('--resultsfile', '-r', help='store test results (as CSV) to the provided file')
args, unknown_args = parser.parse_known_args()
@@ -471,6 +475,13 @@ def main():
logging.debug("Temporary test directory at %s" % tmpdir)
+ results_filepath = None
+ if args.resultsfile:
+ results_filepath = pathlib.Path(args.resultsfile)
+ # Stop early if the parent directory doesn't exist
+ assert results_filepath.parent.exists(), "Results file parent directory does not exist"
+ logging.debug("Test results will be written to " + str(results_filepath))
+
enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND")
if not enable_bitcoind:
@@ -557,9 +568,10 @@ def main():
combined_logs_len=args.combinedlogslen,
failfast=args.failfast,
use_term_control=args.ansi,
+ results_filepath=results_filepath,
)
-def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control):
+def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, results_filepath=None):
args = args or []
# Warn if bitcoind is already running
@@ -651,11 +663,14 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
break
if "[Errno 28] No space left on device" in stdout:
- sys.exit(f"Early exiting after test failure due to insuffient free space in {tmpdir}\n"
+ sys.exit(f"Early exiting after test failure due to insufficient free space in {tmpdir}\n"
f"Test execution data left in {tmpdir}.\n"
f"Additional storage is needed to execute testing.")
- print_results(test_results, max_len_name, (int(time.time() - start_time)))
+ runtime = int(time.time() - start_time)
+ print_results(test_results, max_len_name, runtime)
+ if results_filepath:
+ write_results(test_results, results_filepath, runtime)
if coverage:
coverage_passed = coverage.report_rpc_coverage()
@@ -702,6 +717,17 @@ def print_results(test_results, max_len_name, runtime):
results += "Runtime: %s s\n" % (runtime)
print(results)
+
+def write_results(test_results, filepath, total_runtime):
+ with open(filepath, mode="w", encoding="utf8") as results_file:
+ results_writer = csv.writer(results_file)
+ results_writer.writerow(['test', 'status', 'duration(seconds)'])
+ all_passed = True
+ for test_result in test_results:
+ all_passed = all_passed and test_result.was_successful
+ results_writer.writerow([test_result.name, test_result.status, str(test_result.time)])
+ results_writer.writerow(['ALL', ("Passed" if all_passed else "Failed"), str(total_runtime)])
+
class TestHandler:
"""
Trigger the test scripts passed in via the list.
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 5b7db55f45..6d45adc823 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -117,6 +117,7 @@ class BumpFeeTest(BitcoinTestFramework):
# Context independent tests
test_feerate_checks_replaced_outputs(self, rbf_node, peer_node)
+ test_bumpfee_with_feerate_ignores_walletincrementalrelayfee(self, rbf_node, peer_node)
def test_invalid_parameters(self, rbf_node, peer_node, dest_address):
self.log.info('Test invalid parameters')
@@ -816,7 +817,7 @@ def test_feerate_checks_replaced_outputs(self, rbf_node, peer_node):
# Since the bumped tx will replace all of the outputs with a single output, we can estimate that its size will 31 * (len(outputs) - 1) bytes smaller
tx_size = tx_details["decoded"]["vsize"]
est_bumped_size = tx_size - (len(tx_details["decoded"]["vout"]) - 1) * 31
- inc_fee_rate = max(rbf_node.getmempoolinfo()["incrementalrelayfee"], Decimal(0.00005000)) # Wallet has a fixed incremental relay fee of 5 sat/vb
+ inc_fee_rate = rbf_node.getmempoolinfo()["incrementalrelayfee"]
# RPC gives us fee as negative
min_fee = (-tx_details["fee"] + get_fee(est_bumped_size, inc_fee_rate)) * Decimal(1e8)
min_fee_rate = (min_fee / est_bumped_size).quantize(Decimal("1.000"))
@@ -830,5 +831,27 @@ def test_feerate_checks_replaced_outputs(self, rbf_node, peer_node):
self.clear_mempool()
+def test_bumpfee_with_feerate_ignores_walletincrementalrelayfee(self, rbf_node, peer_node):
+ self.log.info('Test that bumpfee with fee_rate ignores walletincrementalrelayfee')
+ # Make sure there is enough balance
+ peer_node.sendtoaddress(rbf_node.getnewaddress(), 2)
+ self.generate(peer_node, 1)
+
+ dest_address = peer_node.getnewaddress(address_type="bech32")
+ tx = rbf_node.send(outputs=[{dest_address: 1}], fee_rate=2)
+
+ # Ensure you can not fee bump with a fee_rate below or equal to the original fee_rate
+ assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 1})
+ assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 2})
+
+ # Ensure you can not fee bump if the fee_rate is more than original fee_rate but the total fee from new fee_rate is
+ # less than (original fee + incrementalrelayfee)
+ assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 2.8})
+
+ # You can fee bump as long as the new fee set from fee_rate is atleast (original fee + incrementalrelayfee)
+ rbf_node.bumpfee(tx["txid"], {"fee_rate": 3})
+ self.clear_mempool()
+
+
if __name__ == "__main__":
BumpFeeTest().main()
diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py
index fb1ee3ae61..fa3e920c25 100755
--- a/test/functional/wallet_create_tx.py
+++ b/test/functional/wallet_create_tx.py
@@ -111,7 +111,7 @@ class CreateTxWalletTest(BitcoinTestFramework):
test_wallet.unloadwallet()
def test_version3(self):
- self.log.info('Check wallet does not create transactions with nVersion=3 yet')
+ self.log.info('Check wallet does not create transactions with version=3 yet')
wallet_rpc = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
self.nodes[0].createwallet("v3")
@@ -124,7 +124,7 @@ class CreateTxWalletTest(BitcoinTestFramework):
# While v3 transactions are standard, the CURRENT_VERSION is 2.
# This test can be removed if CURRENT_VERSION is changed, and replaced with tests that the
# wallet handles v3 rules properly.
- assert_equal(tx_current_version.nVersion, 2)
+ assert_equal(tx_current_version.version, 2)
wallet_v3.unloadwallet()
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index fd586d546e..15214539a9 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -40,6 +40,7 @@ class ListSinceBlockTest(BitcoinTestFramework):
self.test_no_blockhash()
self.test_invalid_blockhash()
self.test_reorg()
+ self.test_cant_read_block()
self.test_double_spend()
self.test_double_send()
self.double_spends_filtered()
@@ -167,6 +168,31 @@ class ListSinceBlockTest(BitcoinTestFramework):
found = next(tx for tx in transactions if tx['txid'] == senttx)
assert_equal(found['blockheight'], self.nodes[0].getblockheader(nodes2_first_blockhash)['height'])
+ def test_cant_read_block(self):
+ self.log.info('Test the RPC error "Can\'t read block from disk"')
+
+ # Split network into two
+ self.split_network()
+
+ # generate on both sides
+ nodes1_last_blockhash = self.generate(self.nodes[1], 6, sync_fun=lambda: self.sync_all(self.nodes[:2]))[-1]
+ self.generate(self.nodes[2], 7, sync_fun=lambda: self.sync_all(self.nodes[2:]))[0]
+
+ self.join_network()
+
+ # Renaming the block file to induce unsuccessful block read
+ blk_dat = (self.nodes[0].blocks_path / "blk00000.dat")
+ blk_dat_moved = blk_dat.rename(self.nodes[0].blocks_path / "blk00000.dat.moved")
+ assert not blk_dat.exists()
+
+ # listsinceblock(nodes1_last_blockhash) should now fail as blocks are not accessible
+ assert_raises_rpc_error(-32603, "Can't read block from disk",
+ self.nodes[0].listsinceblock, nodes1_last_blockhash)
+
+ # Restoring block file
+ blk_dat_moved.rename(self.nodes[0].blocks_path / "blk00000.dat")
+ assert blk_dat.exists()
+
def test_double_spend(self):
'''
This tests the case where the same UTXO is spent twice on two separate
diff --git a/test/lint/README.md b/test/lint/README.md
index 49ed8356c3..04a836c4d2 100644
--- a/test/lint/README.md
+++ b/test/lint/README.md
@@ -26,6 +26,21 @@ Then you can use:
( cd ./test/lint/test_runner/ && cargo fmt && cargo clippy && RUST_BACKTRACE=1 cargo run )
```
+If you wish to run individual lint checks, run the test_runner with
+`--lint=TEST_TO_RUN` arguments. If running with `cargo run`, arguments after
+`--` are passed to the binary you are running e.g.:
+
+```sh
+( cd ./test/lint/test_runner/ && RUST_BACKTRACE=1 cargo run -- --lint=doc --lint=trailing_whitespace )
+```
+
+To see a list of all individual lint checks available in test_runner, use `-h`
+or `--help`:
+
+```sh
+( cd ./test/lint/test_runner/ && RUST_BACKTRACE=1 cargo run -- --help )
+```
+
#### Dependencies
| Lint test | Dependency |
diff --git a/test/lint/test_runner/src/main.rs b/test/lint/test_runner/src/main.rs
index 5f980eb398..9c35898c1f 100644
--- a/test/lint/test_runner/src/main.rs
+++ b/test/lint/test_runner/src/main.rs
@@ -12,6 +12,113 @@ type LintError = String;
type LintResult = Result<(), LintError>;
type LintFn = fn() -> LintResult;
+struct Linter {
+ pub description: &'static str,
+ pub name: &'static str,
+ pub lint_fn: LintFn,
+}
+
+fn get_linter_list() -> Vec<&'static Linter> {
+ vec![
+ &Linter {
+ description: "Check that all command line arguments are documented.",
+ name: "doc",
+ lint_fn: lint_doc
+ },
+ &Linter {
+ description: "Check that no symbol from bitcoin-config.h is used without the header being included",
+ name: "includes_build_config",
+ lint_fn: lint_includes_build_config
+ },
+ &Linter {
+ description: "Check that markdown links resolve",
+ name: "markdown",
+ lint_fn: lint_markdown
+ },
+ &Linter {
+ description: "Check that std::filesystem is not used directly",
+ name: "std_filesystem",
+ lint_fn: lint_std_filesystem
+ },
+ &Linter {
+ description: "Check that subtrees are pure subtrees",
+ name: "subtree",
+ lint_fn: lint_subtree
+ },
+ &Linter {
+ description: "Check that tabs are not used as whitespace",
+ name: "tabs_whitespace",
+ lint_fn: lint_tabs_whitespace
+ },
+ &Linter {
+ description: "Check for trailing whitespace",
+ name: "trailing_whitespace",
+ lint_fn: lint_trailing_whitespace
+ },
+ &Linter {
+ description: "Run all linters of the form: test/lint/lint-*.py",
+ name: "all_python_linters",
+ lint_fn: run_all_python_linters
+ },
+ ]
+}
+
+fn print_help_and_exit() {
+ print!(
+ r#"
+Usage: test_runner [--lint=LINTER_TO_RUN]
+Runs all linters in the lint test suite, printing any errors
+they detect.
+
+If you wish to only run some particular lint tests, pass
+'--lint=' with the name of the lint test you wish to run.
+You can set as many '--lint=' values as you wish, e.g.:
+test_runner --lint=doc --lint=subtree
+
+The individual linters available to run are:
+"#
+ );
+ for linter in get_linter_list() {
+ println!("{}: \"{}\"", linter.name, linter.description)
+ }
+
+ std::process::exit(1);
+}
+
+fn parse_lint_args(args: &[String]) -> Vec<&'static Linter> {
+ let linter_list = get_linter_list();
+ let mut lint_values = Vec::new();
+
+ for arg in args {
+ #[allow(clippy::if_same_then_else)]
+ if arg.starts_with("--lint=") {
+ let lint_arg_value = arg
+ .trim_start_matches("--lint=")
+ .trim_matches('"')
+ .trim_matches('\'');
+
+ let try_find_linter = linter_list
+ .iter()
+ .find(|linter| linter.name == lint_arg_value);
+ match try_find_linter {
+ Some(linter) => {
+ lint_values.push(*linter);
+ }
+ None => {
+ println!("No linter {lint_arg_value} found!");
+ print_help_and_exit();
+ }
+ }
+ } else if arg.eq("--help") || arg.eq("-h") {
+ print_help_and_exit();
+ } else {
+ print_help_and_exit();
+ }
+ }
+
+ lint_values
+}
+
/// Return the git command
fn git() -> Command {
let mut git = Command::new("git");
@@ -337,7 +444,7 @@ Markdown link errors found:
}
}
-fn lint_all() -> LintResult {
+fn run_all_python_linters() -> LintResult {
let mut good = true;
let lint_dir = get_git_root().join("test/lint");
for entry in fs::read_dir(lint_dir).unwrap() {
@@ -352,7 +459,7 @@ fn lint_all() -> LintResult {
.success()
{
good = false;
- println!("^---- failure generated from {}", entry_fn);
+ println!("^---- ⚠️ Failure generated from {}", entry_fn);
}
}
if good {
@@ -363,25 +470,26 @@ fn lint_all() -> LintResult {
}
fn main() -> ExitCode {
- let test_list: Vec<(&str, LintFn)> = vec![
- ("subtree check", lint_subtree),
- ("std::filesystem check", lint_std_filesystem),
- ("trailing whitespace check", lint_trailing_whitespace),
- ("no-tabs check", lint_tabs_whitespace),
- ("build config includes check", lint_includes_build_config),
- ("-help=1 documentation check", lint_doc),
- ("markdown hyperlink check", lint_markdown),
- ("lint-*.py scripts", lint_all),
- ];
+ let linters_to_run: Vec<&Linter> = if env::args().count() > 1 {
+ let args: Vec<String> = env::args().skip(1).collect();
+ parse_lint_args(&args)
+ } else {
+ // If no arguments are passed, run all linters.
+ get_linter_list()
+ };
let git_root = get_git_root();
let mut test_failed = false;
- for (lint_name, lint_fn) in test_list {
+ for linter in linters_to_run {
// chdir to root before each lint test
env::set_current_dir(&git_root).unwrap();
- if let Err(err) = lint_fn() {
- println!("{err}\n^---- ⚠️ Failure generated from {lint_name}!");
+ if let Err(err) = (linter.lint_fn)() {
+ println!(
+ "{err}\n^---- ⚠️ Failure generated from lint check '{}'!",
+ linter.name
+ );
+ println!("{}", linter.description);
test_failed = true;
}
}
diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan
index 482667a26a..9818d73fdf 100644
--- a/test/sanitizer_suppressions/ubsan
+++ b/test/sanitizer_suppressions/ubsan
@@ -58,6 +58,7 @@ unsigned-integer-overflow:TxConfirmStats::EstimateMedianVal
unsigned-integer-overflow:prevector.h
unsigned-integer-overflow:EvalScript
unsigned-integer-overflow:xoroshiro128plusplus.h
+unsigned-integer-overflow:bitset_detail::PopCount
implicit-integer-sign-change:CBlockPolicyEstimator::processBlockTx
implicit-integer-sign-change:SetStdinEcho
implicit-integer-sign-change:compressor.h