aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rw-r--r--test/functional/README.md2
-rw-r--r--test/functional/data/wallets/high_minversion/.walletlock0
-rw-r--r--test/functional/data/wallets/high_minversion/GENERATE.md8
-rw-r--r--test/functional/data/wallets/high_minversion/db.log0
-rw-r--r--test/functional/data/wallets/high_minversion/wallet.datbin16384 -> 0 bytes
-rwxr-xr-xtest/functional/example_test.py5
-rwxr-xr-xtest/functional/feature_abortnode.py4
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py187
-rwxr-xr-xtest/functional/feature_bip68_sequence.py17
-rwxr-xr-xtest/functional/feature_block.py24
-rwxr-xr-xtest/functional/feature_config_args.py16
-rwxr-xr-xtest/functional/feature_csv_activation.py1
-rwxr-xr-xtest/functional/feature_dbcrash.py4
-rwxr-xr-xtest/functional/feature_fee_estimation.py19
-rwxr-xr-xtest/functional/feature_filelock.py5
-rwxr-xr-xtest/functional/feature_minchainwork.py4
-rwxr-xr-xtest/functional/feature_notifications.py21
-rwxr-xr-xtest/functional/feature_nulldummy.py31
-rwxr-xr-xtest/functional/feature_proxy.py67
-rwxr-xr-xtest/functional/feature_pruning.py43
-rwxr-xr-xtest/functional/feature_segwit.py5
-rwxr-xr-xtest/functional/feature_settings.py13
-rwxr-xr-xtest/functional/feature_signet.py4
-rwxr-xr-xtest/functional/feature_taproot.py1485
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py2
-rwxr-xr-xtest/functional/interface_zmq.py33
-rwxr-xr-xtest/functional/mempool_accept.py16
-rwxr-xr-xtest/functional/mempool_compatibility.py6
-rwxr-xr-xtest/functional/mempool_persist.py6
-rwxr-xr-xtest/functional/mempool_spend_coinbase.py28
-rwxr-xr-xtest/functional/mempool_unbroadcast.py8
-rwxr-xr-xtest/functional/mining_basic.py17
-rwxr-xr-xtest/functional/mining_getblocktemplate_longpoll.py33
-rwxr-xr-xtest/functional/p2p_addrv2_relay.py79
-rwxr-xr-xtest/functional/p2p_blockfilters.py6
-rwxr-xr-xtest/functional/p2p_blocksonly.py2
-rwxr-xr-xtest/functional/p2p_compactblocks.py11
-rwxr-xr-xtest/functional/p2p_disconnect_ban.py14
-rwxr-xr-xtest/functional/p2p_feefilter.py3
-rwxr-xr-xtest/functional/p2p_fingerprint.py47
-rwxr-xr-xtest/functional/p2p_invalid_messages.py111
-rwxr-xr-xtest/functional/p2p_leak_tx.py19
-rwxr-xr-xtest/functional/p2p_node_network_limited.py16
-rwxr-xr-xtest/functional/p2p_permissions.py36
-rwxr-xr-xtest/functional/p2p_segwit.py27
-rwxr-xr-xtest/functional/p2p_tx_download.py151
-rwxr-xr-xtest/functional/p2p_unrequested_blocks.py3
-rwxr-xr-xtest/functional/rpc_blockchain.py14
-rwxr-xr-xtest/functional/rpc_deprecated.py2
-rwxr-xr-xtest/functional/rpc_estimatefee.py4
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py204
-rwxr-xr-xtest/functional/rpc_getblockfilter.py5
-rwxr-xr-xtest/functional/rpc_getdescriptorinfo.py1
-rwxr-xr-xtest/functional/rpc_getpeerinfo_deprecation.py3
-rwxr-xr-xtest/functional/rpc_invalidateblock.py5
-rwxr-xr-xtest/functional/rpc_net.py112
-rwxr-xr-xtest/functional/rpc_preciousblock.py7
-rwxr-xr-xtest/functional/rpc_psbt.py114
-rwxr-xr-xtest/functional/rpc_rawtransaction.py260
-rwxr-xr-xtest/functional/rpc_scantxoutset.py3
-rwxr-xr-xtest/functional/rpc_setban.py7
-rwxr-xr-xtest/functional/rpc_signrawtransaction.py48
-rwxr-xr-xtest/functional/rpc_txoutproof.py2
-rwxr-xr-xtest/functional/rpc_users.py11
-rw-r--r--test/functional/test_framework/address.py72
-rw-r--r--test/functional/test_framework/bdb.py152
-rw-r--r--test/functional/test_framework/bip340_test_vectors.csv16
-rw-r--r--test/functional/test_framework/blocktools.py75
-rw-r--r--test/functional/test_framework/key.py200
-rwxr-xr-xtest/functional/test_framework/messages.py104
-rwxr-xr-xtest/functional/test_framework/p2p.py12
-rw-r--r--test/functional/test_framework/script.py145
-rwxr-xr-xtest/functional/test_framework/script_util.py59
-rw-r--r--test/functional/test_framework/segwit_addr.py22
-rwxr-xr-xtest/functional/test_framework/test_framework.py117
-rwxr-xr-xtest/functional/test_framework/test_node.py2
-rw-r--r--test/functional/test_framework/util.py107
-rw-r--r--test/functional/test_framework/wallet.py17
-rwxr-xr-xtest/functional/test_runner.py48
-rwxr-xr-xtest/functional/tool_wallet.py102
-rwxr-xr-xtest/functional/wallet_abandonconflict.py6
-rwxr-xr-xtest/functional/wallet_address_types.py47
-rwxr-xr-xtest/functional/wallet_avoidreuse.py3
-rwxr-xr-xtest/functional/wallet_backup.py100
-rwxr-xr-xtest/functional/wallet_balance.py51
-rwxr-xr-xtest/functional/wallet_basic.py208
-rwxr-xr-xtest/functional/wallet_bumpfee.py135
-rwxr-xr-xtest/functional/wallet_create_tx.py8
-rwxr-xr-xtest/functional/wallet_createwallet.py55
-rwxr-xr-xtest/functional/wallet_descriptor.py10
-rwxr-xr-xtest/functional/wallet_disable.py1
-rwxr-xr-xtest/functional/wallet_dump.py7
-rwxr-xr-xtest/functional/wallet_hd.py11
-rwxr-xr-xtest/functional/wallet_import_rescan.py11
-rwxr-xr-xtest/functional/wallet_importdescriptors.py25
-rwxr-xr-xtest/functional/wallet_importmulti.py4
-rwxr-xr-xtest/functional/wallet_importprunedfunds.py53
-rwxr-xr-xtest/functional/wallet_keypool.py2
-rwxr-xr-xtest/functional/wallet_keypool_topup.py11
-rwxr-xr-xtest/functional/wallet_labels.py4
-rwxr-xr-xtest/functional/wallet_listsinceblock.py23
-rwxr-xr-xtest/functional/wallet_listtransactions.py26
-rwxr-xr-xtest/functional/wallet_multiwallet.py154
-rwxr-xr-xtest/functional/wallet_reorgsrestore.py14
-rwxr-xr-xtest/functional/wallet_resendwallettransactions.py17
-rwxr-xr-xtest/functional/wallet_send.py115
-rwxr-xr-xtest/functional/wallet_txn_clone.py6
-rwxr-xr-xtest/functional/wallet_txn_doublespend.py6
-rwxr-xr-xtest/functional/wallet_upgradewallet.py297
109 files changed, 4685 insertions, 1416 deletions
diff --git a/test/functional/README.md b/test/functional/README.md
index 82b30fed51..2764acbf18 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -34,7 +34,7 @@ don't have test cases for.
- When subclassing the BitcoinTestFramework, place overrides for the
`set_test_params()`, `add_options()` and `setup_xxxx()` methods at the top of
the subclass, then locally-defined helper methods, then the `run_test()` method.
-- Use `'{}'.format(x)` for string formatting, not `'%s' % x`.
+- Use `f'{x}'` for string formatting in preference to `'{}'.format(x)` or `'%s' % x`.
#### Naming guidelines
diff --git a/test/functional/data/wallets/high_minversion/.walletlock b/test/functional/data/wallets/high_minversion/.walletlock
deleted file mode 100644
index e69de29bb2..0000000000
--- a/test/functional/data/wallets/high_minversion/.walletlock
+++ /dev/null
diff --git a/test/functional/data/wallets/high_minversion/GENERATE.md b/test/functional/data/wallets/high_minversion/GENERATE.md
deleted file mode 100644
index e55c4557ca..0000000000
--- a/test/functional/data/wallets/high_minversion/GENERATE.md
+++ /dev/null
@@ -1,8 +0,0 @@
-The wallet has been created by starting Bitcoin Core with the options
-`-regtest -datadir=/tmp -nowallet -walletdir=$(pwd)/test/functional/data/wallets/`.
-
-In the source code, `WalletFeature::FEATURE_LATEST` has been modified to be large, so that the minversion is too high
-for a current build of the wallet.
-
-The wallet has then been created with the RPC `createwallet high_minversion true true`, so that a blank wallet with
-private keys disabled is created.
diff --git a/test/functional/data/wallets/high_minversion/db.log b/test/functional/data/wallets/high_minversion/db.log
deleted file mode 100644
index e69de29bb2..0000000000
--- a/test/functional/data/wallets/high_minversion/db.log
+++ /dev/null
diff --git a/test/functional/data/wallets/high_minversion/wallet.dat b/test/functional/data/wallets/high_minversion/wallet.dat
deleted file mode 100644
index 99ab809263..0000000000
--- a/test/functional/data/wallets/high_minversion/wallet.dat
+++ /dev/null
Binary files differ
diff --git a/test/functional/example_test.py b/test/functional/example_test.py
index 3b9bd3048f..c28bb7115f 100755
--- a/test/functional/example_test.py
+++ b/test/functional/example_test.py
@@ -25,7 +25,6 @@ from test_framework.p2p import (
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
)
# P2PInterface is a class containing callbacks to be executed when a P2P
@@ -115,7 +114,7 @@ class ExampleTest(BitcoinTestFramework):
# In this test, we're not connecting node2 to node0 or node1. Calls to
# sync_all() should not include node2, since we're not expecting it to
# sync.
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_all(self.nodes[0:2])
# Use setup_nodes() to customize the node start behaviour (for example if
@@ -183,7 +182,7 @@ class ExampleTest(BitcoinTestFramework):
self.nodes[1].waitforblockheight(11)
self.log.info("Connect node2 and node1")
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(1, 2)
self.log.info("Wait for node2 to receive all the blocks from node1")
self.sync_all()
diff --git a/test/functional/feature_abortnode.py b/test/functional/feature_abortnode.py
index 17fbf50cc8..8abfdef3a1 100755
--- a/test/functional/feature_abortnode.py
+++ b/test/functional/feature_abortnode.py
@@ -11,7 +11,7 @@
"""
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import get_datadir_path, connect_nodes
+from test_framework.util import get_datadir_path
import os
@@ -36,7 +36,7 @@ class AbortNodeTest(BitcoinTestFramework):
# attempt.
self.nodes[1].generate(3)
with self.nodes[0].assert_debug_log(["Failed to disconnect block"]):
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.nodes[1].generate(1)
# Check that node0 aborted
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index daefb161ac..b161c71a85 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -27,6 +27,7 @@ from test_framework.descriptors import descsum_create
from test_framework.util import (
assert_equal,
+ assert_raises_rpc_error,
)
@@ -36,13 +37,14 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
self.num_nodes = 6
# Add new version after each release:
self.extra_args = [
- ["-addresstype=bech32", "-wallet="], # Pre-release: use to mine blocks
+ ["-addresstype=bech32"], # Pre-release: use to mine blocks
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # Pre-release: use to receive coins, swap wallets, etc
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.19.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.18.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.17.2
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-wallet=wallet.dat"], # v0.16.3
]
+ self.wallet_names = [self.default_wallet_name]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -59,6 +61,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
])
self.start_nodes()
+ self.import_deterministic_coinbase_privkeys()
def run_test(self):
self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress())
@@ -80,7 +83,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# w1: regular wallet, created on master: update this test when default
# wallets can no longer be opened by older versions.
- node_master.rpc.createwallet(wallet_name="w1")
+ node_master.createwallet(wallet_name="w1")
wallet = node_master.get_wallet_rpc("w1")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
@@ -125,7 +128,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# w2: wallet with private keys disabled, created on master: update this
# test when default wallets private keys disabled can no longer be
# opened by older versions.
- node_master.rpc.createwallet(wallet_name="w2", disable_private_keys=True)
+ node_master.createwallet(wallet_name="w2", disable_private_keys=True)
wallet = node_master.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
@@ -147,7 +150,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# w3: blank wallet, created on master: update this
# test when default blank wallets can no longer be opened by older versions.
- node_master.rpc.createwallet(wallet_name="w3", blank=True)
+ node_master.createwallet(wallet_name="w3", blank=True)
wallet = node_master.get_wallet_rpc("w3")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
@@ -213,67 +216,89 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
os.path.join(node_v19_wallets_dir, wallet)
)
- # Open the wallets in v0.19
- node_v19.loadwallet("w1")
- wallet = node_v19.get_wallet_rpc("w1")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled']
- assert info['keypoolsize'] > 0
- txs = wallet.listtransactions()
- assert_equal(len(txs), 5)
- assert_equal(txs[1]["txid"], tx1_id)
- assert_equal(txs[2]["walletconflicts"], [tx1_id])
- assert_equal(txs[1]["replaced_by_txid"], tx2_id)
- assert not(txs[1]["abandoned"])
- assert_equal(txs[1]["confirmations"], -1)
- assert_equal(txs[2]["blockindex"], 1)
- assert txs[3]["abandoned"]
- assert_equal(txs[4]["walletconflicts"], [tx3_id])
- assert_equal(txs[3]["replaced_by_txid"], tx4_id)
- assert not(hasattr(txs[3], "blockindex"))
-
- node_v19.loadwallet("w2")
- wallet = node_v19.get_wallet_rpc("w2")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled'] == False
- assert info['keypoolsize'] == 0
-
- node_v19.loadwallet("w3")
- wallet = node_v19.get_wallet_rpc("w3")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled']
- assert info['keypoolsize'] == 0
-
- # Open the wallets in v0.18
- node_v18.loadwallet("w1")
- wallet = node_v18.get_wallet_rpc("w1")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled']
- assert info['keypoolsize'] > 0
- txs = wallet.listtransactions()
- assert_equal(len(txs), 5)
- assert_equal(txs[1]["txid"], tx1_id)
- assert_equal(txs[2]["walletconflicts"], [tx1_id])
- assert_equal(txs[1]["replaced_by_txid"], tx2_id)
- assert not(txs[1]["abandoned"])
- assert_equal(txs[1]["confirmations"], -1)
- assert_equal(txs[2]["blockindex"], 1)
- assert txs[3]["abandoned"]
- assert_equal(txs[4]["walletconflicts"], [tx3_id])
- assert_equal(txs[3]["replaced_by_txid"], tx4_id)
- assert not(hasattr(txs[3], "blockindex"))
-
- node_v18.loadwallet("w2")
- wallet = node_v18.get_wallet_rpc("w2")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled'] == False
- assert info['keypoolsize'] == 0
-
- node_v18.loadwallet("w3")
- wallet = node_v18.get_wallet_rpc("w3")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled']
- assert info['keypoolsize'] == 0
+ if not self.options.descriptors:
+ # Descriptor wallets break compatibility, only run this test for legacy wallet
+ # Open the wallets in v0.19
+ node_v19.loadwallet("w1")
+ wallet = node_v19.get_wallet_rpc("w1")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled']
+ assert info['keypoolsize'] > 0
+ txs = wallet.listtransactions()
+ assert_equal(len(txs), 5)
+ assert_equal(txs[1]["txid"], tx1_id)
+ assert_equal(txs[2]["walletconflicts"], [tx1_id])
+ assert_equal(txs[1]["replaced_by_txid"], tx2_id)
+ assert not(txs[1]["abandoned"])
+ assert_equal(txs[1]["confirmations"], -1)
+ assert_equal(txs[2]["blockindex"], 1)
+ assert txs[3]["abandoned"]
+ assert_equal(txs[4]["walletconflicts"], [tx3_id])
+ assert_equal(txs[3]["replaced_by_txid"], tx4_id)
+ assert not(hasattr(txs[3], "blockindex"))
+
+ node_v19.loadwallet("w2")
+ wallet = node_v19.get_wallet_rpc("w2")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled'] == False
+ assert info['keypoolsize'] == 0
+
+ node_v19.loadwallet("w3")
+ wallet = node_v19.get_wallet_rpc("w3")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled']
+ assert info['keypoolsize'] == 0
+
+ # Open the wallets in v0.18
+ node_v18.loadwallet("w1")
+ wallet = node_v18.get_wallet_rpc("w1")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled']
+ assert info['keypoolsize'] > 0
+ txs = wallet.listtransactions()
+ assert_equal(len(txs), 5)
+ assert_equal(txs[1]["txid"], tx1_id)
+ assert_equal(txs[2]["walletconflicts"], [tx1_id])
+ assert_equal(txs[1]["replaced_by_txid"], tx2_id)
+ assert not(txs[1]["abandoned"])
+ assert_equal(txs[1]["confirmations"], -1)
+ assert_equal(txs[2]["blockindex"], 1)
+ assert txs[3]["abandoned"]
+ assert_equal(txs[4]["walletconflicts"], [tx3_id])
+ assert_equal(txs[3]["replaced_by_txid"], tx4_id)
+ assert not(hasattr(txs[3], "blockindex"))
+
+ node_v18.loadwallet("w2")
+ wallet = node_v18.get_wallet_rpc("w2")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled'] == False
+ assert info['keypoolsize'] == 0
+
+ node_v18.loadwallet("w3")
+ wallet = node_v18.get_wallet_rpc("w3")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled']
+ assert info['keypoolsize'] == 0
+
+ node_v17.loadwallet("w1")
+ wallet = node_v17.get_wallet_rpc("w1")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled']
+ assert info['keypoolsize'] > 0
+
+ node_v17.loadwallet("w2")
+ wallet = node_v17.get_wallet_rpc("w2")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled'] == False
+ assert info['keypoolsize'] == 0
+ else:
+ # Descriptor wallets appear to be corrupted wallets to old software
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w1")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w2")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w3")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w1")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w2")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w3")
# Open the wallets in v0.17
node_v17.loadwallet("w1_v18")
@@ -282,24 +307,12 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
- node_v17.loadwallet("w1")
- wallet = node_v17.get_wallet_rpc("w1")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled']
- assert info['keypoolsize'] > 0
-
node_v17.loadwallet("w2_v18")
wallet = node_v17.get_wallet_rpc("w2_v18")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
- node_v17.loadwallet("w2")
- wallet = node_v17.get_wallet_rpc("w2")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled'] == False
- assert info['keypoolsize'] == 0
-
# RPC loadwallet failure causes bitcoind to exit, in addition to the RPC
# call failure, so the following test won't work:
# assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3_v18')
@@ -307,14 +320,22 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# Instead, we stop node and try to launch it with the wallet:
self.stop_node(4)
node_v17.assert_start_raises_init_error(["-wallet=w3_v18"], "Error: Error loading w3_v18: Wallet requires newer version of Bitcoin Core")
- node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core")
+ if self.options.descriptors:
+ # Descriptor wallets appear to be corrupted wallets to old software
+ node_v17.assert_start_raises_init_error(["-wallet=w1"], "Error: wallet.dat corrupt, salvage failed")
+ node_v17.assert_start_raises_init_error(["-wallet=w2"], "Error: wallet.dat corrupt, salvage failed")
+ node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: wallet.dat corrupt, salvage failed")
+ else:
+ node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core")
self.start_node(4)
- # Open most recent wallet in v0.16 (no loadwallet RPC)
- self.restart_node(5, extra_args=["-wallet=w2"])
- wallet = node_v16.get_wallet_rpc("w2")
- info = wallet.getwalletinfo()
- assert info['keypoolsize'] == 1
+ if not self.options.descriptors:
+ # Descriptor wallets break compatibility, only run this test for legacy wallets
+ # Open most recent wallet in v0.16 (no loadwallet RPC)
+ self.restart_node(5, extra_args=["-wallet=w2"])
+ wallet = node_v16.get_wallet_rpc("w2")
+ info = wallet.getwalletinfo()
+ assert info['keypoolsize'] == 1
# Create upgrade wallet in v0.16
self.restart_node(-1, extra_args=["-wallet=u1_v16"])
diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py
index 1253c45418..60492350ee 100755
--- a/test/functional/feature_bip68_sequence.py
+++ b/test/functional/feature_bip68_sequence.py
@@ -6,7 +6,7 @@
import time
-from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment
+from test_framework.blocktools import create_block, NORMAL_GBT_REQUEST_PARAMS, add_witness_commitment
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, ToHex
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -275,6 +275,8 @@ class BIP68Test(BitcoinTestFramework):
# Advance the time on the node so that we can test timelocks
self.nodes[0].setmocktime(cur_time+600)
+ # Save block template now to use for the reorg later
+ tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
self.nodes[0].generate(1)
assert tx2.hash not in self.nodes[0].getrawmempool()
@@ -318,16 +320,15 @@ class BIP68Test(BitcoinTestFramework):
# diagram above).
# This would cause tx2 to be added back to the mempool, which in turn causes
# tx3 to be removed.
- tip = int(self.nodes[0].getblockhash(self.nodes[0].getblockcount()-1), 16)
- height = self.nodes[0].getblockcount()
for i in range(2):
- block = create_block(tip, create_coinbase(height), cur_time)
- block.nVersion = 3
+ block = create_block(tmpl=tmpl, ntime=cur_time)
block.rehash()
block.solve()
tip = block.sha256
- height += 1
assert_equal(None if i == 1 else 'inconclusive', self.nodes[0].submitblock(ToHex(block)))
+ tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
+ tmpl['previousblockhash'] = '%x' % tip
+ tmpl['transactions'] = []
cur_time += 1
mempool = self.nodes[0].getrawmempool()
@@ -375,9 +376,7 @@ class BIP68Test(BitcoinTestFramework):
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx3))
# make a block that violates bip68; ensure that the tip updates
- tip = int(self.nodes[0].getbestblockhash(), 16)
- block = create_block(tip, create_coinbase(self.nodes[0].getblockcount()+1))
- block.nVersion = 3
+ block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS))
block.vtx.extend([tx1, tx2, tx3])
block.hashMerkleRoot = block.calc_merkle_root()
block.rehash()
diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py
index 19753d73ef..158efb52c9 100755
--- a/test/functional/feature_block.py
+++ b/test/functional/feature_block.py
@@ -119,7 +119,7 @@ class FullBlockTest(BitcoinTestFramework):
# Allow the block to mature
blocks = []
for i in range(NUM_BUFFER_BLOCKS_TO_GENERATE):
- blocks.append(self.next_block("maturitybuffer.{}".format(i)))
+ blocks.append(self.next_block(f"maturitybuffer.{i}"))
self.save_spendable_output()
self.send_blocks(blocks)
@@ -151,8 +151,8 @@ class FullBlockTest(BitcoinTestFramework):
if template.valid_in_block:
continue
- self.log.info("Reject block with invalid tx: %s", TxTemplate.__name__)
- blockname = "for_invalid.%s" % TxTemplate.__name__
+ self.log.info(f"Reject block with invalid tx: {TxTemplate.__name__}")
+ blockname = f"for_invalid.{TxTemplate.__name__}"
badblock = self.next_block(blockname)
badtx = template.get_tx()
if TxTemplate != invalid_txs.InputMissing:
@@ -1251,7 +1251,7 @@ class FullBlockTest(BitcoinTestFramework):
blocks = []
spend = out[32]
for i in range(89, LARGE_REORG_SIZE + 89):
- b = self.next_block(i, spend, version=4)
+ b = self.next_block(i, spend)
tx = CTransaction()
script_length = MAX_BLOCK_BASE_SIZE - len(b.serialize()) - 69
script_output = CScript([b'\x00' * script_length])
@@ -1270,18 +1270,18 @@ class FullBlockTest(BitcoinTestFramework):
self.move_tip(88)
blocks2 = []
for i in range(89, LARGE_REORG_SIZE + 89):
- blocks2.append(self.next_block("alt" + str(i), version=4))
+ blocks2.append(self.next_block("alt" + str(i)))
self.send_blocks(blocks2, False, force_send=True)
# extend alt chain to trigger re-org
- block = self.next_block("alt" + str(chain1_tip + 1), version=4)
+ block = self.next_block("alt" + str(chain1_tip + 1))
self.send_blocks([block], True, timeout=2440)
# ... and re-org back to the first chain
self.move_tip(chain1_tip)
- block = self.next_block(chain1_tip + 1, version=4)
+ block = self.next_block(chain1_tip + 1)
self.send_blocks([block], False, force_send=True)
- block = self.next_block(chain1_tip + 2, version=4)
+ block = self.next_block(chain1_tip + 2)
self.send_blocks([block], True, timeout=2440)
self.log.info("Reject a block with an invalid block header version")
@@ -1289,7 +1289,7 @@ class FullBlockTest(BitcoinTestFramework):
self.send_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)', reconnect=True)
self.move_tip(chain1_tip + 2)
- b_cb34 = self.next_block('b_cb34', version=4)
+ b_cb34 = self.next_block('b_cb34')
b_cb34.vtx[0].vin[0].scriptSig = b_cb34.vtx[0].vin[0].scriptSig[:-1]
b_cb34.vtx[0].rehash()
b_cb34.hashMerkleRoot = b_cb34.calc_merkle_root()
@@ -1323,7 +1323,7 @@ class FullBlockTest(BitcoinTestFramework):
tx.rehash()
return tx
- def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), *, version=1):
+ def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), *, version=4):
if self.tip is None:
base_block_hash = self.genesis_hash
block_time = int(time.time()) + 1
@@ -1355,12 +1355,12 @@ class FullBlockTest(BitcoinTestFramework):
# save the current tip so it can be spent by a later block
def save_spendable_output(self):
- self.log.debug("saving spendable output %s" % self.tip.vtx[0])
+ self.log.debug(f"saving spendable output {self.tip.vtx[0]}")
self.spendable_outputs.append(self.tip)
# get an output that we previously marked as spendable
def get_spendable_output(self):
- self.log.debug("getting spendable output %s" % self.spendable_outputs[0].vtx[0])
+ self.log.debug(f"getting spendable output {self.spendable_outputs[0].vtx[0]}")
return self.spendable_outputs.pop(0).vtx[0]
# move the tip back to a previous block
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 34e856c1ba..3e28dae4b3 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -14,6 +14,7 @@ class ConfArgsTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
self.supports_cli = False
+ self.wallet_names = []
def test_config_file_parser(self):
# Assume node is stopped
@@ -78,6 +79,12 @@ class ConfArgsTest(BitcoinTestFramework):
with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf:
conf.write('') # clear
+ def test_invalid_command_line_options(self):
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.',
+ extra_args=['-proxy'],
+ )
+
def test_log_buffer(self):
with self.nodes[0].assert_debug_log(expected_msgs=['Warning: parsed potentially confusing double-negative -connect=0\n']):
self.start_node(0, extra_args=['-noconnect=0'])
@@ -146,6 +153,7 @@ class ConfArgsTest(BitcoinTestFramework):
self.test_networkactive()
self.test_config_file_parser()
+ self.test_invalid_command_line_options()
# Remove the -datadir argument so it doesn't override the config file
self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")]
@@ -171,19 +179,15 @@ class ConfArgsTest(BitcoinTestFramework):
# Create the directory and ensure the config file now works
os.mkdir(new_data_dir)
- self.start_node(0, ['-conf='+conf_file, '-wallet=w1'])
+ self.start_node(0, ['-conf='+conf_file])
self.stop_node(0)
assert os.path.exists(os.path.join(new_data_dir, self.chain, 'blocks'))
- if self.is_wallet_compiled():
- assert os.path.exists(os.path.join(new_data_dir, self.chain, 'wallets', 'w1'))
# Ensure command line argument overrides datadir in conf
os.mkdir(new_data_dir_2)
self.nodes[0].datadir = new_data_dir_2
- self.start_node(0, ['-datadir='+new_data_dir_2, '-conf='+conf_file, '-wallet=w2'])
+ self.start_node(0, ['-datadir='+new_data_dir_2, '-conf='+conf_file])
assert os.path.exists(os.path.join(new_data_dir_2, self.chain, 'blocks'))
- if self.is_wallet_compiled():
- assert os.path.exists(os.path.join(new_data_dir_2, self.chain, 'wallets', 'w2'))
if __name__ == '__main__':
diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py
index 39e8bca751..46ba18b9b5 100755
--- a/test/functional/feature_csv_activation.py
+++ b/test/functional/feature_csv_activation.py
@@ -150,7 +150,6 @@ class BIP68_112_113Test(BitcoinTestFramework):
self.setup_clean_chain = True
self.extra_args = [[
'-whitelist=noban@127.0.0.1',
- '-blockversion=4',
'-addresstype=legacy',
'-par=1', # Use only one script thread to get the exact reject reason for testing
]]
diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py
index 4bc47141a1..7a2e35c095 100755
--- a/test/functional/feature_dbcrash.py
+++ b/test/functional/feature_dbcrash.py
@@ -56,7 +56,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
# Set -maxmempool=0 to turn off mempool memory sharing with dbcache
# Set -rpcservertimeout=900 to reduce socket disconnects in this
# long-running test
- self.base_args = ["-limitdescendantsize=0", "-maxmempool=0", "-rpcservertimeout=900", "-dbbatchsize=200000", "-wallet="]
+ self.base_args = ["-limitdescendantsize=0", "-maxmempool=0", "-rpcservertimeout=900", "-dbbatchsize=200000"]
# Set different crash ratios and cache sizes. Note that not all of
# -dbcache goes to the in-memory coins cache.
@@ -66,7 +66,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
# Node3 is a normal node with default args, except will mine full blocks
# and non-standard txs (e.g. txs with "dust" outputs)
- self.node3_args = ["-blockmaxweight=4000000", "-acceptnonstdtxn", "-wallet="]
+ self.node3_args = ["-blockmaxweight=4000000", "-acceptnonstdtxn"]
self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args]
def skip_test_if_missing_module(self):
diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py
index d89eeec400..8f522aee66 100755
--- a/test/functional/feature_fee_estimation.py
+++ b/test/functional/feature_fee_estimation.py
@@ -13,7 +13,7 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
- connect_nodes,
+ assert_raises_rpc_error,
satoshi_round,
)
@@ -145,9 +145,9 @@ class EstimateFeeTest(BitcoinTestFramework):
# mine non-standard txs (e.g. txs with "dust" outputs)
# Force fSendTrickle to true (via whitelist.noban)
self.extra_args = [
- ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-wallet="],
- ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-blockmaxweight=68000", "-wallet="],
- ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-blockmaxweight=32000", "-wallet="],
+ ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1"],
+ ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-blockmaxweight=68000"],
+ ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"],
]
def skip_test_if_missing_module(self):
@@ -232,9 +232,9 @@ class EstimateFeeTest(BitcoinTestFramework):
# so the estimates would not be affected by the splitting transactions
self.start_node(1)
self.start_node(2)
- connect_nodes(self.nodes[1], 0)
- connect_nodes(self.nodes[0], 2)
- connect_nodes(self.nodes[2], 1)
+ self.connect_nodes(1, 0)
+ self.connect_nodes(0, 2)
+ self.connect_nodes(2, 1)
self.sync_all()
@@ -263,6 +263,11 @@ class EstimateFeeTest(BitcoinTestFramework):
self.log.info("Final estimates after emptying mempools")
check_estimates(self.nodes[1], self.fees_per_kb)
+ self.log.info("Testing that fee estimation is disabled in blocksonly.")
+ self.restart_node(0, ["-blocksonly"])
+ assert_raises_rpc_error(-32603, "Fee estimation disabled",
+ self.nodes[0].estimatesmartfee, 2)
+
if __name__ == '__main__':
EstimateFeeTest().main()
diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py
index e4ceb62c94..7de9a589be 100755
--- a/test/functional/feature_filelock.py
+++ b/test/functional/feature_filelock.py
@@ -15,7 +15,7 @@ class FilelockTest(BitcoinTestFramework):
def setup_network(self):
self.add_nodes(self.num_nodes, extra_args=None)
- self.nodes[0].start(['-wallet='])
+ self.nodes[0].start()
self.nodes[0].wait_for_rpc_connection()
def run_test(self):
@@ -27,10 +27,11 @@ class FilelockTest(BitcoinTestFramework):
self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg)
if self.is_wallet_compiled():
+ self.nodes[0].createwallet(self.default_wallet_name)
wallet_dir = os.path.join(datadir, 'wallets')
self.log.info("Check that we can't start a second bitcoind instance using the same wallet")
expected_msg = "Error: Error initializing wallet database environment"
- self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=', '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
+ self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + self.default_wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
if __name__ == '__main__':
FilelockTest().main()
diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py
index dbff6f15f2..abf87e8f0c 100755
--- a/test/functional/feature_minchainwork.py
+++ b/test/functional/feature_minchainwork.py
@@ -18,7 +18,7 @@ only succeeds past a given node once its nMinimumChainWork has been exceeded.
import time
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import connect_nodes, assert_equal
+from test_framework.util import assert_equal
# 2 hashes required per regtest block (with no difficulty adjustment)
REGTEST_WORK_PER_BLOCK = 2
@@ -39,7 +39,7 @@ class MinimumChainWorkTest(BitcoinTestFramework):
# block relay to inbound peers.
self.setup_nodes()
for i in range(self.num_nodes-1):
- connect_nodes(self.nodes[i+1], i)
+ self.connect_nodes(i+1, i)
def run_test(self):
# Start building a chain on node0. node2 shouldn't be able to sync until node1's
diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py
index 20020c3237..47bc8dbb49 100755
--- a/test/functional/feature_notifications.py
+++ b/test/functional/feature_notifications.py
@@ -9,8 +9,6 @@ from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE, keyhash_to_p2pkh
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
- disconnect_nodes,
hex_str_to_bytes,
)
@@ -41,12 +39,13 @@ class NotificationsTest(BitcoinTestFramework):
# -alertnotify and -blocknotify on node0, walletnotify on node1
self.extra_args = [[
- "-alertnotify=echo > {}".format(os.path.join(self.alertnotify_dir, '%s')),
- "-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s'))],
- ["-blockversion=211",
- "-rescan",
- "-wallet={}".format(self.wallet),
- "-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s')))]]
+ "-alertnotify=echo > {}".format(os.path.join(self.alertnotify_dir, '%s')),
+ "-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s')),
+ ], [
+ "-rescan",
+ "-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s'))),
+ ]]
+ self.wallet_names = [self.default_wallet_name, self.wallet]
super().setup_network()
def run_test(self):
@@ -75,7 +74,7 @@ class NotificationsTest(BitcoinTestFramework):
self.log.info("test -walletnotify after rescan")
# restart node to rescan to force wallet notifications
self.start_node(1)
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
@@ -126,12 +125,12 @@ class NotificationsTest(BitcoinTestFramework):
# Bump tx2 as bump2 and generate a block on node 0 while
# disconnected, then reconnect and check for notifications on node 1
# about newly confirmed bump2 and newly conflicted tx2.
- disconnect_nodes(self.nodes[0], 1)
+ self.disconnect_nodes(0, 1)
bump2 = self.nodes[0].bumpfee(tx2)["txid"]
self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
assert_equal(self.nodes[0].gettransaction(bump2)["confirmations"], 1)
assert_equal(tx2 in self.nodes[1].getrawmempool(), True)
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_blocks()
self.expect_wallet_notify([bump2, tx2])
assert_equal(self.nodes[1].gettransaction(bump2)["confirmations"], 1)
diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py
index ff55cb76d9..b0eac7056b 100755
--- a/test/functional/feature_nulldummy.py
+++ b/test/functional/feature_nulldummy.py
@@ -14,7 +14,7 @@ Generate 427 more blocks.
"""
import time
-from test_framework.blocktools import create_coinbase, create_block, create_transaction, add_witness_commitment
+from test_framework.blocktools import NORMAL_GBT_REQUEST_PARAMS, create_block, create_transaction, add_witness_commitment
from test_framework.messages import CTransaction
from test_framework.script import CScript
from test_framework.test_framework import BitcoinTestFramework
@@ -37,23 +37,32 @@ def trueDummy(tx):
class NULLDUMMYTest(BitcoinTestFramework):
def set_test_params(self):
- self.num_nodes = 1
+ # Need two nodes only so GBT doesn't complain that it's not connected
+ self.num_nodes = 2
self.setup_clean_chain = True
# This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through
# normal segwit activation here (and don't use the default always-on behaviour).
self.extra_args = [[
'-segwitheight=432',
'-addresstype=legacy',
- ]]
+ ]] * 2
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
- self.address = self.nodes[0].getnewaddress()
- self.ms_address = self.nodes[0].addmultisigaddress(1, [self.address])['address']
- self.wit_address = self.nodes[0].getnewaddress(address_type='p2sh-segwit')
- self.wit_ms_address = self.nodes[0].addmultisigaddress(1, [self.address], '', 'p2sh-segwit')['address']
+ self.nodes[0].createwallet(wallet_name='wmulti', disable_private_keys=True)
+ wmulti = self.nodes[0].get_wallet_rpc('wmulti')
+ w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+ self.address = w0.getnewaddress()
+ self.pubkey = w0.getaddressinfo(self.address)['pubkey']
+ self.ms_address = wmulti.addmultisigaddress(1, [self.pubkey])['address']
+ self.wit_address = w0.getnewaddress(address_type='p2sh-segwit')
+ self.wit_ms_address = wmulti.addmultisigaddress(1, [self.pubkey], '', 'p2sh-segwit')['address']
+ if not self.options.descriptors:
+ # Legacy wallets need to import these so that they are watched by the wallet. This is unnecssary (and does not need to be tested) for descriptor wallets
+ wmulti.importaddress(self.ms_address)
+ wmulti.importaddress(self.wit_ms_address)
self.coinbase_blocks = self.nodes[0].generate(2) # Block 2
coinbase_txid = []
@@ -61,7 +70,6 @@ class NULLDUMMYTest(BitcoinTestFramework):
coinbase_txid.append(self.nodes[0].getblock(i)['tx'][0])
self.nodes[0].generate(427) # Block 429
self.lastblockhash = self.nodes[0].getbestblockhash()
- self.tip = int("0x" + self.lastblockhash, 0)
self.lastblockheight = 429
self.lastblocktime = int(time.time()) + 429
@@ -102,8 +110,10 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.block_submit(self.nodes[0], test6txs, True, True)
def block_submit(self, node, txs, witness=False, accept=False):
- block = create_block(self.tip, create_coinbase(self.lastblockheight + 1), self.lastblocktime + 1)
- block.nVersion = 4
+ tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
+ assert_equal(tmpl['previousblockhash'], self.lastblockhash)
+ assert_equal(tmpl['height'], self.lastblockheight + 1)
+ block = create_block(tmpl=tmpl, ntime=self.lastblocktime + 1)
for tx in txs:
tx.rehash()
block.vtx.append(tx)
@@ -114,7 +124,6 @@ class NULLDUMMYTest(BitcoinTestFramework):
assert_equal(None if accept else 'block-validation-failed', node.submitblock(block.serialize().hex()))
if (accept):
assert_equal(node.getbestblockhash(), block.hash)
- self.tip = block.sha256
self.lastblockhash = block.hash
self.lastblocktime += 1
self.lastblockheight += 1
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index be323d355e..05b658ed87 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -18,13 +18,16 @@ Test plan:
- proxy on IPv6
- Create various proxies (as threads)
-- Create bitcoinds that connect to them
-- Manipulate the bitcoinds using addnode (onetry) an observe effects
+- Create nodes that connect to them
+- Manipulate the peer connections using addnode (onetry) and observe effects
+- Test the getpeerinfo `network` field for the peer
addnode connect to IPv4
addnode connect to IPv6
addnode connect to onion
addnode connect to generic DNS name
+
+- Test getnetworkinfo for each node
"""
import socket
@@ -41,6 +44,16 @@ from test_framework.netutil import test_ipv6_local
RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports
+# Networks returned by RPC getpeerinfo, defined in src/netbase.cpp::GetNetworkName()
+NET_UNROUTABLE = "unroutable"
+NET_IPV4 = "ipv4"
+NET_IPV6 = "ipv6"
+NET_ONION = "onion"
+
+# Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo()
+NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION})
+
+
class ProxyTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
@@ -77,23 +90,29 @@ class ProxyTest(BitcoinTestFramework):
self.serv3 = Socks5Server(self.conf3)
self.serv3.start()
- # Note: proxies are not used to connect to local nodes
- # this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost
+ # Note: proxies are not used to connect to local nodes. This is because the proxy to
+ # use is based on CService.GetNetwork(), which returns NET_UNROUTABLE for localhost.
args = [
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'],
['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
[]
- ]
+ ]
if self.have_ipv6:
args[3] = ['-listen', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion']
self.add_nodes(self.num_nodes, extra_args=args)
self.start_nodes()
+ def network_test(self, node, addr, network):
+ for peer in node.getpeerinfo():
+ if peer["addr"] == addr:
+ assert_equal(peer["network"], network)
+
def node_test(self, node, proxies, auth, test_onion=True):
rv = []
- # Test: outgoing IPv4 connection through node
- node.addnode("15.61.23.23:1234", "onetry")
+ addr = "15.61.23.23:1234"
+ self.log.debug("Test: outgoing IPv4 connection through node for address {}".format(addr))
+ node.addnode(addr, "onetry")
cmd = proxies[0].queue.get()
assert isinstance(cmd, Socks5Command)
# Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
@@ -104,10 +123,12 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
+ self.network_test(node, addr, network=NET_IPV4)
if self.have_ipv6:
- # Test: outgoing IPv6 connection through node
- node.addnode("[1233:3432:2434:2343:3234:2345:6546:4534]:5443", "onetry")
+ addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443"
+ self.log.debug("Test: outgoing IPv6 connection through node for address {}".format(addr))
+ node.addnode(addr, "onetry")
cmd = proxies[1].queue.get()
assert isinstance(cmd, Socks5Command)
# Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
@@ -118,10 +139,12 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
+ self.network_test(node, addr, network=NET_IPV6)
if test_onion:
- # Test: outgoing onion connection through node
- node.addnode("bitcoinostk4e4re.onion:8333", "onetry")
+ addr = "bitcoinostk4e4re.onion:8333"
+ self.log.debug("Test: outgoing onion connection through node for address {}".format(addr))
+ node.addnode(addr, "onetry")
cmd = proxies[2].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@@ -131,9 +154,11 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
+ self.network_test(node, addr, network=NET_ONION)
- # Test: outgoing DNS name connection through node
- node.addnode("node.noumenon:8333", "onetry")
+ addr = "node.noumenon:8333"
+ self.log.debug("Test: outgoing DNS name connection through node for address {}".format(addr))
+ node.addnode(addr, "onetry")
cmd = proxies[3].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@@ -143,6 +168,7 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
+ self.network_test(node, addr, network=NET_UNROUTABLE)
return rv
@@ -169,15 +195,17 @@ class ProxyTest(BitcoinTestFramework):
r[x['name']] = x
return r
- # test RPC getnetworkinfo
+ self.log.info("Test RPC getnetworkinfo")
n0 = networks_dict(self.nodes[0].getnetworkinfo())
- for net in ['ipv4','ipv6','onion']:
+ assert_equal(NETWORKS, n0.keys())
+ for net in NETWORKS:
assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n0[net]['proxy_randomize_credentials'], True)
assert_equal(n0['onion']['reachable'], True)
n1 = networks_dict(self.nodes[1].getnetworkinfo())
- for net in ['ipv4','ipv6']:
+ assert_equal(NETWORKS, n1.keys())
+ for net in ['ipv4', 'ipv6']:
assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n1[net]['proxy_randomize_credentials'], False)
assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
@@ -185,17 +213,20 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n1['onion']['reachable'], True)
n2 = networks_dict(self.nodes[2].getnetworkinfo())
- for net in ['ipv4','ipv6','onion']:
+ assert_equal(NETWORKS, n2.keys())
+ for net in NETWORKS:
assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr))
assert_equal(n2[net]['proxy_randomize_credentials'], True)
assert_equal(n2['onion']['reachable'], True)
if self.have_ipv6:
n3 = networks_dict(self.nodes[3].getnetworkinfo())
- for net in ['ipv4','ipv6']:
+ assert_equal(NETWORKS, n3.keys())
+ for net in NETWORKS:
assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr))
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
+
if __name__ == '__main__':
ProxyTest().main()
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index df54ae777b..f09bffe2d4 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -18,8 +18,6 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
- connect_nodes,
- disconnect_nodes,
)
# Rescans start at the earliest block up to 2 hours before a key timestamp, so
@@ -81,16 +79,16 @@ class PruneTest(BitcoinTestFramework):
# Create nodes 0 and 1 to mine.
# Create node 2 to test pruning.
- self.full_node_default_args = ["-maxreceivebuffer=20000", "-checkblocks=5", "-wallet="]
+ self.full_node_default_args = ["-maxreceivebuffer=20000", "-checkblocks=5"]
# Create nodes 3 and 4 to test manual pruning (they will be re-started with manual pruning later)
# Create nodes 5 to test wallet in prune mode, but do not connect
self.extra_args = [
self.full_node_default_args,
self.full_node_default_args,
- ["-wallet=", "-maxreceivebuffer=20000", "-prune=550"],
- ["-wallet=", "-maxreceivebuffer=20000"],
- ["-wallet=", "-maxreceivebuffer=20000"],
- ["-wallet=", "-prune=550"],
+ ["-maxreceivebuffer=20000", "-prune=550"],
+ ["-maxreceivebuffer=20000"],
+ ["-maxreceivebuffer=20000"],
+ ["-prune=550"],
]
self.rpc_timeout = 120
@@ -102,18 +100,17 @@ class PruneTest(BitcoinTestFramework):
self.prunedir = os.path.join(self.nodes[2].datadir, self.chain, 'blocks', '')
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 2)
- connect_nodes(self.nodes[0], 2)
- connect_nodes(self.nodes[0], 3)
- connect_nodes(self.nodes[0], 4)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 2)
+ self.connect_nodes(0, 2)
+ self.connect_nodes(0, 3)
+ self.connect_nodes(0, 4)
self.sync_blocks(self.nodes[0:5])
def setup_nodes(self):
self.add_nodes(self.num_nodes, self.extra_args)
self.start_nodes()
- for n in self.nodes:
- n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase', rescan=False)
+ self.import_deterministic_coinbase_privkeys()
def create_big_chain(self):
# Start by creating some coinbases we can spend later
@@ -149,8 +146,8 @@ class PruneTest(BitcoinTestFramework):
for _ in range(12):
# Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain
# Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects
- disconnect_nodes(self.nodes[0], 1)
- disconnect_nodes(self.nodes[0], 2)
+ self.disconnect_nodes(0, 1)
+ self.disconnect_nodes(0, 2)
# Mine 24 blocks in node 1
mine_large_blocks(self.nodes[1], 24)
@@ -158,8 +155,8 @@ class PruneTest(BitcoinTestFramework):
mine_large_blocks(self.nodes[0], 25)
# Create connections in the order so both nodes can see the reorg at the same time
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(0, 2)
self.sync_blocks(self.nodes[0:3])
self.log.info("Usage can be over target because of high stale rate: %d" % calc_usage(self.prunedir))
@@ -188,15 +185,15 @@ class PruneTest(BitcoinTestFramework):
self.log.info("New best height: %d" % self.nodes[1].getblockcount())
# Disconnect node1 and generate the new chain
- disconnect_nodes(self.nodes[0], 1)
- disconnect_nodes(self.nodes[1], 2)
+ self.disconnect_nodes(0, 1)
+ self.disconnect_nodes(1, 2)
self.log.info("Generating new longer chain of 300 more blocks")
self.nodes[1].generate(300)
self.log.info("Reconnect nodes")
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 2)
self.sync_blocks(self.nodes[0:3], timeout=120)
self.log.info("Verify height on node 2: %d" % self.nodes[2].getblockcount())
@@ -337,7 +334,7 @@ class PruneTest(BitcoinTestFramework):
# check that wallet loads successfully when restarting a pruned node after IBD.
# this was reported to fail in #7494.
self.log.info("Syncing node 5 to test wallet")
- connect_nodes(self.nodes[0], 5)
+ self.connect_nodes(0, 5)
nds = [self.nodes[0], self.nodes[5]]
self.sync_blocks(nds, wait=5, timeout=300)
self.restart_node(5, extra_args=["-prune=550"]) # restart to trigger rescan
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 0842972779..7bd2fc7847 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -22,7 +22,6 @@ from test_framework.util import (
assert_equal,
assert_is_hex_string,
assert_raises_rpc_error,
- connect_nodes,
hex_str_to_bytes,
try_rpc,
)
@@ -61,14 +60,12 @@ class SegWitTest(BitcoinTestFramework):
],
[
"-acceptnonstdtxn=1",
- "-blockversion=4",
"-rpcserialversion=1",
"-segwitheight=432",
"-addresstype=legacy",
],
[
"-acceptnonstdtxn=1",
- "-blockversion=536870915",
"-segwitheight=432",
"-addresstype=legacy",
],
@@ -80,7 +77,7 @@ class SegWitTest(BitcoinTestFramework):
def setup_network(self):
super().setup_network()
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 2)
self.sync_all()
def success_mine(self, node, txid, sign, redeem_script=""):
diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py
index c565854bb0..5a0236401d 100755
--- a/test/functional/feature_settings.py
+++ b/test/functional/feature_settings.py
@@ -17,6 +17,7 @@ class SettingsTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
+ self.wallet_names = []
def run_test(self):
node, = self.nodes
@@ -30,19 +31,25 @@ class SettingsTest(BitcoinTestFramework):
# Assert settings are parsed and logged
with settings.open("w") as fp:
- json.dump({"string": "string", "num": 5, "bool": True, "null": None, "list": [6,7]}, fp)
+ json.dump({"string": "string", "num": 5, "bool": True, "null": None, "list": [6, 7]}, fp)
with node.assert_debug_log(expected_msgs=[
+ 'Ignoring unknown rw_settings value bool',
+ 'Ignoring unknown rw_settings value list',
+ 'Ignoring unknown rw_settings value null',
+ 'Ignoring unknown rw_settings value num',
+ 'Ignoring unknown rw_settings value string',
'Setting file arg: string = "string"',
'Setting file arg: num = 5',
'Setting file arg: bool = true',
'Setting file arg: null = null',
- 'Setting file arg: list = [6,7]']):
+ 'Setting file arg: list = [6,7]',
+ ]):
self.start_node(0)
self.stop_node(0)
# Assert settings are unchanged after shutdown
with settings.open() as fp:
- assert_equal(json.load(fp), {"string": "string", "num": 5, "bool": True, "null": None, "list": [6,7]})
+ assert_equal(json.load(fp), {"string": "string", "num": 5, "bool": True, "null": None, "list": [6, 7]})
# Test invalid json
with settings.open("w") as fp:
diff --git a/test/functional/feature_signet.py b/test/functional/feature_signet.py
index a0e7f3ee6e..96c581dede 100755
--- a/test/functional/feature_signet.py
+++ b/test/functional/feature_signet.py
@@ -65,6 +65,10 @@ class SignetBasicTest(BitcoinTestFramework):
assert_equal(self.nodes[4].submitblock(signet_blocks[0]), 'bad-signet-blksig')
+ self.log.info("test that signet logs the network magic on node start")
+ with self.nodes[0].assert_debug_log(["Signet derived magic (message start)"]):
+ self.restart_node(0)
+
if __name__ == '__main__':
SignetBasicTest().main()
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
new file mode 100755
index 0000000000..6ee2b72c11
--- /dev/null
+++ b/test/functional/feature_taproot.py
@@ -0,0 +1,1485 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019-2020 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 Taproot softfork (BIPs 340-342)
+
+from test_framework.blocktools import (
+ create_coinbase,
+ create_block,
+ add_witness_commitment,
+ MAX_BLOCK_SIGOPS_WEIGHT,
+ NORMAL_GBT_REQUEST_PARAMS,
+ WITNESS_SCALE_FACTOR,
+)
+from test_framework.messages import (
+ COutPoint,
+ CTransaction,
+ CTxIn,
+ CTxInWitness,
+ CTxOut,
+ ToHex,
+)
+from test_framework.script import (
+ ANNEX_TAG,
+ CScript,
+ CScriptNum,
+ CScriptOp,
+ LEAF_VERSION_TAPSCRIPT,
+ LegacySignatureHash,
+ LOCKTIME_THRESHOLD,
+ MAX_SCRIPT_ELEMENT_SIZE,
+ OP_0,
+ OP_1,
+ OP_2,
+ OP_3,
+ OP_4,
+ OP_5,
+ OP_6,
+ OP_7,
+ OP_8,
+ OP_9,
+ OP_10,
+ OP_11,
+ OP_12,
+ OP_16,
+ OP_2DROP,
+ OP_2DUP,
+ OP_CHECKMULTISIG,
+ OP_CHECKMULTISIGVERIFY,
+ OP_CHECKSIG,
+ OP_CHECKSIGADD,
+ OP_CHECKSIGVERIFY,
+ OP_CODESEPARATOR,
+ OP_DROP,
+ OP_DUP,
+ OP_ELSE,
+ OP_ENDIF,
+ OP_EQUAL,
+ OP_EQUALVERIFY,
+ OP_HASH160,
+ OP_IF,
+ OP_NOP,
+ OP_NOT,
+ OP_NOTIF,
+ OP_PUSHDATA1,
+ OP_RETURN,
+ OP_SWAP,
+ OP_VERIFY,
+ SIGHASH_DEFAULT,
+ SIGHASH_ALL,
+ SIGHASH_NONE,
+ SIGHASH_SINGLE,
+ SIGHASH_ANYONECANPAY,
+ SegwitV0SignatureHash,
+ TaprootSignatureHash,
+ is_op_success,
+ taproot_construct,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_raises_rpc_error, assert_equal
+from test_framework.key import generate_privkey, compute_xonly_pubkey, sign_schnorr, tweak_add_privkey, ECKey
+from test_framework.address import (
+ hash160,
+ sha256,
+)
+from collections import OrderedDict, namedtuple
+from io import BytesIO
+import json
+import hashlib
+import os
+import random
+
+# === Framework for building spending transactions. ===
+#
+# The computation is represented as a "context" dict, whose entries store potentially-unevaluated expressions that
+# refer to lower-level ones. By overwriting these expression, many aspects - both high and low level - of the signing
+# process can be overridden.
+#
+# Specifically, a context object is a dict that maps names to compositions of:
+# - values
+# - lists of values
+# - callables which, when fed the context object as argument, produce any of these
+#
+# The DEFAULT_CONTEXT object specifies a standard signing process, with many overridable knobs.
+#
+# The get(ctx, name) function can evaluate a name, and cache its result in the context.
+# getter(name) can be used to construct a callable that evaluates name. For example:
+#
+# ctx1 = {**DEFAULT_CONTEXT, inputs=[getter("sign"), b'\x01']}
+#
+# creates a context where the script inputs are a signature plus the bytes 0x01.
+#
+# override(expr, name1=expr1, name2=expr2, ...) can be used to cause an expression to be evaluated in a selectively
+# modified context. For example:
+#
+# ctx2 = {**DEFAULT_CONTEXT, sighash=override(default_sighash, hashtype=SIGHASH_DEFAULT)}
+#
+# creates a context ctx2 where the sighash is modified to use hashtype=SIGHASH_DEFAULT. This differs from
+#
+# ctx3 = {**DEFAULT_CONTEXT, hashtype=SIGHASH_DEFAULT}
+#
+# in that ctx3 will globally use hashtype=SIGHASH_DEFAULT (including in the hashtype byte appended to the signature)
+# while ctx2 only uses the modified hashtype inside the sighash calculation.
+
+def deep_eval(ctx, expr):
+ """Recursively replace any callables c in expr (including inside lists) with c(ctx)."""
+ while callable(expr):
+ expr = expr(ctx)
+ if isinstance(expr, list):
+ expr = [deep_eval(ctx, x) for x in expr]
+ return expr
+
+# Data type to represent fully-evaluated expressions in a context dict (so we can avoid reevaluating them).
+Final = namedtuple("Final", "value")
+
+def get(ctx, name):
+ """Evaluate name in context ctx."""
+ assert name in ctx, "Missing '%s' in context" % name
+ expr = ctx[name]
+ if not isinstance(expr, Final):
+ # Evaluate and cache the result.
+ expr = Final(deep_eval(ctx, expr))
+ ctx[name] = expr
+ return expr.value
+
+def getter(name):
+ """Return a callable that evaluates name in its passed context."""
+ return lambda ctx: get(ctx, name)
+
+def override(expr, **kwargs):
+ """Return a callable that evaluates expr in a modified context."""
+ return lambda ctx: deep_eval({**ctx, **kwargs}, expr)
+
+# === Implementations for the various default expressions in DEFAULT_CONTEXT ===
+
+def default_hashtype(ctx):
+ """Default expression for "hashtype": SIGHASH_DEFAULT for taproot, SIGHASH_ALL otherwise."""
+ mode = get(ctx, "mode")
+ if mode == "taproot":
+ return SIGHASH_DEFAULT
+ else:
+ return SIGHASH_ALL
+
+def default_tapleaf(ctx):
+ """Default expression for "tapleaf": looking up leaf in tap[2]."""
+ return get(ctx, "tap").leaves[get(ctx, "leaf")]
+
+def default_script_taproot(ctx):
+ """Default expression for "script_taproot": tapleaf.script."""
+ return get(ctx, "tapleaf").script
+
+def default_leafversion(ctx):
+ """Default expression for "leafversion": tapleaf.version"""
+ return get(ctx, "tapleaf").version
+
+def default_negflag(ctx):
+ """Default expression for "negflag": tap.negflag."""
+ return get(ctx, "tap").negflag
+
+def default_pubkey_inner(ctx):
+ """Default expression for "pubkey_inner": tap.inner_pubkey."""
+ return get(ctx, "tap").inner_pubkey
+
+def default_merklebranch(ctx):
+ """Default expression for "merklebranch": tapleaf.merklebranch."""
+ return get(ctx, "tapleaf").merklebranch
+
+def default_controlblock(ctx):
+ """Default expression for "controlblock": combine leafversion, negflag, pubkey_inner, merklebranch."""
+ return bytes([get(ctx, "leafversion") + get(ctx, "negflag")]) + get(ctx, "pubkey_inner") + get(ctx, "merklebranch")
+
+def default_sighash(ctx):
+ """Default expression for "sighash": depending on mode, compute BIP341, BIP143, or legacy sighash."""
+ tx = get(ctx, "tx")
+ idx = get(ctx, "idx")
+ hashtype = get(ctx, "hashtype_actual")
+ mode = get(ctx, "mode")
+ if mode == "taproot":
+ # BIP341 signature hash
+ utxos = get(ctx, "utxos")
+ annex = get(ctx, "annex")
+ if get(ctx, "leaf") is not None:
+ codeseppos = get(ctx, "codeseppos")
+ leaf_ver = get(ctx, "leafversion")
+ script = get(ctx, "script_taproot")
+ return TaprootSignatureHash(tx, utxos, hashtype, idx, scriptpath=True, script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex)
+ else:
+ return TaprootSignatureHash(tx, utxos, hashtype, idx, scriptpath=False, annex=annex)
+ elif mode == "witv0":
+ # BIP143 signature hash
+ scriptcode = get(ctx, "scriptcode")
+ utxos = get(ctx, "utxos")
+ return SegwitV0SignatureHash(scriptcode, tx, idx, hashtype, utxos[idx].nValue)
+ else:
+ # Pre-segwit signature hash
+ scriptcode = get(ctx, "scriptcode")
+ return LegacySignatureHash(scriptcode, tx, idx, hashtype)[0]
+
+def default_tweak(ctx):
+ """Default expression for "tweak": None if a leaf is specified, tap[0] otherwise."""
+ if get(ctx, "leaf") is None:
+ return get(ctx, "tap").tweak
+ return None
+
+def default_key_tweaked(ctx):
+ """Default expression for "key_tweaked": key if tweak is None, tweaked with it otherwise."""
+ key = get(ctx, "key")
+ tweak = get(ctx, "tweak")
+ if tweak is None:
+ return key
+ else:
+ return tweak_add_privkey(key, tweak)
+
+def default_signature(ctx):
+ """Default expression for "signature": BIP340 signature or ECDSA signature depending on mode."""
+ sighash = get(ctx, "sighash")
+ if get(ctx, "mode") == "taproot":
+ key = get(ctx, "key_tweaked")
+ flip_r = get(ctx, "flag_flip_r")
+ flip_p = get(ctx, "flag_flip_p")
+ return sign_schnorr(key, sighash, flip_r=flip_r, flip_p=flip_p)
+ else:
+ key = get(ctx, "key")
+ return key.sign_ecdsa(sighash)
+
+def default_hashtype_actual(ctx):
+ """Default expression for "hashtype_actual": hashtype, unless mismatching SIGHASH_SINGLE in taproot."""
+ hashtype = get(ctx, "hashtype")
+ mode = get(ctx, "mode")
+ if mode != "taproot":
+ return hashtype
+ idx = get(ctx, "idx")
+ tx = get(ctx, "tx")
+ if hashtype & 3 == SIGHASH_SINGLE and idx >= len(tx.vout):
+ return (hashtype & ~3) | SIGHASH_NONE
+ return hashtype
+
+def default_bytes_hashtype(ctx):
+ """Default expression for "bytes_hashtype": bytes([hashtype_actual]) if not 0, b"" otherwise."""
+ return bytes([x for x in [get(ctx, "hashtype_actual")] if x != 0])
+
+def default_sign(ctx):
+ """Default expression for "sign": concatenation of signature and bytes_hashtype."""
+ return get(ctx, "signature") + get(ctx, "bytes_hashtype")
+
+def default_inputs_keypath(ctx):
+ """Default expression for "inputs_keypath": a signature."""
+ return [get(ctx, "sign")]
+
+def default_witness_taproot(ctx):
+ """Default expression for "witness_taproot", consisting of inputs, script, control block, and annex as needed."""
+ annex = get(ctx, "annex")
+ suffix_annex = []
+ if annex is not None:
+ suffix_annex = [annex]
+ if get(ctx, "leaf") is None:
+ return get(ctx, "inputs_keypath") + suffix_annex
+ else:
+ return get(ctx, "inputs") + [bytes(get(ctx, "script_taproot")), get(ctx, "controlblock")] + suffix_annex
+
+def default_witness_witv0(ctx):
+ """Default expression for "witness_witv0", consisting of inputs and witness script, as needed."""
+ script = get(ctx, "script_witv0")
+ inputs = get(ctx, "inputs")
+ if script is None:
+ return inputs
+ else:
+ return inputs + [script]
+
+def default_witness(ctx):
+ """Default expression for "witness", delegating to "witness_taproot" or "witness_witv0" as needed."""
+ mode = get(ctx, "mode")
+ if mode == "taproot":
+ return get(ctx, "witness_taproot")
+ elif mode == "witv0":
+ return get(ctx, "witness_witv0")
+ else:
+ return []
+
+def default_scriptsig(ctx):
+ """Default expression for "scriptsig", consisting of inputs and redeemscript, as needed."""
+ scriptsig = []
+ mode = get(ctx, "mode")
+ if mode == "legacy":
+ scriptsig = get(ctx, "inputs")
+ redeemscript = get(ctx, "script_p2sh")
+ if redeemscript is not None:
+ scriptsig += [bytes(redeemscript)]
+ return scriptsig
+
+# The default context object.
+DEFAULT_CONTEXT = {
+ # == The main expressions to evaluate. Only override these for unusual or invalid spends. ==
+ # The overall witness stack, as a list of bytes objects.
+ "witness": default_witness,
+ # The overall scriptsig, as a list of CScript objects (to be concatenated) and bytes objects (to be pushed)
+ "scriptsig": default_scriptsig,
+
+ # == Expressions you'll generally only override for intentionally invalid spends. ==
+ # The witness stack for spending a taproot output.
+ "witness_taproot": default_witness_taproot,
+ # The witness stack for spending a P2WPKH/P2WSH output.
+ "witness_witv0": default_witness_witv0,
+ # The script inputs for a taproot key path spend.
+ "inputs_keypath": default_inputs_keypath,
+ # The actual hashtype to use (usually equal to hashtype, but in taproot SIGHASH_SINGLE is not always allowed).
+ "hashtype_actual": default_hashtype_actual,
+ # The bytes object for a full signature (including hashtype byte, if needed).
+ "bytes_hashtype": default_bytes_hashtype,
+ # A full script signature (bytes including hashtype, if needed)
+ "sign": default_sign,
+ # An ECDSA or Schnorr signature (excluding hashtype byte).
+ "signature": default_signature,
+ # The 32-byte tweaked key (equal to key for script path spends, or key+tweak for key path spends).
+ "key_tweaked": default_key_tweaked,
+ # The tweak to use (None for script path spends, the actual tweak for key path spends).
+ "tweak": default_tweak,
+ # The sighash value (32 bytes)
+ "sighash": default_sighash,
+ # The information about the chosen script path spend (TaprootLeafInfo object).
+ "tapleaf": default_tapleaf,
+ # The script to push, and include in the sighash, for a taproot script path spend.
+ "script_taproot": default_script_taproot,
+ # The inner pubkey for a taproot script path spend (32 bytes).
+ "pubkey_inner": default_pubkey_inner,
+ # The negation flag of the inner pubkey for a taproot script path spend.
+ "negflag": default_negflag,
+ # The leaf version to include in the sighash (this does not affect the one in the control block).
+ "leafversion": default_leafversion,
+ # The Merkle path to include in the control block for a script path spend.
+ "merklebranch": default_merklebranch,
+ # The control block to push for a taproot script path spend.
+ "controlblock": default_controlblock,
+ # Whether to produce signatures with invalid P sign (Schnorr signatures only).
+ "flag_flip_p": False,
+ # Whether to produce signatures with invalid R sign (Schnorr signatures only).
+ "flag_flip_r": False,
+
+ # == Parameters that can be changed without invalidating, but do have a default: ==
+ # The hashtype (as an integer).
+ "hashtype": default_hashtype,
+ # The annex (only when mode=="taproot").
+ "annex": None,
+ # The codeseparator position (only when mode=="taproot").
+ "codeseppos": -1,
+ # The redeemscript to add to the scriptSig (if P2SH; None implies not P2SH).
+ "script_p2sh": None,
+ # The script to add to the witness in (if P2WSH; None implies P2WPKH)
+ "script_witv0": None,
+ # The leaf to use in taproot spends (if script path spend; None implies key path spend).
+ "leaf": None,
+ # The input arguments to provide to the executed script
+ "inputs": [],
+
+ # == Parameters to be set before evaluation: ==
+ # - mode: what spending style to use ("taproot", "witv0", or "legacy").
+ # - key: the (untweaked) private key to sign with (ECKey object for ECDSA, 32 bytes for Schnorr).
+ # - tap: the TaprootInfo object (see taproot_construct; needed in mode=="taproot").
+ # - tx: the transaction to sign.
+ # - utxos: the UTXOs being spent (needed in mode=="witv0" and mode=="taproot").
+ # - idx: the input position being signed.
+ # - scriptcode: the scriptcode to include in legacy and witv0 sighashes.
+}
+
+def flatten(lst):
+ ret = []
+ for elem in lst:
+ if isinstance(elem, list):
+ ret += flatten(elem)
+ else:
+ ret.append(elem)
+ return ret
+
+def spend(tx, idx, utxos, **kwargs):
+ """Sign transaction input idx of tx, provided utxos is the list of outputs being spent.
+
+ Additional arguments may be provided that override any aspect of the signing process.
+ See DEFAULT_CONTEXT above for what can be overridden, and what must be provided.
+ """
+
+ ctx = {**DEFAULT_CONTEXT, "tx":tx, "idx":idx, "utxos":utxos, **kwargs}
+
+ def to_script(elem):
+ """If fed a CScript, return it; if fed bytes, return a CScript that pushes it."""
+ if isinstance(elem, CScript):
+ return elem
+ else:
+ return CScript([elem])
+
+ scriptsig_list = flatten(get(ctx, "scriptsig"))
+ scriptsig = CScript(b"".join(bytes(to_script(elem)) for elem in scriptsig_list))
+ witness_stack = flatten(get(ctx, "witness"))
+ return (scriptsig, witness_stack)
+
+
+# === Spender objects ===
+#
+# Each spender is a tuple of:
+# - A scriptPubKey which is to be spent from (CScript)
+# - A comment describing the test (string)
+# - Whether the spending (on itself) is expected to be standard (bool)
+# - A tx-signing lambda returning (scriptsig, witness_stack), taking as inputs:
+# - A transaction to sign (CTransaction)
+# - An input position (int)
+# - The spent UTXOs by this transaction (list of CTxOut)
+# - Whether to produce a valid spend (bool)
+# - A string with an expected error message for failure case if known
+# - The (pre-taproot) sigops weight consumed by a successful spend
+# - Whether this spend cannot fail
+# - Whether this test demands being placed in a txin with no corresponding txout (for testing SIGHASH_SINGLE behavior)
+
+Spender = namedtuple("Spender", "script,comment,is_standard,sat_function,err_msg,sigops_weight,no_fail,need_vin_vout_mismatch")
+
+def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=True, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs):
+ """Helper for constructing Spender objects using the context signing framework.
+
+ * tap: a TaprootInfo object (see taproot_construct), for Taproot spends (cannot be combined with pkh, witv0, or script)
+ * witv0: boolean indicating the use of witness v0 spending (needs one of script or pkh)
+ * script: the actual script executed (for bare/P2WSH/P2SH spending)
+ * pkh: the public key for P2PKH or P2WPKH spending
+ * p2sh: whether the output is P2SH wrapper (this is supported even for Taproot, where it makes the output unencumbered)
+ * spk_mutate_pre_psh: a callable to be applied to the script (before potentially P2SH-wrapping it)
+ * failure: a dict of entries to override in the context when intentionally failing to spend (if None, no_fail will be set)
+ * standard: whether the (valid version of) spending is expected to be standard
+ * err_msg: a string with an expected error message for failure (or None, if not cared about)
+ * sigops_weight: the pre-taproot sigops weight consumed by a successful spend
+ * need_vin_vout_mismatch: whether this test requires being tested in a transaction input that has no corresponding
+ transaction output.
+ """
+
+ conf = dict()
+
+ # Compute scriptPubKey and set useful defaults based on the inputs.
+ if witv0:
+ assert tap is None
+ conf["mode"] = "witv0"
+ if pkh is not None:
+ # P2WPKH
+ assert script is None
+ pubkeyhash = hash160(pkh)
+ spk = CScript([OP_0, pubkeyhash])
+ conf["scriptcode"] = CScript([OP_DUP, OP_HASH160, pubkeyhash, OP_EQUALVERIFY, OP_CHECKSIG])
+ conf["script_witv0"] = None
+ conf["inputs"] = [getter("sign"), pkh]
+ elif script is not None:
+ # P2WSH
+ spk = CScript([OP_0, sha256(script)])
+ conf["scriptcode"] = script
+ conf["script_witv0"] = script
+ else:
+ assert False
+ elif tap is None:
+ conf["mode"] = "legacy"
+ if pkh is not None:
+ # P2PKH
+ assert script is None
+ pubkeyhash = hash160(pkh)
+ spk = CScript([OP_DUP, OP_HASH160, pubkeyhash, OP_EQUALVERIFY, OP_CHECKSIG])
+ conf["scriptcode"] = spk
+ conf["inputs"] = [getter("sign"), pkh]
+ elif script is not None:
+ # bare
+ spk = script
+ conf["scriptcode"] = script
+ else:
+ assert False
+ else:
+ assert script is None
+ conf["mode"] = "taproot"
+ conf["tap"] = tap
+ spk = tap.scriptPubKey
+
+ if spk_mutate_pre_p2sh is not None:
+ spk = spk_mutate_pre_p2sh(spk)
+
+ if p2sh:
+ # P2SH wrapper can be combined with anything else
+ conf["script_p2sh"] = spk
+ spk = CScript([OP_HASH160, hash160(spk), OP_EQUAL])
+
+ conf = {**conf, **kwargs}
+
+ def sat_fn(tx, idx, utxos, valid):
+ if valid:
+ return spend(tx, idx, utxos, **conf)
+ else:
+ assert failure is not None
+ return spend(tx, idx, utxos, **{**conf, **failure})
+
+ return Spender(script=spk, comment=comment, is_standard=standard, sat_function=sat_fn, err_msg=err_msg, sigops_weight=sigops_weight, no_fail=failure is None, need_vin_vout_mismatch=need_vin_vout_mismatch)
+
+def add_spender(spenders, *args, **kwargs):
+ """Make a spender using make_spender, and add it to spenders."""
+ spenders.append(make_spender(*args, **kwargs))
+
+# === Helpers for the test ===
+
+def random_checksig_style(pubkey):
+ """Creates a random CHECKSIG* tapscript that would succeed with only the valid signature on witness stack."""
+ return bytes(CScript([pubkey, OP_CHECKSIG]))
+ opcode = random.choice([OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKSIGADD])
+ if (opcode == OP_CHECKSIGVERIFY):
+ ret = CScript([pubkey, opcode, OP_1])
+ elif (opcode == OP_CHECKSIGADD):
+ num = random.choice([0, 0x7fffffff, -0x7fffffff])
+ ret = CScript([num, pubkey, opcode, num + 1, OP_EQUAL])
+ else:
+ ret = CScript([pubkey, opcode])
+ return bytes(ret)
+
+def random_bytes(n):
+ """Return a random bytes object of length n."""
+ return bytes(random.getrandbits(8) for i in range(n))
+
+def bitflipper(expr):
+ """Return a callable that evaluates expr and returns it with a random bitflip."""
+ def fn(ctx):
+ sub = deep_eval(ctx, expr)
+ assert isinstance(sub, bytes)
+ return (int.from_bytes(sub, 'little') ^ (1 << random.randrange(len(sub) * 8))).to_bytes(len(sub), 'little')
+ return fn
+
+def zero_appender(expr):
+ """Return a callable that evaluates expr and returns it with a zero added."""
+ return lambda ctx: deep_eval(ctx, expr) + b"\x00"
+
+def byte_popper(expr):
+ """Return a callable that evaluates expr and returns it with its last byte removed."""
+ return lambda ctx: deep_eval(ctx, expr)[:-1]
+
+# Expected error strings
+
+ERR_SIG_SIZE = {"err_msg": "Invalid Schnorr signature size"}
+ERR_SIG_HASHTYPE = {"err_msg": "Invalid Schnorr signature hash type"}
+ERR_SIG_SCHNORR = {"err_msg": "Invalid Schnorr signature"}
+ERR_OP_RETURN = {"err_msg": "OP_RETURN was encountered"}
+ERR_CONTROLBLOCK_SIZE = {"err_msg": "Invalid Taproot control block size"}
+ERR_WITNESS_PROGRAM_MISMATCH = {"err_msg": "Witness program hash mismatch"}
+ERR_PUSH_LIMIT = {"err_msg": "Push value size limit exceeded"}
+ERR_DISABLED_OPCODE = {"err_msg": "Attempted to use a disabled opcode"}
+ERR_TAPSCRIPT_CHECKMULTISIG = {"err_msg": "OP_CHECKMULTISIG(VERIFY) is not available in tapscript"}
+ERR_MINIMALIF = {"err_msg": "OP_IF/NOTIF argument must be minimal in tapscript"}
+ERR_UNKNOWN_PUBKEY = {"err_msg": "Public key is neither compressed or uncompressed"}
+ERR_STACK_SIZE = {"err_msg": "Stack size limit exceeded"}
+ERR_CLEANSTACK = {"err_msg": "Stack size must be exactly one after execution"}
+ERR_STACK_EMPTY = {"err_msg": "Operation not valid with the current stack size"}
+ERR_SIGOPS_RATIO = {"err_msg": "Too much signature validation relative to witness weight"}
+ERR_UNDECODABLE = {"err_msg": "Opcode missing or not understood"}
+ERR_NO_SUCCESS = {"err_msg": "Script evaluated without error but finished with a false/empty top stack element"}
+ERR_EMPTY_WITNESS = {"err_msg": "Witness program was passed an empty witness"}
+ERR_CHECKSIGVERIFY = {"err_msg": "Script failed an OP_CHECKSIGVERIFY operation"}
+
+VALID_SIGHASHES_ECDSA = [
+ SIGHASH_ALL,
+ SIGHASH_NONE,
+ SIGHASH_SINGLE,
+ SIGHASH_ANYONECANPAY + SIGHASH_ALL,
+ SIGHASH_ANYONECANPAY + SIGHASH_NONE,
+ SIGHASH_ANYONECANPAY + SIGHASH_SINGLE
+]
+
+VALID_SIGHASHES_TAPROOT = [SIGHASH_DEFAULT] + VALID_SIGHASHES_ECDSA
+
+VALID_SIGHASHES_TAPROOT_SINGLE = [
+ SIGHASH_SINGLE,
+ SIGHASH_ANYONECANPAY + SIGHASH_SINGLE
+]
+
+VALID_SIGHASHES_TAPROOT_NO_SINGLE = [h for h in VALID_SIGHASHES_TAPROOT if h not in VALID_SIGHASHES_TAPROOT_SINGLE]
+
+SIGHASH_BITFLIP = {"failure": {"sighash": bitflipper(default_sighash)}}
+SIG_POP_BYTE = {"failure": {"sign": byte_popper(default_sign)}}
+SINGLE_SIG = {"inputs": [getter("sign")]}
+SIG_ADD_ZERO = {"failure": {"sign": zero_appender(default_sign)}}
+
+DUST_LIMIT = 600
+MIN_FEE = 50000
+
+# === Actual test cases ===
+
+
+def spenders_taproot_active():
+ """Return a list of Spenders for testing post-Taproot activation behavior."""
+
+ secs = [generate_privkey() for _ in range(8)]
+ pubs = [compute_xonly_pubkey(sec)[0] for sec in secs]
+
+ spenders = []
+
+ # == Tests for BIP340 signature validation. ==
+ # These are primarily tested through the test vectors implemented in libsecp256k1, and in src/tests/key_tests.cpp.
+ # Some things are tested programmatically as well here.
+
+ tap = taproot_construct(pubs[0])
+ # Test with key with bit flipped.
+ add_spender(spenders, "sig/key", tap=tap, key=secs[0], failure={"key_tweaked": bitflipper(default_key_tweaked)}, **ERR_SIG_SCHNORR)
+ # Test with sighash with bit flipped.
+ add_spender(spenders, "sig/sighash", tap=tap, key=secs[0], failure={"sighash": bitflipper(default_sighash)}, **ERR_SIG_SCHNORR)
+ # Test with invalid R sign.
+ add_spender(spenders, "sig/flip_r", tap=tap, key=secs[0], failure={"flag_flip_r": True}, **ERR_SIG_SCHNORR)
+ # Test with invalid P sign.
+ add_spender(spenders, "sig/flip_p", tap=tap, key=secs[0], failure={"flag_flip_p": True}, **ERR_SIG_SCHNORR)
+ # Test with signature with bit flipped.
+ add_spender(spenders, "sig/bitflip", tap=tap, key=secs[0], failure={"signature": bitflipper(default_signature)}, **ERR_SIG_SCHNORR)
+
+ # == Tests for signature hashing ==
+
+ # Run all tests once with no annex, and once with a valid random annex.
+ for annex in [None, lambda _: bytes([ANNEX_TAG]) + random_bytes(random.randrange(0, 250))]:
+ # Non-empty annex is non-standard
+ no_annex = annex is None
+
+ # Sighash mutation tests (test all sighash combinations)
+ for hashtype in VALID_SIGHASHES_TAPROOT:
+ common = {"annex": annex, "hashtype": hashtype, "standard": no_annex}
+
+ # Pure pubkey
+ tap = taproot_construct(pubs[0])
+ add_spender(spenders, "sighash/purepk", tap=tap, key=secs[0], **common, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
+
+ # Pubkey/P2PK script combination
+ scripts = [("s0", CScript(random_checksig_style(pubs[1])))]
+ tap = taproot_construct(pubs[0], scripts)
+ add_spender(spenders, "sighash/keypath_hashtype_%x" % hashtype, tap=tap, key=secs[0], **common, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/scriptpath_hashtype_%x" % hashtype, tap=tap, leaf="s0", key=secs[1], **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
+
+ # Test SIGHASH_SINGLE behavior in combination with mismatching outputs
+ if hashtype in VALID_SIGHASHES_TAPROOT_SINGLE:
+ add_spender(spenders, "sighash/keypath_hashtype_mis_%x" % hashtype, tap=tap, key=secs[0], annex=annex, standard=no_annex, hashtype_actual=random.choice(VALID_SIGHASHES_TAPROOT_NO_SINGLE), failure={"hashtype_actual": hashtype}, **ERR_SIG_HASHTYPE, need_vin_vout_mismatch=True)
+ add_spender(spenders, "sighash/scriptpath_hashtype_mis_%x" % hashtype, tap=tap, leaf="s0", key=secs[1], annex=annex, standard=no_annex, hashtype_actual=random.choice(VALID_SIGHASHES_TAPROOT_NO_SINGLE), **SINGLE_SIG, failure={"hashtype_actual": hashtype}, **ERR_SIG_HASHTYPE, need_vin_vout_mismatch=True)
+
+ # Test OP_CODESEPARATOR impact on sighashing.
+ hashtype = lambda _: random.choice(VALID_SIGHASHES_TAPROOT)
+ common = {"annex": annex, "hashtype": hashtype, "standard": no_annex}
+ scripts = [
+ ("pk_codesep", CScript(random_checksig_style(pubs[1]) + bytes([OP_CODESEPARATOR]))), # codesep after checksig
+ ("codesep_pk", CScript(bytes([OP_CODESEPARATOR]) + random_checksig_style(pubs[1]))), # codesep before checksig
+ ("branched_codesep", CScript([random_bytes(random.randrange(511)), OP_DROP, OP_IF, OP_CODESEPARATOR, pubs[0], OP_ELSE, OP_CODESEPARATOR, pubs[1], OP_ENDIF, OP_CHECKSIG])), # branch dependent codesep
+ ]
+ random.shuffle(scripts)
+ tap = taproot_construct(pubs[0], scripts)
+ add_spender(spenders, "sighash/pk_codesep", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/codesep_pk", tap=tap, leaf="codesep_pk", key=secs[1], codeseppos=0, **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/branched_codesep/left", tap=tap, leaf="branched_codesep", key=secs[0], codeseppos=3, **common, inputs=[getter("sign"), b'\x01'], **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/branched_codesep/right", tap=tap, leaf="branched_codesep", key=secs[1], codeseppos=6, **common, inputs=[getter("sign"), b''], **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
+
+ # Reusing the scripts above, test that various features affect the sighash.
+ add_spender(spenders, "sighash/annex", tap=tap, leaf="pk_codesep", key=secs[1], hashtype=hashtype, standard=False, **SINGLE_SIG, annex=bytes([ANNEX_TAG]), failure={"sighash": override(default_sighash, annex=None)}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/script", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, script_taproot=tap.leaves["codesep_pk"].script)}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/leafver", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leafversion=random.choice([x & 0xFE for x in range(0x100) if x & 0xFE != 0xC0]))}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/scriptpath", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leaf=None)}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/keypath", tap=tap, key=secs[0], **common, failure={"sighash": override(default_sighash, leaf="pk_codesep")}, **ERR_SIG_SCHNORR)
+
+ # Test that invalid hashtypes don't work, both in key path and script path spends
+ hashtype = lambda _: random.choice(VALID_SIGHASHES_TAPROOT)
+ for invalid_hashtype in [x for x in range(0x100) if x not in VALID_SIGHASHES_TAPROOT]:
+ add_spender(spenders, "sighash/keypath_unk_hashtype_%x" % invalid_hashtype, tap=tap, key=secs[0], hashtype=hashtype, failure={"hashtype": invalid_hashtype}, **ERR_SIG_HASHTYPE)
+ add_spender(spenders, "sighash/scriptpath_unk_hashtype_%x" % invalid_hashtype, tap=tap, leaf="pk_codesep", key=secs[1], **SINGLE_SIG, hashtype=hashtype, failure={"hashtype": invalid_hashtype}, **ERR_SIG_HASHTYPE)
+
+ # Test that hashtype 0 cannot have a hashtype byte, and 1 must have one.
+ add_spender(spenders, "sighash/hashtype0_byte_keypath", tap=tap, key=secs[0], hashtype=SIGHASH_DEFAULT, failure={"bytes_hashtype": bytes([SIGHASH_DEFAULT])}, **ERR_SIG_HASHTYPE)
+ add_spender(spenders, "sighash/hashtype0_byte_scriptpath", tap=tap, leaf="pk_codesep", key=secs[1], **SINGLE_SIG, hashtype=SIGHASH_DEFAULT, failure={"bytes_hashtype": bytes([SIGHASH_DEFAULT])}, **ERR_SIG_HASHTYPE)
+ add_spender(spenders, "sighash/hashtype1_byte_keypath", tap=tap, key=secs[0], hashtype=SIGHASH_ALL, failure={"bytes_hashtype": b''}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/hashtype1_byte_scriptpath", tap=tap, leaf="pk_codesep", key=secs[1], **SINGLE_SIG, hashtype=SIGHASH_ALL, failure={"bytes_hashtype": b''}, **ERR_SIG_SCHNORR)
+ # Test that hashtype 0 and hashtype 1 cannot be transmuted into each other.
+ add_spender(spenders, "sighash/hashtype0to1_keypath", tap=tap, key=secs[0], hashtype=SIGHASH_DEFAULT, failure={"bytes_hashtype": bytes([SIGHASH_ALL])}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/hashtype0to1_scriptpath", tap=tap, leaf="pk_codesep", key=secs[1], **SINGLE_SIG, hashtype=SIGHASH_DEFAULT, failure={"bytes_hashtype": bytes([SIGHASH_ALL])}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/hashtype1to0_keypath", tap=tap, key=secs[0], hashtype=SIGHASH_ALL, failure={"bytes_hashtype": b''}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "sighash/hashtype1to0_scriptpath", tap=tap, leaf="pk_codesep", key=secs[1], **SINGLE_SIG, hashtype=SIGHASH_ALL, failure={"bytes_hashtype": b''}, **ERR_SIG_SCHNORR)
+
+ # Test aspects of signatures with unusual lengths
+ for hashtype in [SIGHASH_DEFAULT, random.choice(VALID_SIGHASHES_TAPROOT)]:
+ scripts = [
+ ("csv", CScript([pubs[2], OP_CHECKSIGVERIFY, OP_1])),
+ ("cs_pos", CScript([pubs[2], OP_CHECKSIG])),
+ ("csa_pos", CScript([OP_0, pubs[2], OP_CHECKSIGADD, OP_1, OP_EQUAL])),
+ ("cs_neg", CScript([pubs[2], OP_CHECKSIG, OP_NOT])),
+ ("csa_neg", CScript([OP_2, pubs[2], OP_CHECKSIGADD, OP_2, OP_EQUAL]))
+ ]
+ random.shuffle(scripts)
+ tap = taproot_construct(pubs[3], scripts)
+ # Empty signatures
+ add_spender(spenders, "siglen/empty_keypath", tap=tap, key=secs[3], hashtype=hashtype, failure={"sign": b""}, **ERR_SIG_SIZE)
+ add_spender(spenders, "siglen/empty_csv", tap=tap, key=secs[2], leaf="csv", hashtype=hashtype, **SINGLE_SIG, failure={"sign": b""}, **ERR_CHECKSIGVERIFY)
+ add_spender(spenders, "siglen/empty_cs", tap=tap, key=secs[2], leaf="cs_pos", hashtype=hashtype, **SINGLE_SIG, failure={"sign": b""}, **ERR_NO_SUCCESS)
+ add_spender(spenders, "siglen/empty_csa", tap=tap, key=secs[2], leaf="csa_pos", hashtype=hashtype, **SINGLE_SIG, failure={"sign": b""}, **ERR_NO_SUCCESS)
+ add_spender(spenders, "siglen/empty_cs_neg", tap=tap, key=secs[2], leaf="cs_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", failure={"sign": lambda _: random_bytes(random.randrange(1, 63))}, **ERR_SIG_SIZE)
+ add_spender(spenders, "siglen/empty_csa_neg", tap=tap, key=secs[2], leaf="csa_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", failure={"sign": lambda _: random_bytes(random.randrange(66, 100))}, **ERR_SIG_SIZE)
+ # Appending a zero byte to signatures invalidates them
+ add_spender(spenders, "siglen/padzero_keypath", tap=tap, key=secs[3], hashtype=hashtype, **SIG_ADD_ZERO, **(ERR_SIG_HASHTYPE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SIZE))
+ add_spender(spenders, "siglen/padzero_csv", tap=tap, key=secs[2], leaf="csv", hashtype=hashtype, **SINGLE_SIG, **SIG_ADD_ZERO, **(ERR_SIG_HASHTYPE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SIZE))
+ add_spender(spenders, "siglen/padzero_cs", tap=tap, key=secs[2], leaf="cs_pos", hashtype=hashtype, **SINGLE_SIG, **SIG_ADD_ZERO, **(ERR_SIG_HASHTYPE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SIZE))
+ add_spender(spenders, "siglen/padzero_csa", tap=tap, key=secs[2], leaf="csa_pos", hashtype=hashtype, **SINGLE_SIG, **SIG_ADD_ZERO, **(ERR_SIG_HASHTYPE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SIZE))
+ add_spender(spenders, "siglen/padzero_cs_neg", tap=tap, key=secs[2], leaf="cs_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", **SIG_ADD_ZERO, **(ERR_SIG_HASHTYPE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SIZE))
+ add_spender(spenders, "siglen/padzero_csa_neg", tap=tap, key=secs[2], leaf="csa_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", **SIG_ADD_ZERO, **(ERR_SIG_HASHTYPE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SIZE))
+ # Removing the last byte from signatures invalidates them
+ add_spender(spenders, "siglen/popbyte_keypath", tap=tap, key=secs[3], hashtype=hashtype, **SIG_POP_BYTE, **(ERR_SIG_SIZE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SCHNORR))
+ add_spender(spenders, "siglen/popbyte_csv", tap=tap, key=secs[2], leaf="csv", hashtype=hashtype, **SINGLE_SIG, **SIG_POP_BYTE, **(ERR_SIG_SIZE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SCHNORR))
+ add_spender(spenders, "siglen/popbyte_cs", tap=tap, key=secs[2], leaf="cs_pos", hashtype=hashtype, **SINGLE_SIG, **SIG_POP_BYTE, **(ERR_SIG_SIZE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SCHNORR))
+ add_spender(spenders, "siglen/popbyte_csa", tap=tap, key=secs[2], leaf="csa_pos", hashtype=hashtype, **SINGLE_SIG, **SIG_POP_BYTE, **(ERR_SIG_SIZE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SCHNORR))
+ add_spender(spenders, "siglen/popbyte_cs_neg", tap=tap, key=secs[2], leaf="cs_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", **SIG_POP_BYTE, **(ERR_SIG_SIZE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SCHNORR))
+ add_spender(spenders, "siglen/popbyte_csa_neg", tap=tap, key=secs[2], leaf="csa_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", **SIG_POP_BYTE, **(ERR_SIG_SIZE if hashtype == SIGHASH_DEFAULT else ERR_SIG_SCHNORR))
+ # Verify that an invalid signature is not allowed, not even when the CHECKSIG* is expected to fail.
+ add_spender(spenders, "siglen/invalid_cs_neg", tap=tap, key=secs[2], leaf="cs_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", failure={"sign": default_sign, "sighash": bitflipper(default_sighash)}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "siglen/invalid_csa_neg", tap=tap, key=secs[2], leaf="csa_neg", hashtype=hashtype, **SINGLE_SIG, sign=b"", failure={"sign": default_sign, "sighash": bitflipper(default_sighash)}, **ERR_SIG_SCHNORR)
+
+ # == Test that BIP341 spending only applies to witness version 1, program length 32, no P2SH ==
+
+ for p2sh in [False, True]:
+ for witver in range(1, 17):
+ for witlen in [20, 31, 32, 33]:
+ def mutate(spk):
+ prog = spk[2:]
+ assert len(prog) == 32
+ if witlen < 32:
+ prog = prog[0:witlen]
+ elif witlen > 32:
+ prog += bytes([0 for _ in range(witlen - 32)])
+ return CScript([CScriptOp.encode_op_n(witver), prog])
+ scripts = [("s0", CScript([pubs[0], OP_CHECKSIG])), ("dummy", CScript([OP_RETURN]))]
+ tap = taproot_construct(pubs[1], scripts)
+ if not p2sh and witver == 1 and witlen == 32:
+ add_spender(spenders, "applic/keypath", p2sh=p2sh, spk_mutate_pre_p2sh=mutate, tap=tap, key=secs[1], **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "applic/scriptpath", p2sh=p2sh, leaf="s0", spk_mutate_pre_p2sh=mutate, tap=tap, key=secs[0], **SINGLE_SIG, failure={"leaf": "dummy"}, **ERR_OP_RETURN)
+ else:
+ add_spender(spenders, "applic/keypath", p2sh=p2sh, spk_mutate_pre_p2sh=mutate, tap=tap, key=secs[1], standard=False)
+ add_spender(spenders, "applic/scriptpath", p2sh=p2sh, leaf="s0", spk_mutate_pre_p2sh=mutate, tap=tap, key=secs[0], **SINGLE_SIG, standard=False)
+
+ # == Test various aspects of BIP341 spending paths ==
+
+ # A set of functions that compute the hashing partner in a Merkle tree, designed to exercise
+ # edge cases. This relies on the taproot_construct feature that a lambda can be passed in
+ # instead of a subtree, to compute the partner to be hashed with.
+ PARTNER_MERKLE_FN = [
+ # Combine with itself
+ lambda h: h,
+ # Combine with hash 0
+ lambda h: bytes([0 for _ in range(32)]),
+ # Combine with hash 2^256-1
+ lambda h: bytes([0xff for _ in range(32)]),
+ # Combine with itself-1 (BE)
+ lambda h: (int.from_bytes(h, 'big') - 1).to_bytes(32, 'big'),
+ # Combine with itself+1 (BE)
+ lambda h: (int.from_bytes(h, 'big') + 1).to_bytes(32, 'big'),
+ # Combine with itself-1 (LE)
+ lambda h: (int.from_bytes(h, 'little') - 1).to_bytes(32, 'big'),
+ # Combine with itself+1 (LE)
+ lambda h: (int.from_bytes(h, 'little') + 1).to_bytes(32, 'little'),
+ # Combine with random bitflipped version of self.
+ lambda h: (int.from_bytes(h, 'little') ^ (1 << random.randrange(256))).to_bytes(32, 'little')
+ ]
+ # Start with a tree of that has depth 1 for "128deep" and depth 2 for "129deep".
+ scripts = [("128deep", CScript([pubs[0], OP_CHECKSIG])), [("129deep", CScript([pubs[0], OP_CHECKSIG])), random.choice(PARTNER_MERKLE_FN)]]
+ # Add 127 nodes on top of that tree, so that "128deep" and "129deep" end up at their designated depths.
+ for _ in range(127):
+ scripts = [scripts, random.choice(PARTNER_MERKLE_FN)]
+ tap = taproot_construct(pubs[0], scripts)
+ # Test that spends with a depth of 128 work, but 129 doesn't (even with a tree with weird Merkle branches in it).
+ add_spender(spenders, "spendpath/merklelimit", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"leaf": "129deep"}, **ERR_CONTROLBLOCK_SIZE)
+ # Test that flipping the negation bit invalidates spends.
+ add_spender(spenders, "spendpath/negflag", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"negflag": lambda ctx: 1 - default_negflag(ctx)}, **ERR_WITNESS_PROGRAM_MISMATCH)
+ # Test that bitflips in the Merkle branch invalidate it.
+ add_spender(spenders, "spendpath/bitflipmerkle", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"merklebranch": bitflipper(default_merklebranch)}, **ERR_WITNESS_PROGRAM_MISMATCH)
+ # Test that bitflips in the inner pubkey invalidate it.
+ add_spender(spenders, "spendpath/bitflippubkey", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"pubkey_inner": bitflipper(default_pubkey_inner)}, **ERR_WITNESS_PROGRAM_MISMATCH)
+ # Test that empty witnesses are invalid.
+ add_spender(spenders, "spendpath/emptywit", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"witness": []}, **ERR_EMPTY_WITNESS)
+ # Test that adding garbage to the control block invalidates it.
+ add_spender(spenders, "spendpath/padlongcontrol", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_controlblock(ctx) + random_bytes(random.randrange(1, 32))}, **ERR_CONTROLBLOCK_SIZE)
+ # Test that truncating the control block invalidates it.
+ add_spender(spenders, "spendpath/trunclongcontrol", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_merklebranch(ctx)[0:random.randrange(1, 32)]}, **ERR_CONTROLBLOCK_SIZE)
+
+ scripts = [("s", CScript([pubs[0], OP_CHECKSIG]))]
+ tap = taproot_construct(pubs[1], scripts)
+ # Test that adding garbage to the control block invalidates it.
+ add_spender(spenders, "spendpath/padshortcontrol", tap=tap, leaf="s", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_controlblock(ctx) + random_bytes(random.randrange(1, 32))}, **ERR_CONTROLBLOCK_SIZE)
+ # Test that truncating the control block invalidates it.
+ add_spender(spenders, "spendpath/truncshortcontrol", tap=tap, leaf="s", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_merklebranch(ctx)[0:random.randrange(1, 32)]}, **ERR_CONTROLBLOCK_SIZE)
+ # Test that truncating the control block to 1 byte ("-1 Merkle length") invalidates it
+ add_spender(spenders, "spendpath/trunc1shortcontrol", tap=tap, leaf="s", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_merklebranch(ctx)[0:1]}, **ERR_CONTROLBLOCK_SIZE)
+
+ # == Test BIP342 edge cases ==
+
+ csa_low_val = random.randrange(0, 17) # Within range for OP_n
+ csa_low_result = csa_low_val + 1
+
+ csa_high_val = random.randrange(17, 100) if random.getrandbits(1) else random.randrange(-100, -1) # Outside OP_n range
+ csa_high_result = csa_high_val + 1
+
+ OVERSIZE_NUMBER = 2**31
+ assert_equal(len(CScriptNum.encode(CScriptNum(OVERSIZE_NUMBER))), 6)
+ assert_equal(len(CScriptNum.encode(CScriptNum(OVERSIZE_NUMBER-1))), 5)
+
+ big_choices = []
+ big_scriptops = []
+ for i in range(1000):
+ r = random.randrange(len(pubs))
+ big_choices.append(r)
+ big_scriptops += [pubs[r], OP_CHECKSIGVERIFY]
+
+
+ def big_spend_inputs(ctx):
+ """Helper function to construct the script input for t33/t34 below."""
+ # Instead of signing 999 times, precompute signatures for every (key, hashtype) combination
+ sigs = {}
+ for ht in VALID_SIGHASHES_TAPROOT:
+ for k in range(len(pubs)):
+ sigs[(k, ht)] = override(default_sign, hashtype=ht, key=secs[k])(ctx)
+ num = get(ctx, "num")
+ return [sigs[(big_choices[i], random.choice(VALID_SIGHASHES_TAPROOT))] for i in range(num - 1, -1, -1)]
+
+ # Various BIP342 features
+ scripts = [
+ # 0) drop stack element and OP_CHECKSIG
+ ("t0", CScript([OP_DROP, pubs[1], OP_CHECKSIG])),
+ # 1) normal OP_CHECKSIG
+ ("t1", CScript([pubs[1], OP_CHECKSIG])),
+ # 2) normal OP_CHECKSIGVERIFY
+ ("t2", CScript([pubs[1], OP_CHECKSIGVERIFY, OP_1])),
+ # 3) Hypothetical OP_CHECKMULTISIG script that takes a single sig as input
+ ("t3", CScript([OP_0, OP_SWAP, OP_1, pubs[1], OP_1, OP_CHECKMULTISIG])),
+ # 4) Hypothetical OP_CHECKMULTISIGVERIFY script that takes a single sig as input
+ ("t4", CScript([OP_0, OP_SWAP, OP_1, pubs[1], OP_1, OP_CHECKMULTISIGVERIFY, OP_1])),
+ # 5) OP_IF script that needs a true input
+ ("t5", CScript([OP_IF, pubs[1], OP_CHECKSIG, OP_ELSE, OP_RETURN, OP_ENDIF])),
+ # 6) OP_NOTIF script that needs a true input
+ ("t6", CScript([OP_NOTIF, OP_RETURN, OP_ELSE, pubs[1], OP_CHECKSIG, OP_ENDIF])),
+ # 7) OP_CHECKSIG with an empty key
+ ("t7", CScript([OP_0, OP_CHECKSIG])),
+ # 8) OP_CHECKSIGVERIFY with an empty key
+ ("t8", CScript([OP_0, OP_CHECKSIGVERIFY, OP_1])),
+ # 9) normal OP_CHECKSIGADD that also ensures return value is correct
+ ("t9", CScript([csa_low_val, pubs[1], OP_CHECKSIGADD, csa_low_result, OP_EQUAL])),
+ # 10) OP_CHECKSIGADD with empty key
+ ("t10", CScript([csa_low_val, OP_0, OP_CHECKSIGADD, csa_low_result, OP_EQUAL])),
+ # 11) OP_CHECKSIGADD with missing counter stack element
+ ("t11", CScript([pubs[1], OP_CHECKSIGADD, OP_1, OP_EQUAL])),
+ # 12) OP_CHECKSIG that needs invalid signature
+ ("t12", CScript([pubs[1], OP_CHECKSIGVERIFY, pubs[0], OP_CHECKSIG, OP_NOT])),
+ # 13) OP_CHECKSIG with empty key that needs invalid signature
+ ("t13", CScript([pubs[1], OP_CHECKSIGVERIFY, OP_0, OP_CHECKSIG, OP_NOT])),
+ # 14) OP_CHECKSIGADD that needs invalid signature
+ ("t14", CScript([pubs[1], OP_CHECKSIGVERIFY, OP_0, pubs[0], OP_CHECKSIGADD, OP_NOT])),
+ # 15) OP_CHECKSIGADD with empty key that needs invalid signature
+ ("t15", CScript([pubs[1], OP_CHECKSIGVERIFY, OP_0, OP_0, OP_CHECKSIGADD, OP_NOT])),
+ # 16) OP_CHECKSIG with unknown pubkey type
+ ("t16", CScript([OP_1, OP_CHECKSIG])),
+ # 17) OP_CHECKSIGADD with unknown pubkey type
+ ("t17", CScript([OP_0, OP_1, OP_CHECKSIGADD])),
+ # 18) OP_CHECKSIGVERIFY with unknown pubkey type
+ ("t18", CScript([OP_1, OP_CHECKSIGVERIFY, OP_1])),
+ # 19) script longer than 10000 bytes and over 201 non-push opcodes
+ ("t19", CScript([OP_0, OP_0, OP_2DROP] * 10001 + [pubs[1], OP_CHECKSIG])),
+ # 20) OP_CHECKSIGVERIFY with empty key
+ ("t20", CScript([pubs[1], OP_CHECKSIGVERIFY, OP_0, OP_0, OP_CHECKSIGVERIFY, OP_1])),
+ # 21) Script that grows the stack to 1000 elements
+ ("t21", CScript([pubs[1], OP_CHECKSIGVERIFY, OP_1] + [OP_DUP] * 999 + [OP_DROP] * 999)),
+ # 22) Script that grows the stack to 1001 elements
+ ("t22", CScript([pubs[1], OP_CHECKSIGVERIFY, OP_1] + [OP_DUP] * 1000 + [OP_DROP] * 1000)),
+ # 23) Script that expects an input stack of 1000 elements
+ ("t23", CScript([OP_DROP] * 999 + [pubs[1], OP_CHECKSIG])),
+ # 24) Script that expects an input stack of 1001 elements
+ ("t24", CScript([OP_DROP] * 1000 + [pubs[1], OP_CHECKSIG])),
+ # 25) Script that pushes a MAX_SCRIPT_ELEMENT_SIZE-bytes element
+ ("t25", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE), OP_DROP, pubs[1], OP_CHECKSIG])),
+ # 26) Script that pushes a (MAX_SCRIPT_ELEMENT_SIZE+1)-bytes element
+ ("t26", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, pubs[1], OP_CHECKSIG])),
+ # 27) CHECKSIGADD that must fail because numeric argument number is >4 bytes
+ ("t27", CScript([CScriptNum(OVERSIZE_NUMBER), pubs[1], OP_CHECKSIGADD])),
+ # 28) Pushes random CScriptNum value, checks OP_CHECKSIGADD result
+ ("t28", CScript([csa_high_val, pubs[1], OP_CHECKSIGADD, csa_high_result, OP_EQUAL])),
+ # 29) CHECKSIGADD that succeeds with proper sig because numeric argument number is <=4 bytes
+ ("t29", CScript([CScriptNum(OVERSIZE_NUMBER-1), pubs[1], OP_CHECKSIGADD])),
+ # 30) Variant of t1 with "normal" 33-byte pubkey
+ ("t30", CScript([b'\x03' + pubs[1], OP_CHECKSIG])),
+ # 31) Variant of t2 with "normal" 33-byte pubkey
+ ("t31", CScript([b'\x02' + pubs[1], OP_CHECKSIGVERIFY, OP_1])),
+ # 32) Variant of t28 with "normal" 33-byte pubkey
+ ("t32", CScript([csa_high_val, b'\x03' + pubs[1], OP_CHECKSIGADD, csa_high_result, OP_EQUAL])),
+ # 33) 999-of-999 multisig
+ ("t33", CScript(big_scriptops[:1998] + [OP_1])),
+ # 34) 1000-of-1000 multisig
+ ("t34", CScript(big_scriptops[:2000] + [OP_1])),
+ # 35) Variant of t9 that uses a non-minimally encoded input arg
+ ("t35", CScript([bytes([csa_low_val]), pubs[1], OP_CHECKSIGADD, csa_low_result, OP_EQUAL])),
+ # 36) Empty script
+ ("t36", CScript([])),
+ ]
+ # Add many dummies to test huge trees
+ for j in range(100000):
+ scripts.append((None, CScript([OP_RETURN, random.randrange(100000)])))
+ random.shuffle(scripts)
+ tap = taproot_construct(pubs[0], scripts)
+ common = {
+ "hashtype": hashtype,
+ "key": secs[1],
+ "tap": tap,
+ }
+ # Test that MAX_SCRIPT_ELEMENT_SIZE byte stack element inputs are valid, but not one more (and 80 bytes is standard but 81 is not).
+ add_spender(spenders, "tapscript/inputmaxlimit", leaf="t0", **common, standard=False, inputs=[getter("sign"), random_bytes(MAX_SCRIPT_ELEMENT_SIZE)], failure={"inputs": [getter("sign"), random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1)]}, **ERR_PUSH_LIMIT)
+ add_spender(spenders, "tapscript/input80limit", leaf="t0", **common, inputs=[getter("sign"), random_bytes(80)])
+ add_spender(spenders, "tapscript/input81limit", leaf="t0", **common, standard=False, inputs=[getter("sign"), random_bytes(81)])
+ # Test that OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY cause failure, but OP_CHECKSIG and OP_CHECKSIGVERIFY work.
+ add_spender(spenders, "tapscript/disabled_checkmultisig", leaf="t1", **common, **SINGLE_SIG, failure={"leaf": "t3"}, **ERR_TAPSCRIPT_CHECKMULTISIG)
+ add_spender(spenders, "tapscript/disabled_checkmultisigverify", leaf="t2", **common, **SINGLE_SIG, failure={"leaf": "t4"}, **ERR_TAPSCRIPT_CHECKMULTISIG)
+ # Test that OP_IF and OP_NOTIF do not accept non-0x01 as truth value (the MINIMALIF rule is consensus in Tapscript)
+ add_spender(spenders, "tapscript/minimalif", leaf="t5", **common, inputs=[getter("sign"), b'\x01'], failure={"inputs": [getter("sign"), b'\x02']}, **ERR_MINIMALIF)
+ add_spender(spenders, "tapscript/minimalnotif", leaf="t6", **common, inputs=[getter("sign"), b'\x01'], failure={"inputs": [getter("sign"), b'\x03']}, **ERR_MINIMALIF)
+ add_spender(spenders, "tapscript/minimalif", leaf="t5", **common, inputs=[getter("sign"), b'\x01'], failure={"inputs": [getter("sign"), b'\x0001']}, **ERR_MINIMALIF)
+ add_spender(spenders, "tapscript/minimalnotif", leaf="t6", **common, inputs=[getter("sign"), b'\x01'], failure={"inputs": [getter("sign"), b'\x0100']}, **ERR_MINIMALIF)
+ # Test that 1-byte public keys (which are unknown) are acceptable but nonstandard with unrelated signatures, but 0-byte public keys are not valid.
+ add_spender(spenders, "tapscript/unkpk/checksig", leaf="t16", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t7"}, **ERR_UNKNOWN_PUBKEY)
+ add_spender(spenders, "tapscript/unkpk/checksigadd", leaf="t17", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t10"}, **ERR_UNKNOWN_PUBKEY)
+ add_spender(spenders, "tapscript/unkpk/checksigverify", leaf="t18", standard=False, **common, **SINGLE_SIG, failure={"leaf": "t8"}, **ERR_UNKNOWN_PUBKEY)
+ # Test that 33-byte public keys (which are unknown) are acceptable but nonstandard with valid signatures, but normal pubkeys are not valid in that case.
+ add_spender(spenders, "tapscript/oldpk/checksig", leaf="t30", standard=False, **common, **SINGLE_SIG, sighash=bitflipper(default_sighash), failure={"leaf": "t1"}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "tapscript/oldpk/checksigadd", leaf="t31", standard=False, **common, **SINGLE_SIG, sighash=bitflipper(default_sighash), failure={"leaf": "t2"}, **ERR_SIG_SCHNORR)
+ add_spender(spenders, "tapscript/oldpk/checksigverify", leaf="t32", standard=False, **common, **SINGLE_SIG, sighash=bitflipper(default_sighash), failure={"leaf": "t28"}, **ERR_SIG_SCHNORR)
+ # Test that 0-byte public keys are not acceptable.
+ add_spender(spenders, "tapscript/emptypk/checksig", leaf="t1", **SINGLE_SIG, **common, failure={"leaf": "t7"}, **ERR_UNKNOWN_PUBKEY)
+ add_spender(spenders, "tapscript/emptypk/checksigverify", leaf="t2", **SINGLE_SIG, **common, failure={"leaf": "t8"}, **ERR_UNKNOWN_PUBKEY)
+ add_spender(spenders, "tapscript/emptypk/checksigadd", leaf="t9", **SINGLE_SIG, **common, failure={"leaf": "t10"}, **ERR_UNKNOWN_PUBKEY)
+ add_spender(spenders, "tapscript/emptypk/checksigadd", leaf="t35", standard=False, **SINGLE_SIG, **common, failure={"leaf": "t10"}, **ERR_UNKNOWN_PUBKEY)
+ # Test that OP_CHECKSIGADD results are as expected
+ add_spender(spenders, "tapscript/checksigaddresults", leaf="t28", **SINGLE_SIG, **common, failure={"leaf": "t27"}, err_msg="unknown error")
+ add_spender(spenders, "tapscript/checksigaddoversize", leaf="t29", **SINGLE_SIG, **common, failure={"leaf": "t27"}, err_msg="unknown error")
+ # Test that OP_CHECKSIGADD requires 3 stack elements.
+ add_spender(spenders, "tapscript/checksigadd3args", leaf="t9", **SINGLE_SIG, **common, failure={"leaf": "t11"}, **ERR_STACK_EMPTY)
+ # Test that empty signatures do not cause script failure in OP_CHECKSIG and OP_CHECKSIGADD (but do fail with empty pubkey, and do fail OP_CHECKSIGVERIFY)
+ add_spender(spenders, "tapscript/emptysigs/checksig", leaf="t12", **common, inputs=[b'', getter("sign")], failure={"leaf": "t13"}, **ERR_UNKNOWN_PUBKEY)
+ add_spender(spenders, "tapscript/emptysigs/nochecksigverify", leaf="t12", **common, inputs=[b'', getter("sign")], failure={"leaf": "t20"}, **ERR_UNKNOWN_PUBKEY)
+ add_spender(spenders, "tapscript/emptysigs/checksigadd", leaf="t14", **common, inputs=[b'', getter("sign")], failure={"leaf": "t15"}, **ERR_UNKNOWN_PUBKEY)
+ # Test that scripts over 10000 bytes (and over 201 non-push ops) are acceptable.
+ add_spender(spenders, "tapscript/no10000limit", leaf="t19", **SINGLE_SIG, **common)
+ # Test that a stack size of 1000 elements is permitted, but 1001 isn't.
+ add_spender(spenders, "tapscript/1000stack", leaf="t21", **SINGLE_SIG, **common, failure={"leaf": "t22"}, **ERR_STACK_SIZE)
+ # Test that an input stack size of 1000 elements is permitted, but 1001 isn't.
+ add_spender(spenders, "tapscript/1000inputs", leaf="t23", **common, inputs=[getter("sign")] + [b'' for _ in range(999)], failure={"leaf": "t24", "inputs": [getter("sign")] + [b'' for _ in range(1000)]}, **ERR_STACK_SIZE)
+ # Test that pushing a MAX_SCRIPT_ELEMENT_SIZE byte stack element is valid, but one longer is not.
+ add_spender(spenders, "tapscript/pushmaxlimit", leaf="t25", **common, **SINGLE_SIG, failure={"leaf": "t26"}, **ERR_PUSH_LIMIT)
+ # Test that 999-of-999 multisig works (but 1000-of-1000 triggers stack size limits)
+ add_spender(spenders, "tapscript/bigmulti", leaf="t33", **common, inputs=big_spend_inputs, num=999, failure={"leaf": "t34", "num": 1000}, **ERR_STACK_SIZE)
+ # Test that the CLEANSTACK rule is consensus critical in tapscript
+ add_spender(spenders, "tapscript/cleanstack", leaf="t36", tap=tap, inputs=[b'\x01'], failure={"inputs": [b'\x01', b'\x01']}, **ERR_CLEANSTACK)
+
+ # == Test for sigops ratio limit ==
+
+ # Given a number n, and a public key pk, functions that produce a (CScript, sigops). Each script takes as
+ # input a valid signature with the passed pk followed by a dummy push of bytes that are to be dropped, and
+ # will execute sigops signature checks.
+ SIGOPS_RATIO_SCRIPTS = [
+ # n OP_CHECKSIGVERFIYs and 1 OP_CHECKSIG.
+ lambda n, pk: (CScript([OP_DROP, pk] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_CHECKSIG]), n + 1),
+ # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGVERIFY.
+ lambda n, pk: (CScript([OP_DROP, pk, OP_0, OP_IF, OP_2DUP, OP_CHECKSIGVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_2, OP_SWAP, OP_CHECKSIGADD, OP_3, OP_EQUAL]), n + 1),
+ # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIG.
+ lambda n, pk: (CScript([random_bytes(220), OP_2DROP, pk, OP_1, OP_NOTIF, OP_2DUP, OP_CHECKSIG, OP_VERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_4, OP_SWAP, OP_CHECKSIGADD, OP_5, OP_EQUAL]), n + 1),
+ # n OP_CHECKSIGVERFIYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGADD.
+ lambda n, pk: (CScript([OP_DROP, pk, OP_1, OP_IF, OP_ELSE, OP_2DUP, OP_6, OP_SWAP, OP_CHECKSIGADD, OP_7, OP_EQUALVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_8, OP_SWAP, OP_CHECKSIGADD, OP_9, OP_EQUAL]), n + 1),
+ # n+1 OP_CHECKSIGs, but also one OP_CHECKSIG with an empty signature.
+ lambda n, pk: (CScript([OP_DROP, OP_0, pk, OP_CHECKSIG, OP_NOT, OP_VERIFY, pk] + [OP_2DUP, OP_CHECKSIG, OP_VERIFY] * n + [OP_CHECKSIG]), n + 1),
+ # n OP_CHECKSIGADDs and 1 OP_CHECKSIG, but also an OP_CHECKSIGADD with an empty signature.
+ lambda n, pk: (CScript([OP_DROP, OP_0, OP_10, pk, OP_CHECKSIGADD, OP_10, OP_EQUALVERIFY, pk] + [OP_2DUP, OP_16, OP_SWAP, OP_CHECKSIGADD, b'\x11', OP_EQUALVERIFY] * n + [OP_CHECKSIG]), n + 1),
+ ]
+ for annex in [None, bytes([ANNEX_TAG]) + random_bytes(random.randrange(1000))]:
+ for hashtype in [SIGHASH_DEFAULT, SIGHASH_ALL]:
+ for pubkey in [pubs[1], random_bytes(random.choice([x for x in range(2, 81) if x != 32]))]:
+ for fn_num, fn in enumerate(SIGOPS_RATIO_SCRIPTS):
+ merkledepth = random.randrange(129)
+
+
+ def predict_sigops_ratio(n, dummy_size):
+ """Predict whether spending fn(n, pubkey) with dummy_size will pass the ratio test."""
+ script, sigops = fn(n, pubkey)
+ # Predict the size of the witness for a given choice of n
+ stacklen_size = 1
+ sig_size = 64 + (hashtype != SIGHASH_DEFAULT)
+ siglen_size = 1
+ dummylen_size = 1 + 2 * (dummy_size >= 253)
+ script_size = len(script)
+ scriptlen_size = 1 + 2 * (script_size >= 253)
+ control_size = 33 + 32 * merkledepth
+ controllen_size = 1 + 2 * (control_size >= 253)
+ annex_size = 0 if annex is None else len(annex)
+ annexlen_size = 0 if annex is None else 1 + 2 * (annex_size >= 253)
+ witsize = stacklen_size + sig_size + siglen_size + dummy_size + dummylen_size + script_size + scriptlen_size + control_size + controllen_size + annex_size + annexlen_size
+ # sigops ratio test
+ return witsize + 50 >= 50 * sigops
+ # Make sure n is high enough that with empty dummy, the script is not valid
+ n = 0
+ while predict_sigops_ratio(n, 0):
+ n += 1
+ # But allow picking a bit higher still
+ n += random.randrange(5)
+ # Now pick dummy size *just* large enough that the overall construction passes
+ dummylen = 0
+ while not predict_sigops_ratio(n, dummylen):
+ dummylen += 1
+ scripts = [("s", fn(n, pubkey)[0])]
+ for _ in range(merkledepth):
+ scripts = [scripts, random.choice(PARTNER_MERKLE_FN)]
+ tap = taproot_construct(pubs[0], scripts)
+ standard = annex is None and dummylen <= 80 and len(pubkey) == 32
+ add_spender(spenders, "tapscript/sigopsratio_%i" % fn_num, tap=tap, leaf="s", annex=annex, hashtype=hashtype, key=secs[1], inputs=[getter("sign"), random_bytes(dummylen)], standard=standard, failure={"inputs": [getter("sign"), random_bytes(dummylen - 1)]}, **ERR_SIGOPS_RATIO)
+
+ # Future leaf versions
+ for leafver in range(0, 0x100, 2):
+ if leafver == LEAF_VERSION_TAPSCRIPT or leafver == ANNEX_TAG:
+ # Skip the defined LEAF_VERSION_TAPSCRIPT, and the ANNEX_TAG which is not usable as leaf version
+ continue
+ scripts = [
+ ("bare_c0", CScript([OP_NOP])),
+ ("bare_unkver", CScript([OP_NOP]), leafver),
+ ("return_c0", CScript([OP_RETURN])),
+ ("return_unkver", CScript([OP_RETURN]), leafver),
+ ("undecodable_c0", CScript([OP_PUSHDATA1])),
+ ("undecodable_unkver", CScript([OP_PUSHDATA1]), leafver),
+ ("bigpush_c0", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP])),
+ ("bigpush_unkver", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP]), leafver),
+ ("1001push_c0", CScript([OP_0] * 1001)),
+ ("1001push_unkver", CScript([OP_0] * 1001), leafver),
+ ]
+ random.shuffle(scripts)
+ tap = taproot_construct(pubs[0], scripts)
+ add_spender(spenders, "unkver/bare", standard=False, tap=tap, leaf="bare_unkver", failure={"leaf": "bare_c0"}, **ERR_CLEANSTACK)
+ add_spender(spenders, "unkver/return", standard=False, tap=tap, leaf="return_unkver", failure={"leaf": "return_c0"}, **ERR_OP_RETURN)
+ add_spender(spenders, "unkver/undecodable", standard=False, tap=tap, leaf="undecodable_unkver", failure={"leaf": "undecodable_c0"}, **ERR_UNDECODABLE)
+ add_spender(spenders, "unkver/bigpush", standard=False, tap=tap, leaf="bigpush_unkver", failure={"leaf": "bigpush_c0"}, **ERR_PUSH_LIMIT)
+ add_spender(spenders, "unkver/1001push", standard=False, tap=tap, leaf="1001push_unkver", failure={"leaf": "1001push_c0"}, **ERR_STACK_SIZE)
+ add_spender(spenders, "unkver/1001inputs", standard=False, tap=tap, leaf="bare_unkver", inputs=[b'']*1001, failure={"leaf": "bare_c0"}, **ERR_STACK_SIZE)
+
+ # OP_SUCCESSx tests.
+ hashtype = lambda _: random.choice(VALID_SIGHASHES_TAPROOT)
+ for opval in range(76, 0x100):
+ opcode = CScriptOp(opval)
+ if not is_op_success(opcode):
+ continue
+ scripts = [
+ ("bare_success", CScript([opcode])),
+ ("bare_nop", CScript([OP_NOP])),
+ ("unexecif_success", CScript([OP_0, OP_IF, opcode, OP_ENDIF])),
+ ("unexecif_nop", CScript([OP_0, OP_IF, OP_NOP, OP_ENDIF])),
+ ("return_success", CScript([OP_RETURN, opcode])),
+ ("return_nop", CScript([OP_RETURN, OP_NOP])),
+ ("undecodable_success", CScript([opcode, OP_PUSHDATA1])),
+ ("undecodable_nop", CScript([OP_NOP, OP_PUSHDATA1])),
+ ("undecodable_bypassed_success", CScript([OP_PUSHDATA1, OP_2, opcode])),
+ ("bigpush_success", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, opcode])),
+ ("bigpush_nop", CScript([random_bytes(MAX_SCRIPT_ELEMENT_SIZE+1), OP_DROP, OP_NOP])),
+ ("1001push_success", CScript([OP_0] * 1001 + [opcode])),
+ ("1001push_nop", CScript([OP_0] * 1001 + [OP_NOP])),
+ ]
+ random.shuffle(scripts)
+ tap = taproot_construct(pubs[0], scripts)
+ add_spender(spenders, "opsuccess/bare", standard=False, tap=tap, leaf="bare_success", failure={"leaf": "bare_nop"}, **ERR_CLEANSTACK)
+ add_spender(spenders, "opsuccess/unexecif", standard=False, tap=tap, leaf="unexecif_success", failure={"leaf": "unexecif_nop"}, **ERR_CLEANSTACK)
+ add_spender(spenders, "opsuccess/return", standard=False, tap=tap, leaf="return_success", failure={"leaf": "return_nop"}, **ERR_OP_RETURN)
+ add_spender(spenders, "opsuccess/undecodable", standard=False, tap=tap, leaf="undecodable_success", failure={"leaf": "undecodable_nop"}, **ERR_UNDECODABLE)
+ add_spender(spenders, "opsuccess/undecodable_bypass", standard=False, tap=tap, leaf="undecodable_success", failure={"leaf": "undecodable_bypassed_success"}, **ERR_UNDECODABLE)
+ add_spender(spenders, "opsuccess/bigpush", standard=False, tap=tap, leaf="bigpush_success", failure={"leaf": "bigpush_nop"}, **ERR_PUSH_LIMIT)
+ add_spender(spenders, "opsuccess/1001push", standard=False, tap=tap, leaf="1001push_success", failure={"leaf": "1001push_nop"}, **ERR_STACK_SIZE)
+ add_spender(spenders, "opsuccess/1001inputs", standard=False, tap=tap, leaf="bare_success", inputs=[b'']*1001, failure={"leaf": "bare_nop"}, **ERR_STACK_SIZE)
+
+ # Non-OP_SUCCESSx (verify that those aren't accidentally treated as OP_SUCCESSx)
+ for opval in range(0, 0x100):
+ opcode = CScriptOp(opval)
+ if is_op_success(opcode):
+ continue
+ scripts = [
+ ("normal", CScript([OP_RETURN, opcode] + [OP_NOP] * 75)),
+ ("op_success", CScript([OP_RETURN, CScriptOp(0x50)]))
+ ]
+ tap = taproot_construct(pubs[0], scripts)
+ add_spender(spenders, "alwaysvalid/notsuccessx", tap=tap, leaf="op_success", inputs=[], standard=False, failure={"leaf": "normal"}) # err_msg differs based on opcode
+
+ # == Legacy tests ==
+
+ # Also add a few legacy spends into the mix, so that transactions which combine taproot and pre-taproot spends get tested too.
+ for compressed in [False, True]:
+ eckey1 = ECKey()
+ eckey1.set(generate_privkey(), compressed)
+ pubkey1 = eckey1.get_pubkey().get_bytes()
+ eckey2 = ECKey()
+ eckey2.set(generate_privkey(), compressed)
+ for p2sh in [False, True]:
+ for witv0 in [False, True]:
+ for hashtype in VALID_SIGHASHES_ECDSA + [random.randrange(0x04, 0x80), random.randrange(0x84, 0x100)]:
+ standard = (hashtype in VALID_SIGHASHES_ECDSA) and (compressed or not witv0)
+ add_spender(spenders, "legacy/pk-wrongkey", hashtype=hashtype, p2sh=p2sh, witv0=witv0, standard=standard, script=CScript([pubkey1, OP_CHECKSIG]), **SINGLE_SIG, key=eckey1, failure={"key": eckey2}, sigops_weight=4-3*witv0, **ERR_NO_SUCCESS)
+ add_spender(spenders, "legacy/pkh-sighashflip", hashtype=hashtype, p2sh=p2sh, witv0=witv0, standard=standard, pkh=pubkey1, key=eckey1, **SIGHASH_BITFLIP, sigops_weight=4-3*witv0, **ERR_NO_SUCCESS)
+
+ # Verify that OP_CHECKSIGADD wasn't accidentally added to pre-taproot validation logic.
+ for p2sh in [False, True]:
+ for witv0 in [False, True]:
+ for hashtype in VALID_SIGHASHES_ECDSA + [random.randrange(0x04, 0x80), random.randrange(0x84, 0x100)]:
+ standard = hashtype in VALID_SIGHASHES_ECDSA and (p2sh or witv0)
+ add_spender(spenders, "compat/nocsa", hashtype=hashtype, p2sh=p2sh, witv0=witv0, standard=standard, script=CScript([OP_IF, OP_11, pubkey1, OP_CHECKSIGADD, OP_12, OP_EQUAL, OP_ELSE, pubkey1, OP_CHECKSIG, OP_ENDIF]), key=eckey1, sigops_weight=4-3*witv0, inputs=[getter("sign"), b''], failure={"inputs": [getter("sign"), b'\x01']}, **ERR_UNDECODABLE)
+
+ return spenders
+
+def spenders_taproot_inactive():
+ """Spenders for testing that pre-activation Taproot rules don't apply."""
+
+ spenders = []
+
+ sec = generate_privkey()
+ pub, _ = compute_xonly_pubkey(sec)
+ scripts = [
+ ("pk", CScript([pub, OP_CHECKSIG])),
+ ("future_leaf", CScript([pub, OP_CHECKSIG]), 0xc2),
+ ("op_success", CScript([pub, OP_CHECKSIG, OP_0, OP_IF, CScriptOp(0x50), OP_ENDIF])),
+ ]
+ tap = taproot_construct(pub, scripts)
+
+ # Test that keypath spending is valid & non-standard, regardless of validity.
+ add_spender(spenders, "inactive/keypath_valid", key=sec, tap=tap, standard=False)
+ add_spender(spenders, "inactive/keypath_invalidsig", key=sec, tap=tap, standard=False, sighash=bitflipper(default_sighash))
+ add_spender(spenders, "inactive/keypath_empty", key=sec, tap=tap, standard=False, witness=[])
+
+ # Same for scriptpath spending (and features like annex, leaf versions, or OP_SUCCESS don't change this)
+ add_spender(spenders, "inactive/scriptpath_valid", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")])
+ add_spender(spenders, "inactive/scriptpath_invalidsig", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash))
+ add_spender(spenders, "inactive/scriptpath_invalidcb", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], controlblock=bitflipper(default_controlblock))
+ add_spender(spenders, "inactive/scriptpath_valid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")])
+ add_spender(spenders, "inactive/scriptpath_invalid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash))
+ add_spender(spenders, "inactive/scriptpath_valid_opsuccess", key=sec, tap=tap, leaf="op_success", standard=False, inputs=[getter("sign")])
+ add_spender(spenders, "inactive/scriptpath_valid_opsuccess", key=sec, tap=tap, leaf="op_success", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash))
+
+ return spenders
+
+# Consensus validation flags to use in dumps for tests with "legacy/" or "inactive/" prefix.
+LEGACY_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY"
+# Consensus validation flags to use in dumps for all other tests.
+TAPROOT_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT"
+
+def dump_json_test(tx, input_utxos, idx, success, failure):
+ spender = input_utxos[idx].spender
+ # Determine flags to dump
+ flags = LEGACY_FLAGS if spender.comment.startswith("legacy/") or spender.comment.startswith("inactive/") else TAPROOT_FLAGS
+
+ fields = [
+ ("tx", tx.serialize().hex()),
+ ("prevouts", [x.output.serialize().hex() for x in input_utxos]),
+ ("index", idx),
+ ("flags", flags),
+ ("comment", spender.comment)
+ ]
+
+ # The "final" field indicates that a spend should be always valid, even with more validation flags enabled
+ # than the listed ones. Use standardness as a proxy for this (which gives a conservative underestimate).
+ if spender.is_standard:
+ fields.append(("final", True))
+
+ def dump_witness(wit):
+ return OrderedDict([("scriptSig", wit[0].hex()), ("witness", [x.hex() for x in wit[1]])])
+ if success is not None:
+ fields.append(("success", dump_witness(success)))
+ if failure is not None:
+ fields.append(("failure", dump_witness(failure)))
+
+ # Write the dump to $TEST_DUMP_DIR/x/xyz... where x,y,z,... are the SHA1 sum of the dump (which makes the
+ # file naming scheme compatible with fuzzing infrastructure).
+ dump = json.dumps(OrderedDict(fields)) + ",\n"
+ sha1 = hashlib.sha1(dump.encode("utf-8")).hexdigest()
+ dirname = os.environ.get("TEST_DUMP_DIR", ".") + ("/%s" % sha1[0])
+ os.makedirs(dirname, exist_ok=True)
+ with open(dirname + ("/%s" % sha1), 'w', encoding="utf8") as f:
+ f.write(dump)
+
+# Data type to keep track of UTXOs, where they were created, and how to spend them.
+UTXOData = namedtuple('UTXOData', 'outpoint,output,spender')
+
+class TaprootTest(BitcoinTestFramework):
+ def add_options(self, parser):
+ parser.add_argument("--dumptests", dest="dump_tests", default=False, action="store_true",
+ help="Dump generated test cases to directory set by TEST_DUMP_DIR environment variable")
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def set_test_params(self):
+ self.num_nodes = 2
+ self.setup_clean_chain = True
+ # Node 0 has Taproot inactive, Node 1 active.
+ self.extra_args = [["-par=1", "-vbparams=taproot:1:1"], ["-par=1"]]
+
+ def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False):
+
+ # Deplete block of any non-tapscript sigops using a single additional 0-value coinbase output.
+ # It is not impossible to fit enough tapscript sigops to hit the old 80k limit without
+ # busting txin-level limits. We simply have to account for the p2pk outputs in all
+ # transactions.
+ extra_output_script = CScript([OP_CHECKSIG]*((MAX_BLOCK_SIGOPS_WEIGHT - sigops_weight) // WITNESS_SCALE_FACTOR))
+
+ block = create_block(self.tip, create_coinbase(self.lastblockheight + 1, pubkey=cb_pubkey, extra_output_script=extra_output_script, fees=fees), self.lastblocktime + 1)
+ block.nVersion = 4
+ for tx in txs:
+ tx.rehash()
+ block.vtx.append(tx)
+ block.hashMerkleRoot = block.calc_merkle_root()
+ witness and add_witness_commitment(block)
+ block.rehash()
+ block.solve()
+ block_response = node.submitblock(block.serialize().hex())
+ if err_msg is not None:
+ assert block_response is not None and err_msg in block_response, "Missing error message '%s' from block response '%s': %s" % (err_msg, "(None)" if block_response is None else block_response, msg)
+ if (accept):
+ assert node.getbestblockhash() == block.hash, "Failed to accept: %s (response: %s)" % (msg, block_response)
+ self.tip = block.sha256
+ self.lastblockhash = block.hash
+ self.lastblocktime += 1
+ self.lastblockheight += 1
+ else:
+ assert node.getbestblockhash() == self.lastblockhash, "Failed to reject: " + msg
+
+ def test_spenders(self, node, spenders, input_counts):
+ """Run randomized tests with a number of "spenders".
+
+ Steps:
+ 1) Generate an appropriate UTXO for each spender to test spend conditions
+ 2) Generate 100 random addresses of all wallet types: pkh/sh_wpkh/wpkh
+ 3) Select random number of inputs from (1)
+ 4) Select random number of addresses from (2) as outputs
+
+ Each spender embodies a test; in a large randomized test, it is verified
+ that toggling the valid argument to each lambda toggles the validity of
+ the transaction. This is accomplished by constructing transactions consisting
+ of all valid inputs, except one invalid one.
+ """
+
+ # Construct a bunch of sPKs that send coins back to the host wallet
+ self.log.info("- Constructing addresses for returning coins")
+ host_spks = []
+ host_pubkeys = []
+ for i in range(16):
+ addr = node.getnewaddress(address_type=random.choice(["legacy", "p2sh-segwit", "bech32"]))
+ info = node.getaddressinfo(addr)
+ spk = bytes.fromhex(info['scriptPubKey'])
+ host_spks.append(spk)
+ host_pubkeys.append(bytes.fromhex(info['pubkey']))
+
+ # Initialize variables used by block_submit().
+ self.lastblockhash = node.getbestblockhash()
+ self.tip = int(self.lastblockhash, 16)
+ block = node.getblock(self.lastblockhash)
+ self.lastblockheight = block['height']
+ self.lastblocktime = block['time']
+
+ # Create transactions spending up to 50 of the wallet's inputs, with one output for each spender, and
+ # one change output at the end. The transaction is constructed on the Python side to enable
+ # having multiple outputs to the same address and outputs with no assigned address. The wallet
+ # is then asked to sign it through signrawtransactionwithwallet, and then added to a block on the
+ # Python side (to bypass standardness rules).
+ self.log.info("- Creating test UTXOs...")
+ random.shuffle(spenders)
+ normal_utxos = []
+ mismatching_utxos = [] # UTXOs with input that requires mismatching output position
+ done = 0
+ while done < len(spenders):
+ # Compute how many UTXOs to create with this transaction
+ count_this_tx = min(len(spenders) - done, (len(spenders) + 4) // 5, 10000)
+
+ fund_tx = CTransaction()
+ # Add the 50 highest-value inputs
+ unspents = node.listunspent()
+ random.shuffle(unspents)
+ unspents.sort(key=lambda x: int(x["amount"] * 100000000), reverse=True)
+ if len(unspents) > 50:
+ unspents = unspents[:50]
+ random.shuffle(unspents)
+ balance = 0
+ for unspent in unspents:
+ balance += int(unspent["amount"] * 100000000)
+ txid = int(unspent["txid"], 16)
+ fund_tx.vin.append(CTxIn(COutPoint(txid, int(unspent["vout"])), CScript()))
+ # Add outputs
+ cur_progress = done / len(spenders)
+ next_progress = (done + count_this_tx) / len(spenders)
+ change_goal = (1.0 - 0.6 * next_progress) / (1.0 - 0.6 * cur_progress) * balance
+ self.log.debug("Create %i UTXOs in a transaction spending %i inputs worth %.8f (sending ~%.8f to change)" % (count_this_tx, len(unspents), balance * 0.00000001, change_goal * 0.00000001))
+ for i in range(count_this_tx):
+ avg = (balance - change_goal) / (count_this_tx - i)
+ amount = int(random.randrange(int(avg*0.85 + 0.5), int(avg*1.15 + 0.5)) + 0.5)
+ balance -= amount
+ fund_tx.vout.append(CTxOut(amount, spenders[done + i].script))
+ # Add change
+ fund_tx.vout.append(CTxOut(balance - 10000, random.choice(host_spks)))
+ # Ask the wallet to sign
+ ss = BytesIO(bytes.fromhex(node.signrawtransactionwithwallet(ToHex(fund_tx))["hex"]))
+ fund_tx.deserialize(ss)
+ # Construct UTXOData entries
+ fund_tx.rehash()
+ for i in range(count_this_tx):
+ utxodata = UTXOData(outpoint=COutPoint(fund_tx.sha256, i), output=fund_tx.vout[i], spender=spenders[done])
+ if utxodata.spender.need_vin_vout_mismatch:
+ mismatching_utxos.append(utxodata)
+ else:
+ normal_utxos.append(utxodata)
+ done += 1
+ # Mine into a block
+ self.block_submit(node, [fund_tx], "Funding tx", None, random.choice(host_pubkeys), 10000, MAX_BLOCK_SIGOPS_WEIGHT, True, True)
+
+ # Consume groups of choice(input_coins) from utxos in a tx, testing the spenders.
+ self.log.info("- Running %i spending tests" % done)
+ random.shuffle(normal_utxos)
+ random.shuffle(mismatching_utxos)
+ assert done == len(normal_utxos) + len(mismatching_utxos)
+
+ left = done
+ while left:
+ # Construct CTransaction with random nVersion, 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
+ if random.choice([True, False]):
+ tx.nLockTime = random.randrange(LOCKTIME_THRESHOLD, self.lastblocktime - 7200) # all absolute locktimes in the past
+ else:
+ tx.nLockTime = random.randrange(self.lastblockheight + 1) # all block heights in the past
+
+ # Decide how many UTXOs to test with.
+ acceptable = [n for n in input_counts if n <= left and (left - n > max(input_counts) or (left - n) in [0] + input_counts)]
+ num_inputs = random.choice(acceptable)
+
+ # If we have UTXOs that require mismatching inputs/outputs left, include exactly one of those
+ # unless there is only one normal UTXO left (as tests with mismatching UTXOs require at least one
+ # normal UTXO to go in the first position), and we don't want to run out of normal UTXOs.
+ input_utxos = []
+ while len(mismatching_utxos) and (len(input_utxos) == 0 or len(normal_utxos) == 1):
+ input_utxos.append(mismatching_utxos.pop())
+ left -= 1
+
+ # Top up until we hit num_inputs (but include at least one normal UTXO always).
+ for _ in range(max(1, num_inputs - len(input_utxos))):
+ input_utxos.append(normal_utxos.pop())
+ left -= 1
+
+ # The first input cannot require a mismatching output (as there is at least one output).
+ while True:
+ random.shuffle(input_utxos)
+ if not input_utxos[0].spender.need_vin_vout_mismatch:
+ break
+ first_mismatch_input = None
+ for i in range(len(input_utxos)):
+ if input_utxos[i].spender.need_vin_vout_mismatch:
+ first_mismatch_input = i
+ assert first_mismatch_input is None or first_mismatch_input > 0
+
+ # Decide fee, and add CTxIns to tx.
+ amount = sum(utxo.output.nValue for utxo in input_utxos)
+ fee = min(random.randrange(MIN_FEE * 2, MIN_FEE * 4), amount - DUST_LIMIT) # 10000-20000 sat fee
+ in_value = amount - fee
+ tx.vin = [CTxIn(outpoint=utxo.outpoint, nSequence=random.randint(min_sequence, 0xffffffff)) for utxo in input_utxos]
+ tx.wit.vtxinwit = [CTxInWitness() for _ in range(len(input_utxos))]
+ sigops_weight = sum(utxo.spender.sigops_weight for utxo in input_utxos)
+ self.log.debug("Test: %s" % (", ".join(utxo.spender.comment for utxo in input_utxos)))
+
+ # Add 1 to 4 random outputs (but constrained by inputs that require mismatching outputs)
+ num_outputs = random.choice(range(1, 1 + min(4, 4 if first_mismatch_input is None else first_mismatch_input)))
+ assert in_value >= 0 and fee - num_outputs * DUST_LIMIT >= MIN_FEE
+ for i in range(num_outputs):
+ tx.vout.append(CTxOut())
+ if in_value <= DUST_LIMIT:
+ tx.vout[-1].nValue = DUST_LIMIT
+ elif i < num_outputs - 1:
+ tx.vout[-1].nValue = in_value
+ else:
+ tx.vout[-1].nValue = random.randint(DUST_LIMIT, in_value)
+ in_value -= tx.vout[-1].nValue
+ tx.vout[-1].scriptPubKey = random.choice(host_spks)
+ sigops_weight += CScript(tx.vout[-1].scriptPubKey).GetSigOpCount(False) * WITNESS_SCALE_FACTOR
+ fee += in_value
+ assert fee >= 0
+
+ # Select coinbase pubkey
+ cb_pubkey = random.choice(host_pubkeys)
+ sigops_weight += 1 * WITNESS_SCALE_FACTOR
+
+ # Precompute one satisfying and one failing scriptSig/witness for each input.
+ input_data = []
+ for i in range(len(input_utxos)):
+ fn = input_utxos[i].spender.sat_function
+ fail = None
+ success = fn(tx, i, [utxo.output for utxo in input_utxos], True)
+ if not input_utxos[i].spender.no_fail:
+ fail = fn(tx, i, [utxo.output for utxo in input_utxos], False)
+ input_data.append((fail, success))
+ if self.options.dump_tests:
+ dump_json_test(tx, input_utxos, i, success, fail)
+
+ # Sign each input incorrectly once on each complete signing pass, except the very last.
+ for fail_input in list(range(len(input_utxos))) + [None]:
+ # Skip trying to fail at spending something that can't be made to fail.
+ if fail_input is not None and input_utxos[fail_input].spender.no_fail:
+ continue
+ # Expected message with each input failure, may be None(which is ignored)
+ expected_fail_msg = None if fail_input is None else input_utxos[fail_input].spender.err_msg
+ # Fill inputs/witnesses
+ for i in range(len(input_utxos)):
+ tx.vin[i].scriptSig = input_data[i][i != fail_input][0]
+ tx.wit.vtxinwit[i].scriptWitness.stack = input_data[i][i != fail_input][1]
+ # Submit to mempool to check standardness
+ is_standard_tx = fail_input is None and all(utxo.spender.is_standard for utxo in input_utxos) and tx.nVersion >= 1 and tx.nVersion <= 2
+ tx.rehash()
+ msg = ','.join(utxo.spender.comment + ("*" if n == fail_input else "") for n, utxo in enumerate(input_utxos))
+ if is_standard_tx:
+ node.sendrawtransaction(tx.serialize().hex(), 0)
+ assert node.getmempoolentry(tx.hash) is not None, "Failed to accept into mempool: " + msg
+ else:
+ assert_raises_rpc_error(-26, None, node.sendrawtransaction, tx.serialize().hex(), 0)
+ # Submit in a block
+ self.block_submit(node, [tx], msg, witness=True, accept=fail_input is None, cb_pubkey=cb_pubkey, fees=fee, sigops_weight=sigops_weight, err_msg=expected_fail_msg)
+
+ if (len(spenders) - left) // 200 > (len(spenders) - left - len(input_utxos)) // 200:
+ self.log.info(" - %i tests done" % (len(spenders) - left))
+
+ assert left == 0
+ assert len(normal_utxos) == 0
+ assert len(mismatching_utxos) == 0
+ self.log.info(" - Done")
+
+ def run_test(self):
+ # Post-taproot activation tests go first (pre-taproot tests' blocks are invalid post-taproot).
+ self.log.info("Post-activation tests...")
+ self.nodes[1].generate(101)
+ self.test_spenders(self.nodes[1], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3])
+
+ # Re-connect nodes in case they have been disconnected
+ self.disconnect_nodes(0, 1)
+ self.connect_nodes(0, 1)
+
+ # Transfer value of the largest 500 coins to pre-taproot node.
+ addr = self.nodes[0].getnewaddress()
+
+ unsp = self.nodes[1].listunspent()
+ unsp = sorted(unsp, key=lambda i: i['amount'], reverse=True)
+ unsp = unsp[:500]
+
+ rawtx = self.nodes[1].createrawtransaction(
+ inputs=[{
+ 'txid': i['txid'],
+ 'vout': i['vout']
+ } for i in unsp],
+ outputs={addr: sum(i['amount'] for i in unsp)}
+ )
+ rawtx = self.nodes[1].signrawtransactionwithwallet(rawtx)['hex']
+
+ # Mine a block with the transaction
+ block = create_block(tmpl=self.nodes[1].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS), txlist=[rawtx])
+ add_witness_commitment(block)
+ block.rehash()
+ block.solve()
+ assert_equal(None, self.nodes[1].submitblock(block.serialize().hex()))
+ self.sync_blocks()
+
+ # Pre-taproot activation tests.
+ self.log.info("Pre-activation tests...")
+ # Run each test twice; once in isolation, and once combined with others. Testing in isolation
+ # means that the standardness is verified in every test (as combined transactions are only standard
+ # when all their inputs are standard).
+ self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[1])
+ self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[2, 3])
+
+
+if __name__ == '__main__':
+ TaprootTest().main()
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 81c007c27b..1257dff1ae 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -95,7 +95,7 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info)
# Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets.
- wallets = ['', 'Encrypted', 'secret']
+ wallets = [self.default_wallet_name, 'Encrypted', 'secret']
amounts = [BALANCE + Decimal('9.999928'), Decimal(9), Decimal(31)]
self.nodes[0].createwallet(wallet_name=wallets[1])
self.nodes[0].createwallet(wallet_name=wallets[2])
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 17032a3b83..d675ae174c 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -11,7 +11,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import CTransaction, hash256, FromHex
from test_framework.util import (
assert_equal,
- connect_nodes,
assert_raises_rpc_error,
)
from io import BytesIO
@@ -75,6 +74,7 @@ class ZMQTest (BitcoinTestFramework):
self.test_sequence()
self.test_mempool_sync()
self.test_reorg()
+ self.test_multiple_interfaces()
finally:
# Destroy the ZMQ context.
self.log.debug("Destroying ZMQ context")
@@ -101,7 +101,7 @@ class ZMQTest (BitcoinTestFramework):
rawtx = subs[3]
self.restart_node(0, ["-zmqpub%s=%s" % (sub.topic.decode(), address) for sub in [hashblock, hashtx, rawblock, rawtx]])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
for socket in sockets:
socket.connect(address)
@@ -206,7 +206,7 @@ class ZMQTest (BitcoinTestFramework):
connect_blocks = self.nodes[1].generatetoaddress(2, ADDRESS_BCRT1_P2WSH_OP_TRUE)
# nodes[0] will reorg chain after connecting back nodes[1]
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_blocks() # tx in mempool valid but not advertised
# Should receive nodes[1] tip
@@ -263,7 +263,7 @@ class ZMQTest (BitcoinTestFramework):
self.nodes[1].generatetoaddress(2, ADDRESS_BCRT1_P2WSH_OP_TRUE)
# nodes[0] will reorg chain after connecting back nodes[1]
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
# Then we receive all block (dis)connect notifications for the 2 block reorg
assert_equal((dc_block, "D", None), seq.receive_sequence())
@@ -405,7 +405,7 @@ class ZMQTest (BitcoinTestFramework):
seq = ZMQSubscriber(socket, b'sequence')
self.restart_node(0, ['-zmqpub%s=%s' % (seq.topic.decode(), address)])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
socket.connect(address)
# Relax so that the subscriber is ready before publishing zmq messages
sleep(0.2)
@@ -506,5 +506,28 @@ class ZMQTest (BitcoinTestFramework):
self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
+ def test_multiple_interfaces(self):
+ # Set up two subscribers with different addresses
+ subscribers = []
+ for i in range(2):
+ address = 'tcp://127.0.0.1:%d' % (28334 + i)
+ socket = self.ctx.socket(zmq.SUB)
+ socket.set(zmq.RCVTIMEO, 60000)
+ hashblock = ZMQSubscriber(socket, b"hashblock")
+ socket.connect(address)
+ subscribers.append({'address': address, 'hashblock': hashblock})
+
+ self.restart_node(0, ['-zmqpub%s=%s' % (subscriber['hashblock'].topic.decode(), subscriber['address']) for subscriber in subscribers])
+
+ # Relax so that the subscriber is ready before publishing zmq messages
+ sleep(0.2)
+
+ # Generate 1 block in nodes[0] and receive all notifications
+ self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
+
+ # Should receive the same block hash on both subscribers
+ assert_equal(self.nodes[0].getbestblockhash(), subscribers[0]['hashblock'].receive().hex())
+ assert_equal(self.nodes[0].getbestblockhash(), subscribers[1]['hashblock'].receive().hex())
+
if __name__ == '__main__':
ZMQTest().main()
diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py
index 57a059b7f7..fc4c775668 100755
--- a/test/functional/mempool_accept.py
+++ b/test/functional/mempool_accept.py
@@ -83,31 +83,31 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
)
self.log.info('A transaction not in the mempool')
- fee = 0.00000700
+ fee = Decimal('0.000007')
raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later
- outputs=[{node.getnewaddress(): 0.3 - fee}],
+ outputs=[{node.getnewaddress(): Decimal('0.3') - fee}],
))['hex']
tx = CTransaction()
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
txid_0 = tx.rehash()
self.check_mempool_result(
- result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': Decimal(str(fee))}}],
+ result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}],
rawtxs=[raw_tx_0],
)
self.log.info('A final transaction not in the mempool')
coin = coins.pop() # Pick a random coin(base) to spend
- output_amount = 0.025
+ output_amount = Decimal('0.025')
raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL
outputs=[{node.getnewaddress(): output_amount}],
locktime=node.getblockcount() + 2000, # Can be anything
))['hex']
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final)))
- fee_expected = int(coin['amount']) - output_amount
+ fee_expected = coin['amount'] - output_amount
self.check_mempool_result(
- result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': Decimal(str(fee_expected))}}],
+ result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}],
rawtxs=[tx.serialize().hex()],
maxfeerate=0,
)
@@ -130,7 +130,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
txid_0 = tx.rehash()
self.check_mempool_result(
- result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': Decimal(str(2 * fee))}}],
+ result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': (2 * fee)}}],
rawtxs=[raw_tx_0],
)
@@ -190,7 +190,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
# Reference tx should be valid on itself
self.check_mempool_result(
- result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal(str(0.1 - 0.05))}}],
+ result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}],
rawtxs=[tx.serialize().hex()],
maxfeerate=0,
)
diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py
index fd3dd47e2d..8ac91bd008 100755
--- a/test/functional/mempool_compatibility.py
+++ b/test/functional/mempool_compatibility.py
@@ -21,6 +21,7 @@ from test_framework.test_framework import BitcoinTestFramework
class MempoolCompatibilityTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
+ self.wallet_names = [None, self.default_wallet_name]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -28,10 +29,10 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
def setup_network(self):
self.add_nodes(self.num_nodes, versions=[
- 150200, # oldest version supported by the test framework
+ 190100, # oldest version with getmempoolinfo.loaded (used to avoid intermittent issues)
None,
])
- self.start_nodes([[], ["-wallet="]])
+ self.start_nodes()
self.import_deterministic_coinbase_privkeys()
def run_test(self):
@@ -71,5 +72,6 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
assert old_tx_hash in old_node.getrawmempool()
assert unbroadcasted_tx_hash in old_node.getrawmempool()
+
if __name__ == "__main__":
MempoolCompatibilityTest().main()
diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py
index f73f1a02a2..70cd4ebb3b 100755
--- a/test/functional/mempool_persist.py
+++ b/test/functional/mempool_persist.py
@@ -45,8 +45,6 @@ from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
assert_raises_rpc_error,
- connect_nodes,
- disconnect_nodes,
)
@@ -83,11 +81,11 @@ class MempoolPersistTest(BitcoinTestFramework):
assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time)
# disconnect nodes & make a txn that remains in the unbroadcast set.
- disconnect_nodes(self.nodes[0], 1)
+ self.disconnect_nodes(0, 1)
assert(len(self.nodes[0].getpeerinfo()) == 0)
assert(len(self.nodes[0].p2ps) == 0)
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("12"))
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 2)
self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.")
self.stop_nodes()
diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py
index 854d506f0d..86d382ff69 100755
--- a/test/functional/mempool_spend_coinbase.py
+++ b/test/functional/mempool_spend_coinbase.py
@@ -13,44 +13,48 @@ but less mature coinbase spends are NOT.
"""
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.blocktools import create_raw_transaction
from test_framework.util import assert_equal, assert_raises_rpc_error
+from test_framework.wallet import MiniWallet
class MempoolSpendCoinbaseTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ self.setup_clean_chain = True
def run_test(self):
+ wallet = MiniWallet(self.nodes[0])
+
+ wallet.generate(200)
chain_height = self.nodes[0].getblockcount()
assert_equal(chain_height, 200)
- node0_address = self.nodes[0].getnewaddress()
# Coinbase at height chain_height-100+1 ok in mempool, should
# get mined. Coinbase at height chain_height-100+2 is
- # is too immature to spend.
+ # too immature to spend.
b = [self.nodes[0].getblockhash(n) for n in range(101, 103)]
coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b]
- spends_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49.99) for txid in coinbase_txids]
+ utxo_101 = wallet.get_utxo(txid=coinbase_txids[0])
+ utxo_102 = wallet.get_utxo(txid=coinbase_txids[1])
- spend_101_id = self.nodes[0].sendrawtransaction(spends_raw[0])
+ spend_101_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_101)["txid"]
# coinbase at height 102 should be too immature to spend
- assert_raises_rpc_error(-26,"bad-txns-premature-spend-of-coinbase", self.nodes[0].sendrawtransaction, spends_raw[1])
+ assert_raises_rpc_error(-26,
+ "bad-txns-premature-spend-of-coinbase",
+ lambda: wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_102))
# mempool should have just spend_101:
- assert_equal(self.nodes[0].getrawmempool(), [ spend_101_id ])
+ assert_equal(self.nodes[0].getrawmempool(), [spend_101_id])
# mine a block, spend_101 should get confirmed
self.nodes[0].generate(1)
assert_equal(set(self.nodes[0].getrawmempool()), set())
# ... and now height 102 can be spent:
- spend_102_id = self.nodes[0].sendrawtransaction(spends_raw[1])
- assert_equal(self.nodes[0].getrawmempool(), [ spend_102_id ])
+ spend_102_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_102)["txid"]
+ assert_equal(self.nodes[0].getrawmempool(), [spend_102_id])
+
if __name__ == '__main__':
MempoolSpendCoinbaseTest().main()
diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py
index abd5a03d95..b475b65e68 100755
--- a/test/functional/mempool_unbroadcast.py
+++ b/test/functional/mempool_unbroadcast.py
@@ -11,9 +11,7 @@ from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
create_confirmed_utxos,
- disconnect_nodes,
)
MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # 15 minutes in seconds
@@ -36,7 +34,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
min_relay_fee = node.getnetworkinfo()["relayfee"]
utxos = create_confirmed_utxos(min_relay_fee, node, 10)
- disconnect_nodes(node, 1)
+ self.disconnect_nodes(0, 1)
self.log.info("Generate transactions that only node 0 knows about")
@@ -70,7 +68,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
self.restart_node(0)
self.log.info("Reconnect nodes & check if they are sent to node 1")
- connect_nodes(node, 1)
+ self.connect_nodes(0, 1)
# fast forward into the future & ensure that the second node has the txns
node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY)
@@ -91,7 +89,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
time.sleep(2) # allow sufficient time for possibility of broadcast
assert_equal(len(conn.get_invs()), 0)
- disconnect_nodes(node, 1)
+ self.disconnect_nodes(0, 1)
node.disconnect_p2ps()
def test_txn_removal(self):
diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py
index 1b2c7644bd..ba467c1517 100755
--- a/test/functional/mining_basic.py
+++ b/test/functional/mining_basic.py
@@ -13,6 +13,7 @@ from decimal import Decimal
from test_framework.blocktools import (
create_coinbase,
+ NORMAL_GBT_REQUEST_PARAMS,
TIME_GENESIS_BLOCK,
)
from test_framework.messages import (
@@ -25,9 +26,11 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
)
+VERSIONBITS_TOP_BITS = 0x20000000
+VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
+
def assert_template(node, block, expect, rehash=True):
if rehash:
@@ -55,8 +58,16 @@ class MiningTest(BitcoinTestFramework):
assert_equal(mining_info['blocks'], 200)
assert_equal(mining_info['currentblocktx'], 0)
assert_equal(mining_info['currentblockweight'], 4000)
+
+ self.log.info('test blockversion')
+ self.restart_node(0, extra_args=['-mocktime={}'.format(t), '-blockversion=1337'])
+ self.connect_nodes(0, 1)
+ assert_equal(1337, self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
+ self.restart_node(0, extra_args=['-mocktime={}'.format(t)])
+ self.connect_nodes(0, 1)
+ assert_equal(VERSIONBITS_TOP_BITS + (1 << VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT), self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
self.restart_node(0)
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
def run_test(self):
self.mine_chain()
@@ -80,7 +91,7 @@ class MiningTest(BitcoinTestFramework):
# Mine a block to leave initial block download
node.generatetoaddress(1, node.get_deterministic_priv_key().address)
- tmpl = node.getblocktemplate({'rules': ['segwit']})
+ tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
self.log.info("getblocktemplate: Test capability advertised")
assert 'proposal' in tmpl['capabilities']
assert 'coinbasetxn' not in tmpl
diff --git a/test/functional/mining_getblocktemplate_longpoll.py b/test/functional/mining_getblocktemplate_longpoll.py
index 6d0b241e57..2adafb1fdb 100755
--- a/test/functional/mining_getblocktemplate_longpoll.py
+++ b/test/functional/mining_getblocktemplate_longpoll.py
@@ -5,11 +5,13 @@
"""Test longpolling with getblocktemplate."""
from decimal import Decimal
+import random
+import threading
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import get_rpc_proxy, random_transaction
+from test_framework.util import get_rpc_proxy
+from test_framework.wallet import MiniWallet
-import threading
class LongpollThread(threading.Thread):
def __init__(self, node):
@@ -29,45 +31,48 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
self.num_nodes = 2
self.supports_cli = False
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
def run_test(self):
self.log.info("Warning: this test will take about 70 seconds in the best case. Be patient.")
+ self.log.info("Test that longpollid doesn't change between successive getblocktemplate() invocations if nothing else happens")
self.nodes[0].generate(10)
template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
longpollid = template['longpollid']
- # longpollid should not change between successive invocations if nothing else happens
template2 = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert template2['longpollid'] == longpollid
- # Test 1: test that the longpolling wait if we do nothing
+ self.log.info("Test that longpoll waits if we do nothing")
thr = LongpollThread(self.nodes[0])
thr.start()
# check that thread still lives
thr.join(5) # wait 5 seconds or until thread exits
assert thr.is_alive()
- # Test 2: test that longpoll will terminate if another node generates a block
- self.nodes[1].generate(1) # generate a block on another node
+ miniwallets = [ MiniWallet(node) for node in self.nodes ]
+ self.log.info("Test that longpoll will terminate if another node generates a block")
+ miniwallets[1].generate(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()
- # Test 3: test that longpoll will terminate if we generate a block ourselves
+ self.log.info("Test that longpoll will terminate if we generate a block ourselves")
thr = LongpollThread(self.nodes[0])
thr.start()
- self.nodes[0].generate(1) # generate a block on another node
+ miniwallets[0].generate(1) # generate a block on own node
thr.join(5) # wait 5 seconds or until thread exits
assert not thr.is_alive()
- # Test 4: test that introducing a new transaction into the mempool will terminate the longpoll
+ # Add enough mature utxos to the wallets, so that all txs spend confirmed coins
+ self.nodes[0].generate(100)
+ self.sync_blocks()
+
+ 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"]
- # min_relay_fee is fee per 1000 bytes, which should be more than enough.
- (txid, txhex, fee) = random_transaction(self.nodes, Decimal("1.1"), min_relay_fee, Decimal("0.001"), 20)
+ 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)
# 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_addrv2_relay.py b/test/functional/p2p_addrv2_relay.py
new file mode 100755
index 0000000000..23ce3e5d04
--- /dev/null
+++ b/test/functional/p2p_addrv2_relay.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 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 addrv2 relay
+"""
+
+import time
+
+from test_framework.messages import (
+ CAddress,
+ msg_addrv2,
+ NODE_NETWORK,
+ NODE_WITNESS,
+)
+from test_framework.p2p import P2PInterface
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+ADDRS = []
+for i in range(10):
+ addr = CAddress()
+ addr.time = int(time.time()) + i
+ addr.nServices = NODE_NETWORK | NODE_WITNESS
+ addr.ip = "123.123.123.{}".format(i % 256)
+ addr.port = 8333 + i
+ ADDRS.append(addr)
+
+
+class AddrReceiver(P2PInterface):
+ addrv2_received_and_checked = False
+
+ def __init__(self):
+ super().__init__(support_addrv2 = True)
+
+ def on_addrv2(self, message):
+ for addr in message.addrs:
+ assert_equal(addr.nServices, 9)
+ assert addr.ip.startswith('123.123.123.')
+ assert (8333 <= addr.port < 8343)
+ self.addrv2_received_and_checked = True
+
+ def wait_for_addrv2(self):
+ self.wait_until(lambda: "addrv2" in self.last_message)
+
+
+class AddrTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def run_test(self):
+ self.log.info('Create connection that sends addrv2 messages')
+ addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
+ msg = msg_addrv2()
+
+ self.log.info('Send too-large addrv2 message')
+ msg.addrs = ADDRS * 101
+ with self.nodes[0].assert_debug_log(['addrv2 message size = 1010']):
+ addr_source.send_and_ping(msg)
+
+ self.log.info('Check that addrv2 message content is relayed and added to addrman')
+ addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
+ msg.addrs = ADDRS
+ with self.nodes[0].assert_debug_log([
+ 'Added 10 addresses from 127.0.0.1: 0 tried',
+ 'received: addrv2 (131 bytes) peer=0',
+ 'sending addrv2 (131 bytes) peer=1',
+ ]):
+ addr_source.send_and_ping(msg)
+ self.nodes[0].setmocktime(int(time.time()) + 30 * 60)
+ addr_receiver.wait_for_addrv2()
+
+ assert addr_receiver.addrv2_received_and_checked
+
+
+if __name__ == '__main__':
+ AddrTest().main()
diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py
index 84178d0dd7..3250cbecf9 100755
--- a/test/functional/p2p_blockfilters.py
+++ b/test/functional/p2p_blockfilters.py
@@ -22,8 +22,6 @@ from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
- disconnect_nodes,
)
class CFiltersClient(P2PInterface):
@@ -61,7 +59,7 @@ class CompactFiltersTest(BitcoinTestFramework):
self.sync_blocks(timeout=600)
# Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting
- disconnect_nodes(self.nodes[0], 1)
+ self.disconnect_nodes(0, 1)
self.nodes[0].generate(1)
self.wait_until(lambda: self.nodes[0].getblockcount() == 1000)
@@ -90,7 +88,7 @@ class CompactFiltersTest(BitcoinTestFramework):
assert_equal(len(response.headers), 1)
self.log.info("Reorg node 0 to a new chain.")
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_blocks(timeout=600)
main_block_hash = self.nodes[0].getblockhash(1000)
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index 646baa1550..e80422d1cf 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -59,7 +59,7 @@ class P2PBlocksOnly(BitcoinTestFramework):
self.log.info('Check that txs from peers with relay-permission are not rejected and relayed to others')
self.log.info("Restarting node 0 with relay permission and blocksonly")
- self.restart_node(0, ["-persistmempool=0", "-whitelist=relay@127.0.0.1", "-blocksonly"])
+ self.restart_node(0, ["-persistmempool=0", "-whitelist=relay@127.0.0.1", "-blocksonly", '-deprecatedrpc=whitelisted'])
assert_equal(self.nodes[0].getrawmempool(), [])
first_peer = self.nodes[0].add_p2p_connection(P2PInterface())
second_peer = self.nodes[0].add_p2p_connection(P2PInterface())
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 2a320dbd99..9a9df73049 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -9,7 +9,7 @@ Version 2 compact blocks are post-segwit (wtxids)
"""
import random
-from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment
+from test_framework.blocktools import create_block, NORMAL_GBT_REQUEST_PARAMS, add_witness_commitment
from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_no_witness_block, msg_no_witness_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_block, msg_blocktxn, MSG_BLOCK, MSG_CMPCT_BLOCK, MSG_WITNESS_FLAG, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex
from test_framework.p2p import p2p_lock, P2PInterface
from test_framework.script import CScript, OP_TRUE, OP_DROP
@@ -104,11 +104,7 @@ class CompactBlocksTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def build_block_on_tip(self, node, segwit=False):
- height = node.getblockcount()
- tip = node.getbestblockhash()
- mtp = node.getblockheader(tip)['mediantime']
- block = create_block(int(tip, 16), create_coinbase(height + 1), mtp + 1)
- block.nVersion = 4
+ block = create_block(tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS))
if segwit:
add_witness_commitment(block)
block.solve()
@@ -797,6 +793,9 @@ class CompactBlocksTest(BitcoinTestFramework):
assert_highbandwidth_states(self.nodes[0], hb_to=True, hb_from=False)
def run_test(self):
+ # Get the nodes out of IBD
+ self.nodes[0].generate(1)
+
# Setup the p2p connections
self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1), services=NODE_NETWORK)
diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py
index b7c2a306eb..a7f51b5364 100755
--- a/test/functional/p2p_disconnect_ban.py
+++ b/test/functional/p2p_disconnect_ban.py
@@ -9,7 +9,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
)
class DisconnectBanTest(BitcoinTestFramework):
@@ -19,8 +18,11 @@ class DisconnectBanTest(BitcoinTestFramework):
def run_test(self):
self.log.info("Connect nodes both way")
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 0)
+ # By default, the test framework sets up an addnode connection from
+ # node 1 --> node0. By connecting node0 --> node 1, we're left with
+ # the two nodes being connected both ways.
+ # Topology will look like: node0 <--> node1
+ self.connect_nodes(0, 1)
self.log.info("Test setban and listbanned RPCs")
@@ -78,8 +80,8 @@ class DisconnectBanTest(BitcoinTestFramework):
# Clear ban lists
self.nodes[1].clearbanned()
self.log.info("Connect nodes both way")
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 0)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 0)
self.log.info("Test disconnectnode RPCs")
@@ -98,7 +100,7 @@ class DisconnectBanTest(BitcoinTestFramework):
assert not [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1]
self.log.info("disconnectnode: successfully reconnect node")
- connect_nodes(self.nodes[0], 1) # reconnect the node
+ self.connect_nodes(0, 1) # reconnect the node
assert_equal(len(self.nodes[0].getpeerinfo()), 2)
assert [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1]
diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py
index ea066a984d..51b7623fe3 100755
--- a/test/functional/p2p_feefilter.py
+++ b/test/functional/p2p_feefilter.py
@@ -108,9 +108,10 @@ class FeeFilterTest(BitcoinTestFramework):
# to 35 entries in an inv, which means that when this next transaction
# is eligible for relay, the prior transactions from node1 are eligible
# as well.
- txids = [miniwallet.send_self_transfer(fee_rate=Decimal('0.00020000'), from_node=node0)['wtxid'] for _ in range(3)]
+ txids = [miniwallet.send_self_transfer(fee_rate=Decimal('0.00020000'), from_node=node0)['wtxid'] for _ in range(1)]
conn.wait_for_invs_to_match(txids)
conn.clear_invs()
+ self.sync_mempools() # must be sure node 1 has received all txs
self.log.info("Remove fee filter and check txs are received again")
conn.send_and_ping(msg_feefilter(0))
diff --git a/test/functional/p2p_fingerprint.py b/test/functional/p2p_fingerprint.py
index aaf862e6c8..9614ab6872 100755
--- a/test/functional/p2p_fingerprint.py
+++ b/test/functional/p2p_fingerprint.py
@@ -18,6 +18,7 @@ from test_framework.p2p import (
msg_block,
msg_getdata,
msg_getheaders,
+ p2p_lock,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -57,18 +58,6 @@ class P2PFingerprintTest(BitcoinTestFramework):
msg.hashstop = block_hash
node.send_message(msg)
- # Check whether last block received from node has a given hash
- def last_block_equals(self, expected_hash, node):
- block_msg = node.last_message.get("block")
- return block_msg and block_msg.block.rehash() == expected_hash
-
- # Check whether last block header received from node has a given hash
- def last_header_equals(self, expected_hash, node):
- headers_msg = node.last_message.get("headers")
- return (headers_msg and
- headers_msg.headers and
- headers_msg.headers[0].rehash() == expected_hash)
-
# Checks that stale blocks timestamped more than a month ago are not served
# by the node while recent stale blocks and old active chain blocks are.
# This does not currently test that stale blocks timestamped within the
@@ -101,34 +90,31 @@ class P2PFingerprintTest(BitcoinTestFramework):
# Check that getdata request for stale block succeeds
self.send_block_request(stale_hash, node0)
- test_function = lambda: self.last_block_equals(stale_hash, node0)
- self.wait_until(test_function, timeout=3)
+ node0.wait_for_block(stale_hash, timeout=3)
# Check that getheader request for stale block header succeeds
self.send_header_request(stale_hash, node0)
- test_function = lambda: self.last_header_equals(stale_hash, node0)
- self.wait_until(test_function, timeout=3)
+ node0.wait_for_header(hex(stale_hash), timeout=3)
# Longest chain is extended so stale is much older than chain tip
self.nodes[0].setmocktime(0)
- tip = self.nodes[0].generatetoaddress(1, self.nodes[0].get_deterministic_priv_key().address)[0]
+ block_hash = int(self.nodes[0].generatetoaddress(1, self.nodes[0].get_deterministic_priv_key().address)[-1], 16)
assert_equal(self.nodes[0].getblockcount(), 14)
-
- # Send getdata & getheaders to refresh last received getheader message
- block_hash = int(tip, 16)
- self.send_block_request(block_hash, node0)
- self.send_header_request(block_hash, node0)
- node0.sync_with_ping()
+ node0.wait_for_block(block_hash, timeout=3)
# Request for very old stale block should now fail
+ with p2p_lock:
+ node0.last_message.pop("block", None)
self.send_block_request(stale_hash, node0)
- time.sleep(3)
- assert not self.last_block_equals(stale_hash, node0)
+ node0.sync_with_ping()
+ assert "block" not in node0.last_message
# Request for very old stale block header should now fail
+ with p2p_lock:
+ node0.last_message.pop("headers", None)
self.send_header_request(stale_hash, node0)
- time.sleep(3)
- assert not self.last_header_equals(stale_hash, node0)
+ node0.sync_with_ping()
+ assert "headers" not in node0.last_message
# Verify we can fetch very old blocks and headers on the active chain
block_hash = int(block_hashes[2], 16)
@@ -137,12 +123,11 @@ class P2PFingerprintTest(BitcoinTestFramework):
node0.sync_with_ping()
self.send_block_request(block_hash, node0)
- test_function = lambda: self.last_block_equals(block_hash, node0)
- self.wait_until(test_function, timeout=3)
+ node0.wait_for_block(block_hash, timeout=3)
self.send_header_request(block_hash, node0)
- test_function = lambda: self.last_header_equals(block_hash, node0)
- self.wait_until(test_function, timeout=3)
+ node0.wait_for_header(hex(block_hash), timeout=3)
+
if __name__ == '__main__':
P2PFingerprintTest().main()
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
index fe57057a83..db72a361d9 100755
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -4,6 +4,9 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test node responses to invalid network messages."""
+import struct
+import time
+
from test_framework.messages import (
CBlockHeader,
CInv,
@@ -24,10 +27,12 @@ from test_framework.p2p import (
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ hex_str_to_bytes,
)
VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte length prefix
+
class msg_unrecognized:
"""Nonsensical message. Modeled after similar types in test_framework.messages."""
@@ -43,6 +48,11 @@ class msg_unrecognized:
return "{}(data={})".format(self.msgtype, self.str_data)
+class SenderOfAddrV2(P2PInterface):
+ def wait_for_sendaddrv2(self):
+ self.wait_until(lambda: 'sendaddrv2' in self.last_message)
+
+
class InvalidMessagesTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
@@ -54,6 +64,10 @@ class InvalidMessagesTest(BitcoinTestFramework):
self.test_checksum()
self.test_size()
self.test_msgtype()
+ self.test_addrv2_empty()
+ self.test_addrv2_no_addresses()
+ self.test_addrv2_too_long_address()
+ self.test_addrv2_unrecognized_network()
self.test_oversized_inv_msg()
self.test_oversized_getdata_msg()
self.test_oversized_headers_msg()
@@ -64,13 +78,13 @@ class InvalidMessagesTest(BitcoinTestFramework):
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
# Create valid message
msg = conn.build_message(msg_ping(nonce=12345))
- cut_pos = 12 # Chosen at an arbitrary position within the header
+ cut_pos = 12 # Chosen at an arbitrary position within the header
# Send message in two pieces
- before = int(self.nodes[0].getnettotals()['totalbytesrecv'])
+ before = self.nodes[0].getnettotals()['totalbytesrecv']
conn.send_raw_message(msg[:cut_pos])
# Wait until node has processed the first half of the message
- self.wait_until(lambda: int(self.nodes[0].getnettotals()['totalbytesrecv']) != before)
- middle = int(self.nodes[0].getnettotals()['totalbytesrecv'])
+ 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)
@@ -81,7 +95,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_magic_bytes(self):
self.log.info("Test message with invalid magic bytes disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
- with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART badmsg']):
+ with self.nodes[0].assert_debug_log(['HEADER ERROR - MESSAGESTART (badmsg, 2 bytes), received ffffffff']):
msg = conn.build_message(msg_unrecognized(str_data="d"))
# modify magic bytes
msg = b'\xff' * 4 + msg[4:]
@@ -100,12 +114,14 @@ class InvalidMessagesTest(BitcoinTestFramework):
msg = msg[:cut_len] + b'\xff' * 4 + msg[cut_len + 4:]
conn.send_raw_message(msg)
conn.sync_with_ping(timeout=1)
+ # Check that traffic is accounted for (24 bytes header + 2 bytes payload)
+ assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 26)
self.nodes[0].disconnect_p2ps()
def test_size(self):
self.log.info("Test message with oversized payload disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
- with self.nodes[0].assert_debug_log(['']):
+ with self.nodes[0].assert_debug_log(['HEADER ERROR - SIZE (badmsg, 4000001 bytes)']):
msg = msg_unrecognized(str_data="d" * (VALID_DATA_LIMIT + 1))
msg = conn.build_message(msg)
conn.send_raw_message(msg)
@@ -115,16 +131,95 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_msgtype(self):
self.log.info("Test message with invalid message type logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
- with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: ERRORS IN HEADER']):
+ with self.nodes[0].assert_debug_log(['HEADER ERROR - COMMAND']):
msg = msg_unrecognized(str_data="d")
- msg.msgtype = b'\xff' * 12
msg = conn.build_message(msg)
# Modify msgtype
msg = msg[:7] + b'\x00' + msg[7 + 1:]
conn.send_raw_message(msg)
conn.sync_with_ping(timeout=1)
+ # Check that traffic is accounted for (24 bytes header + 2 bytes payload)
+ assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 26)
self.nodes[0].disconnect_p2ps()
+ def test_addrv2(self, label, required_log_messages, raw_addrv2):
+ node = self.nodes[0]
+ conn = node.add_p2p_connection(SenderOfAddrV2())
+
+ # Make sure bitcoind signals support for ADDRv2, otherwise this test
+ # will bombard an old node with messages it does not recognize which
+ # will produce unexpected results.
+ conn.wait_for_sendaddrv2()
+
+ self.log.info('Test addrv2: ' + label)
+
+ msg = msg_unrecognized(str_data=b'')
+ msg.msgtype = b'addrv2'
+ with node.assert_debug_log(required_log_messages):
+ # override serialize() which would include the length of the data
+ msg.serialize = lambda: raw_addrv2
+ conn.send_raw_message(conn.build_message(msg))
+ conn.sync_with_ping()
+
+ node.disconnect_p2ps()
+
+ def test_addrv2_empty(self):
+ self.test_addrv2('empty',
+ [
+ 'received: addrv2 (0 bytes)',
+ 'ProcessMessages(addrv2, 0 bytes): Exception',
+ 'end of data',
+ ],
+ b'')
+
+ def test_addrv2_no_addresses(self):
+ self.test_addrv2('no addresses',
+ [
+ 'received: addrv2 (1 bytes)',
+ ],
+ hex_str_to_bytes('00'))
+
+ def test_addrv2_too_long_address(self):
+ self.test_addrv2('too long address',
+ [
+ 'received: addrv2 (525 bytes)',
+ 'ProcessMessages(addrv2, 525 bytes): Exception',
+ 'Address too long: 513 > 512',
+ ],
+ hex_str_to_bytes(
+ '01' + # number of entries
+ '61bc6649' + # time, Fri Jan 9 02:54:25 UTC 2009
+ '00' + # service flags, COMPACTSIZE(NODE_NONE)
+ '01' + # network type (IPv4)
+ 'fd0102' + # address length (COMPACTSIZE(513))
+ 'ab' * 513 + # address
+ '208d')) # port
+
+ def test_addrv2_unrecognized_network(self):
+ now_hex = struct.pack('<I', int(time.time())).hex()
+ self.test_addrv2('unrecognized network',
+ [
+ 'received: addrv2 (25 bytes)',
+ 'IP 9.9.9.9 mapped',
+ 'Added 1 addresses',
+ ],
+ hex_str_to_bytes(
+ '02' + # number of entries
+ # this should be ignored without impeding acceptance of subsequent ones
+ now_hex + # time
+ '01' + # service flags, COMPACTSIZE(NODE_NETWORK)
+ '99' + # network type (unrecognized)
+ '02' + # address length (COMPACTSIZE(2))
+ 'ab' * 2 + # address
+ '208d' + # port
+ # this should be added:
+ now_hex + # time
+ '01' + # service flags, COMPACTSIZE(NODE_NETWORK)
+ '01' + # network type (IPv4)
+ '04' + # address length (COMPACTSIZE(4))
+ '09' * 4 + # address
+ '208d')) # port
+
def test_oversized_msg(self, msg, size):
msg_type = msg.msgtype.decode('ascii')
self.log.info("Test {} message of size {} is logged as misbehaving".format(msg_type, size))
diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py
index 9e761db03f..a45f792e81 100755
--- a/test/functional/p2p_leak_tx.py
+++ b/test/functional/p2p_leak_tx.py
@@ -5,11 +5,12 @@
"""Test that we don't leak txs to inbound peers that we haven't yet announced to"""
from test_framework.messages import msg_getdata, CInv, MSG_TX
-from test_framework.p2p import P2PDataStore
+from test_framework.p2p import p2p_lock, P2PDataStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
)
+from test_framework.wallet import MiniWallet
class P2PNode(P2PDataStore):
@@ -21,12 +22,12 @@ class P2PLeakTxTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
def run_test(self):
gen_node = self.nodes[0] # The block and tx generating node
- gen_node.generate(1)
+ miniwallet = MiniWallet(gen_node)
+ # Add enough mature utxos to the wallet, so that all txs spend confirmed coins
+ miniwallet.generate(1)
+ gen_node.generate(100)
inbound_peer = self.nodes[0].add_p2p_connection(P2PNode()) # An "attacking" inbound peer
@@ -34,18 +35,20 @@ class P2PLeakTxTest(BitcoinTestFramework):
self.log.info("Running test up to {} times.".format(MAX_REPEATS))
for i in range(MAX_REPEATS):
self.log.info('Run repeat {}'.format(i + 1))
- txid = gen_node.sendtoaddress(gen_node.getnewaddress(), 0.01)
+ txid = miniwallet.send_self_transfer(from_node=gen_node)['wtxid']
want_tx = msg_getdata()
want_tx.inv.append(CInv(t=MSG_TX, h=int(txid, 16)))
- inbound_peer.last_message.pop('notfound', None)
+ with p2p_lock:
+ inbound_peer.last_message.pop('notfound', None)
inbound_peer.send_and_ping(want_tx)
if inbound_peer.last_message.get('notfound'):
self.log.debug('tx {} was not yet announced to us.'.format(txid))
self.log.debug("node has responded with a notfound message. End test.")
assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(txid, 16))
- inbound_peer.last_message.pop('notfound')
+ with p2p_lock:
+ inbound_peer.last_message.pop('notfound')
break
else:
self.log.debug('tx {} was already announced to us. Try test again.'.format(txid))
diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py
index 2c9cbea5e4..b1a7ef6877 100755
--- a/test/functional/p2p_node_network_limited.py
+++ b/test/functional/p2p_node_network_limited.py
@@ -13,8 +13,6 @@ from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- disconnect_nodes,
- connect_nodes,
)
@@ -40,9 +38,9 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
self.extra_args = [['-prune=550', '-addrmantest'], [], []]
def disconnect_all(self):
- disconnect_nodes(self.nodes[0], 1)
- disconnect_nodes(self.nodes[0], 2)
- disconnect_nodes(self.nodes[1], 2)
+ self.disconnect_nodes(0, 1)
+ self.disconnect_nodes(0, 2)
+ self.disconnect_nodes(1, 2)
def setup_network(self):
self.add_nodes(self.num_nodes, self.extra_args)
@@ -60,7 +58,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
assert_equal(int(self.nodes[0].getnetworkinfo()['localservices'], 16), expected_services)
self.log.info("Mine enough blocks to reach the NODE_NETWORK_LIMITED range.")
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
blocks = self.nodes[1].generatetoaddress(292, self.nodes[1].get_deterministic_priv_key().address)
self.sync_blocks([self.nodes[0], self.nodes[1]])
@@ -85,7 +83,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
# connect unsynced node 2 with pruned NODE_NETWORK_LIMITED peer
# because node 2 is in IBD and node 0 is a NODE_NETWORK_LIMITED peer, sync must not be possible
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 2)
try:
self.sync_blocks([self.nodes[0], self.nodes[2]], timeout=5)
except:
@@ -94,7 +92,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getblockheader(self.nodes[2].getbestblockhash())['height'], 0)
# now connect also to node 1 (non pruned)
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(1, 2)
# sync must be possible
self.sync_blocks()
@@ -106,7 +104,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
self.nodes[0].generatetoaddress(10, self.nodes[0].get_deterministic_priv_key().address)
# connect node1 (non pruned) with node0 (pruned) and check if the can sync
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
# sync must be possible, node 1 is no longer in IBD and should therefore connect to node 0 (NODE_NETWORK_LIMITED)
self.sync_blocks([self.nodes[0], self.nodes[1]])
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index 3ec36edb41..ed82e6a2e2 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -22,7 +22,6 @@ from test_framework.test_node import ErrorMatch
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
p2p_port,
)
@@ -43,6 +42,13 @@ class P2PPermissionsTests(BitcoinTestFramework):
True)
self.checkpermission(
+ # check without deprecatedrpc=whitelisted
+ ["-whitelist=127.0.0.1"],
+ # Make sure the default values in the command line documentation match the ones here
+ ["relay", "noban", "mempool", "download"],
+ None)
+
+ self.checkpermission(
# no permission (even with forcerelay)
["-whitelist=@127.0.0.1", "-whitelistforcerelay=1"],
[],
@@ -81,6 +87,12 @@ class P2PPermissionsTests(BitcoinTestFramework):
False)
self.checkpermission(
+ # check without deprecatedrpc=whitelisted
+ ["-whitelist=noban,mempool@127.0.0.1", "-whitelistrelay"],
+ ["noban", "mempool", "download"],
+ None)
+
+ self.checkpermission(
# legacy whitelistforcerelay should be ignored
["-whitelist=noban,mempool@127.0.0.1", "-whitelistforcerelay"],
["noban", "mempool", "download"],
@@ -133,7 +145,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
p2p_rebroadcast_wallet.send_txs_and_test([tx], self.nodes[1])
self.log.debug("Check that node[1] will send the tx to node[0] even though it is already in the mempool")
- connect_nodes(self.nodes[1], 0)
+ self.connect_nodes(1, 0)
with self.nodes[1].assert_debug_log(["Force relaying tx {} from peer=0".format(txid)]):
p2p_rebroadcast_wallet.send_txs_and_test([tx], self.nodes[1])
self.wait_until(lambda: txid in self.nodes[0].getrawmempool())
@@ -141,18 +153,32 @@ class P2PPermissionsTests(BitcoinTestFramework):
self.log.debug("Check that node[1] will not send an invalid tx to node[0]")
tx.vout[0].nValue += 1
txid = tx.rehash()
+ # Send the transaction twice. The first time, it'll be rejected by ATMP because it conflicts
+ # with a mempool transaction. The second time, it'll be in the recentRejects filter.
+ p2p_rebroadcast_wallet.send_txs_and_test(
+ [tx],
+ self.nodes[1],
+ success=False,
+ reject_reason='{} from peer=0 was not accepted: txn-mempool-conflict'.format(txid)
+ )
+
p2p_rebroadcast_wallet.send_txs_and_test(
[tx],
self.nodes[1],
success=False,
- reject_reason='Not relaying non-mempool transaction {} from forcerelay peer=0'.format(txid),
+ reject_reason='Not relaying non-mempool transaction {} from forcerelay peer=0'.format(txid)
)
def checkpermission(self, args, expectedPermissions, whitelisted):
+ if whitelisted is not None:
+ args = [*args, '-deprecatedrpc=whitelisted']
self.restart_node(1, args)
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
peerinfo = self.nodes[1].getpeerinfo()[0]
- assert_equal(peerinfo['whitelisted'], whitelisted)
+ if whitelisted is None:
+ assert 'whitelisted' not in peerinfo
+ else:
+ assert_equal(peerinfo['whitelisted'], whitelisted)
assert_equal(len(expectedPermissions), len(peerinfo['permissions']))
for p in expectedPermissions:
if not p in peerinfo['permissions']:
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index d79ed449e5..e99ecd8026 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -55,6 +55,7 @@ from test_framework.script import (
MAX_SCRIPT_ELEMENT_SIZE,
OP_0,
OP_1,
+ OP_2,
OP_16,
OP_2DROP,
OP_CHECKMULTISIG,
@@ -80,8 +81,6 @@ from test_framework.script import (
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
- disconnect_nodes,
softfork_active,
hex_str_to_bytes,
assert_raises_rpc_error,
@@ -232,8 +231,8 @@ class SegWitTest(BitcoinTestFramework):
def setup_network(self):
self.setup_nodes()
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(0, 2)
self.sync_all()
# Helper functions
@@ -497,7 +496,7 @@ class SegWitTest(BitcoinTestFramework):
# node2 doesn't need to be connected for this test.
# (If it's connected, node0 may propagate an invalid block to it over
# compact blocks and the nodes would have inconsistent tips.)
- disconnect_nodes(self.nodes[0], 2)
+ self.disconnect_nodes(0, 2)
# Create two outputs, a p2wsh and p2sh-p2wsh
witness_program = CScript([OP_TRUE])
@@ -559,7 +558,7 @@ class SegWitTest(BitcoinTestFramework):
# TODO: support multiple acceptable reject reasons.
test_witness_block(self.nodes[0], self.test_node, block, accepted=False, with_witness=False)
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 2)
self.utxo.pop(0)
self.utxo.append(UTXO(txid, 2, value))
@@ -1400,7 +1399,11 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(len(self.nodes[1].getrawmempool()), 0)
for version in list(range(OP_1, OP_16 + 1)) + [OP_0]:
# First try to spend to a future version segwit script_pubkey.
- script_pubkey = CScript([CScriptOp(version), witness_hash])
+ if version == OP_1:
+ # Don't use 32-byte v1 witness (used by Taproot; see BIP 341)
+ script_pubkey = CScript([CScriptOp(version), witness_hash + b'\x00'])
+ else:
+ script_pubkey = CScript([CScriptOp(version), witness_hash])
tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")]
tx.vout = [CTxOut(self.utxo[0].nValue - 1000, script_pubkey)]
tx.rehash()
@@ -1413,9 +1416,9 @@ class SegWitTest(BitcoinTestFramework):
self.sync_blocks()
assert len(self.nodes[0].getrawmempool()) == 0
- # Finally, verify that version 0 -> version 1 transactions
+ # Finally, verify that version 0 -> version 2 transactions
# are standard
- script_pubkey = CScript([CScriptOp(OP_1), witness_hash])
+ script_pubkey = CScript([CScriptOp(OP_2), witness_hash])
tx2 = CTransaction()
tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")]
tx2.vout = [CTxOut(tx.vout[0].nValue - 1000, script_pubkey)]
@@ -1940,7 +1943,7 @@ class SegWitTest(BitcoinTestFramework):
"""Test the behavior of starting up a segwit-aware node after the softfork has activated."""
self.restart_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)])
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 2)
# We reconnect more than 100 blocks, give it plenty of time
self.sync_blocks(timeout=240)
@@ -2094,14 +2097,14 @@ class SegWitTest(BitcoinTestFramework):
raw = self.nodes[0].createrawtransaction([{"txid": unspent['txid'], "vout": unspent['vout']}], {self.nodes[0].getnewaddress(): 1})
tx = FromHex(CTransaction(), raw)
- assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, serialize_with_bogus_witness(tx).hex())
+ assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, hexstring=serialize_with_bogus_witness(tx).hex(), iswitness=True)
with self.nodes[0].assert_debug_log(['Superfluous witness record']):
self.test_node.send_and_ping(msg_bogus_tx(tx))
raw = self.nodes[0].signrawtransactionwithwallet(raw)
assert raw['complete']
raw = raw['hex']
tx = FromHex(CTransaction(), raw)
- assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, serialize_with_bogus_witness(tx).hex())
+ assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, hexstring=serialize_with_bogus_witness(tx).hex(), iswitness=True)
with self.nodes[0].assert_debug_log(['Unknown transaction optional data']):
self.test_node.send_and_ping(msg_bogus_tx(tx))
diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py
index 653c7ae43f..16d9302db8 100755
--- a/test/functional/p2p_tx_download.py
+++ b/test/functional/p2p_tx_download.py
@@ -42,15 +42,15 @@ class TestP2PConn(P2PInterface):
# Constants from net_processing
GETDATA_TX_INTERVAL = 60 # seconds
-MAX_GETDATA_RANDOM_DELAY = 2 # seconds
INBOUND_PEER_TX_DELAY = 2 # seconds
TXID_RELAY_DELAY = 2 # seconds
+OVERLOADED_PEER_DELAY = 2 # seconds
MAX_GETDATA_IN_FLIGHT = 100
-TX_EXPIRY_INTERVAL = GETDATA_TX_INTERVAL * 10
+MAX_PEER_TX_ANNOUNCEMENTS = 5000
# Python test constants
NUM_INBOUND = 10
-MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY + TXID_RELAY_DELAY
+MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + INBOUND_PEER_TX_DELAY + TXID_RELAY_DELAY
class TxDownloadTest(BitcoinTestFramework):
@@ -121,14 +121,12 @@ class TxDownloadTest(BitcoinTestFramework):
# * the first time it is re-requested from the outbound peer, plus
# * 2 seconds to avoid races
assert self.nodes[1].getpeerinfo()[0]['inbound'] == False
- timeout = 2 + (MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY) + (
- GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY)
+ timeout = 2 + INBOUND_PEER_TX_DELAY + GETDATA_TX_INTERVAL
self.log.info("Tx should be received at node 1 after {} seconds".format(timeout))
self.sync_mempools(timeout=timeout)
def test_in_flight_max(self):
- self.log.info("Test that we don't request more than {} transactions from any peer, every {} minutes".format(
- MAX_GETDATA_IN_FLIGHT, TX_EXPIRY_INTERVAL / 60))
+ self.log.info("Test that we don't load peers with more than {} transaction requests immediately".format(MAX_GETDATA_IN_FLIGHT))
txids = [i for i in range(MAX_GETDATA_IN_FLIGHT + 2)]
p = self.nodes[0].p2ps[0]
@@ -136,45 +134,130 @@ class TxDownloadTest(BitcoinTestFramework):
with p2p_lock:
p.tx_getdata_count = 0
- p.send_message(msg_inv([CInv(t=MSG_WTX, h=i) for i in txids]))
+ mock_time = int(time.time() + 1)
+ self.nodes[0].setmocktime(mock_time)
+ for i in range(MAX_GETDATA_IN_FLIGHT):
+ p.send_message(msg_inv([CInv(t=MSG_WTX, h=txids[i])]))
+ p.sync_with_ping()
+ mock_time += INBOUND_PEER_TX_DELAY
+ self.nodes[0].setmocktime(mock_time)
p.wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT)
+ for i in range(MAX_GETDATA_IN_FLIGHT, len(txids)):
+ p.send_message(msg_inv([CInv(t=MSG_WTX, h=txids[i])]))
+ p.sync_with_ping()
+ self.log.info("No more than {} requests should be seen within {} seconds after announcement".format(MAX_GETDATA_IN_FLIGHT, INBOUND_PEER_TX_DELAY + OVERLOADED_PEER_DELAY - 1))
+ self.nodes[0].setmocktime(mock_time + INBOUND_PEER_TX_DELAY + OVERLOADED_PEER_DELAY - 1)
+ p.sync_with_ping()
with p2p_lock:
assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT)
-
- self.log.info("Now check that if we send a NOTFOUND for a transaction, we'll get one more request")
- p.send_message(msg_notfound(vec=[CInv(t=MSG_WTX, h=txids[0])]))
- p.wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT + 1, timeout=10)
+ self.log.info("If we wait {} seconds after announcement, we should eventually get more requests".format(INBOUND_PEER_TX_DELAY + OVERLOADED_PEER_DELAY))
+ self.nodes[0].setmocktime(mock_time + INBOUND_PEER_TX_DELAY + OVERLOADED_PEER_DELAY)
+ p.wait_until(lambda: p.tx_getdata_count == len(txids))
+
+ def test_expiry_fallback(self):
+ self.log.info('Check that expiry will select another peer for download')
+ WTXID = 0xffaa
+ peer1 = self.nodes[0].add_p2p_connection(TestP2PConn())
+ peer2 = self.nodes[0].add_p2p_connection(TestP2PConn())
+ for p in [peer1, peer2]:
+ p.send_message(msg_inv([CInv(t=MSG_WTX, h=WTXID)]))
+ # One of the peers is asked for the tx
+ peer2.wait_until(lambda: sum(p.tx_getdata_count for p in [peer1, peer2]) == 1)
with p2p_lock:
- assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT + 1)
-
- WAIT_TIME = TX_EXPIRY_INTERVAL // 2 + TX_EXPIRY_INTERVAL
- self.log.info("if we wait about {} minutes, we should eventually get more requests".format(WAIT_TIME / 60))
- self.nodes[0].setmocktime(int(time.time() + WAIT_TIME))
- p.wait_until(lambda: p.tx_getdata_count == MAX_GETDATA_IN_FLIGHT + 2)
- self.nodes[0].setmocktime(0)
+ peer_expiry, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)
+ assert_equal(peer_fallback.tx_getdata_count, 0)
+ self.nodes[0].setmocktime(int(time.time()) + GETDATA_TX_INTERVAL + 1) # Wait for request to peer_expiry to expire
+ peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1)
+ with p2p_lock:
+ assert_equal(peer_fallback.tx_getdata_count, 1)
+ self.restart_node(0) # reset mocktime
+
+ def test_disconnect_fallback(self):
+ self.log.info('Check that disconnect will select another peer for download')
+ WTXID = 0xffbb
+ peer1 = self.nodes[0].add_p2p_connection(TestP2PConn())
+ peer2 = self.nodes[0].add_p2p_connection(TestP2PConn())
+ for p in [peer1, peer2]:
+ p.send_message(msg_inv([CInv(t=MSG_WTX, h=WTXID)]))
+ # One of the peers is asked for the tx
+ peer2.wait_until(lambda: sum(p.tx_getdata_count for p in [peer1, peer2]) == 1)
+ with p2p_lock:
+ peer_disconnect, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)
+ assert_equal(peer_fallback.tx_getdata_count, 0)
+ peer_disconnect.peer_disconnect()
+ peer_disconnect.wait_for_disconnect()
+ peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1)
+ with p2p_lock:
+ assert_equal(peer_fallback.tx_getdata_count, 1)
+
+ def test_notfound_fallback(self):
+ self.log.info('Check that notfounds will select another peer for download immediately')
+ WTXID = 0xffdd
+ peer1 = self.nodes[0].add_p2p_connection(TestP2PConn())
+ peer2 = self.nodes[0].add_p2p_connection(TestP2PConn())
+ for p in [peer1, peer2]:
+ p.send_message(msg_inv([CInv(t=MSG_WTX, h=WTXID)]))
+ # One of the peers is asked for the tx
+ peer2.wait_until(lambda: sum(p.tx_getdata_count for p in [peer1, peer2]) == 1)
+ with p2p_lock:
+ peer_notfound, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)
+ assert_equal(peer_fallback.tx_getdata_count, 0)
+ peer_notfound.send_and_ping(msg_notfound(vec=[CInv(MSG_WTX, WTXID)])) # Send notfound, so that fallback peer is selected
+ peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1)
+ with p2p_lock:
+ assert_equal(peer_fallback.tx_getdata_count, 1)
+
+ def test_preferred_inv(self):
+ self.log.info('Check that invs from preferred peers are downloaded immediately')
+ self.restart_node(0, extra_args=['-whitelist=noban@127.0.0.1'])
+ peer = self.nodes[0].add_p2p_connection(TestP2PConn())
+ peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))
+ peer.wait_until(lambda: peer.tx_getdata_count >= 1, timeout=1)
+ with p2p_lock:
+ assert_equal(peer.tx_getdata_count, 1)
+
+ def test_large_inv_batch(self):
+ self.log.info('Test how large inv batches are handled with relay permission')
+ self.restart_node(0, extra_args=['-whitelist=relay@127.0.0.1'])
+ peer = self.nodes[0].add_p2p_connection(TestP2PConn())
+ peer.send_message(msg_inv([CInv(t=MSG_WTX, h=wtxid) for wtxid in range(MAX_PEER_TX_ANNOUNCEMENTS + 1)]))
+ peer.wait_until(lambda: peer.tx_getdata_count == MAX_PEER_TX_ANNOUNCEMENTS + 1)
+
+ self.log.info('Test how large inv batches are handled without relay permission')
+ self.restart_node(0)
+ peer = self.nodes[0].add_p2p_connection(TestP2PConn())
+ peer.send_message(msg_inv([CInv(t=MSG_WTX, h=wtxid) for wtxid in range(MAX_PEER_TX_ANNOUNCEMENTS + 1)]))
+ peer.wait_until(lambda: peer.tx_getdata_count == MAX_PEER_TX_ANNOUNCEMENTS)
+ peer.sync_with_ping()
+ with p2p_lock:
+ assert_equal(peer.tx_getdata_count, MAX_PEER_TX_ANNOUNCEMENTS)
def test_spurious_notfound(self):
self.log.info('Check that spurious notfound is ignored')
self.nodes[0].p2ps[0].send_message(msg_notfound(vec=[CInv(MSG_TX, 1)]))
def run_test(self):
- # Setup the p2p connections
- self.peers = []
- for node in self.nodes:
- for _ in range(NUM_INBOUND):
- self.peers.append(node.add_p2p_connection(TestP2PConn()))
-
- self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND))
-
+ # Run tests without mocktime that only need one peer-connection first, to avoid restarting the nodes
+ self.test_expiry_fallback()
+ self.test_disconnect_fallback()
+ self.test_notfound_fallback()
+ self.test_preferred_inv()
+ self.test_large_inv_batch()
self.test_spurious_notfound()
- # Test the in-flight max first, because we want no transactions in
- # flight ahead of this test.
- self.test_in_flight_max()
-
- self.test_inv_block()
-
- self.test_tx_requests()
+ # Run each test against new bitcoind instances, as setting mocktimes has long-term effects on when
+ # the next trickle relay event happens.
+ for test in [self.test_in_flight_max, self.test_inv_block, self.test_tx_requests]:
+ self.stop_nodes()
+ self.start_nodes()
+ self.connect_nodes(1, 0)
+ # Setup the p2p connections
+ self.peers = []
+ for node in self.nodes:
+ for _ in range(NUM_INBOUND):
+ self.peers.append(node.add_p2p_connection(TestP2PConn()))
+ self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND))
+ test()
if __name__ == '__main__':
diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py
index 36b434bce3..e7a05d8547 100755
--- a/test/functional/p2p_unrequested_blocks.py
+++ b/test/functional/p2p_unrequested_blocks.py
@@ -60,7 +60,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
)
@@ -284,7 +283,7 @@ class AcceptBlockTest(BitcoinTestFramework):
test_node.wait_for_disconnect()
# 9. Connect node1 to node0 and ensure it is able to sync
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_blocks([self.nodes[0], self.nodes[1]])
self.log.info("Successfully synced nodes 1 and 0")
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 35cea85c07..f965677408 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -146,7 +146,19 @@ class BlockchainTest(BitcoinTestFramework):
'possible': True,
},
},
- 'active': False}
+ 'active': False
+ },
+ 'taproot': {
+ 'type': 'bip9',
+ 'bip9': {
+ 'status': 'active',
+ 'start_time': -1,
+ 'timeout': 9223372036854775807,
+ 'since': 0
+ },
+ 'height': 0,
+ 'active': True
+ }
})
def _test_getchaintxstats(self):
diff --git a/test/functional/rpc_deprecated.py b/test/functional/rpc_deprecated.py
index b71854d234..adcd8a7d4c 100755
--- a/test/functional/rpc_deprecated.py
+++ b/test/functional/rpc_deprecated.py
@@ -29,7 +29,7 @@ class DeprecatedRpcTest(BitcoinTestFramework):
self.nodes[0].generate(101)
self.nodes[0].createwallet(wallet_name='nopriv', disable_private_keys=True)
noprivs0 = self.nodes[0].get_wallet_rpc('nopriv')
- w0 = self.nodes[0].get_wallet_rpc('')
+ w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
self.nodes[1].createwallet(wallet_name='nopriv', disable_private_keys=True)
noprivs1 = self.nodes[1].get_wallet_rpc('nopriv')
diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py
index 1fff9e1512..81862ac69e 100755
--- a/test/functional/rpc_estimatefee.py
+++ b/test/functional/rpc_estimatefee.py
@@ -28,7 +28,7 @@ class EstimateFeeTest(BitcoinTestFramework):
# wrong type for estimatesmartfee(estimate_mode)
assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].estimatesmartfee, 1, 1)
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", self.nodes[0].estimatesmartfee, 1, 'foo')
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', self.nodes[0].estimatesmartfee, 1, 'foo')
# wrong type for estimaterawfee(threshold)
assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo')
@@ -41,6 +41,8 @@ class EstimateFeeTest(BitcoinTestFramework):
self.nodes[0].estimatesmartfee(1)
# self.nodes[0].estimatesmartfee(1, None)
self.nodes[0].estimatesmartfee(1, 'ECONOMICAL')
+ self.nodes[0].estimatesmartfee(1, 'unset')
+ self.nodes[0].estimatesmartfee(1, 'conservative')
self.nodes[0].estimaterawfee(1)
self.nodes[0].estimaterawfee(1, None)
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 6dcbec2714..8ee0ecab0a 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -5,14 +5,15 @@
"""Test the fundrawtransaction RPC."""
from decimal import Decimal
+from test_framework.descriptors import descsum_create
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
+ assert_approx,
assert_equal,
assert_fee_amount,
assert_greater_than,
assert_greater_than_or_equal,
assert_raises_rpc_error,
- connect_nodes,
count_bytes,
find_vout_for_address,
)
@@ -38,10 +39,10 @@ class RawTransactionsTest(BitcoinTestFramework):
def setup_network(self):
self.setup_nodes()
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 2)
- connect_nodes(self.nodes[0], 2)
- connect_nodes(self.nodes[0], 3)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 2)
+ self.connect_nodes(0, 2)
+ self.connect_nodes(0, 3)
def run_test(self):
self.log.info("Connect nodes, set fees, generate blocks, and sync")
@@ -101,17 +102,19 @@ class RawTransactionsTest(BitcoinTestFramework):
rawmatch = self.nodes[2].fundrawtransaction(rawmatch, {"changePosition":1, "subtractFeeFromOutputs":[0]})
assert_equal(rawmatch["changepos"], -1)
+ self.nodes[3].createwallet(wallet_name="wwatch", disable_private_keys=True)
+ wwatch = self.nodes[3].get_wallet_rpc('wwatch')
watchonly_address = self.nodes[0].getnewaddress()
watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"]
self.watchonly_amount = Decimal(200)
- self.nodes[3].importpubkey(watchonly_pubkey, "", True)
+ wwatch.importpubkey(watchonly_pubkey, "", True)
self.watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, self.watchonly_amount)
# Lock UTXO so nodes[0] doesn't accidentally spend it
self.watchonly_vout = find_vout_for_address(self.nodes[0], self.watchonly_txid, watchonly_address)
self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}])
- self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), self.watchonly_amount / 10)
+ self.nodes[0].sendtoaddress(self.nodes[3].get_wallet_rpc(self.default_wallet_name).getnewaddress(), self.watchonly_amount / 10)
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5)
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
@@ -120,6 +123,8 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all()
+ wwatch.unloadwallet()
+
def test_simple(self):
self.log.info("Test fundrawtxn")
inputs = [ ]
@@ -407,7 +412,7 @@ class RawTransactionsTest(BitcoinTestFramework):
addr1Obj = self.nodes[1].getaddressinfo(addr1)
addr2Obj = self.nodes[1].getaddressinfo(addr2)
- mSigObj = self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
+ mSigObj = self.nodes[3].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
inputs = []
outputs = {mSigObj:1.1}
@@ -439,7 +444,7 @@ class RawTransactionsTest(BitcoinTestFramework):
addr4Obj = self.nodes[1].getaddressinfo(addr4)
addr5Obj = self.nodes[1].getaddressinfo(addr5)
- mSigObj = self.nodes[1].addmultisigaddress(
+ mSigObj = self.nodes[1].createmultisig(
4,
[
addr1Obj['pubkey'],
@@ -465,7 +470,7 @@ class RawTransactionsTest(BitcoinTestFramework):
def test_spend_2of2(self):
"""Spend a 2-of-2 multisig transaction over fundraw."""
- self.log.info("Test fundrawtxn spending 2-of-2 multisig")
+ self.log.info("Test fundpsbt spending 2-of-2 multisig")
# Create 2-of-2 addr.
addr1 = self.nodes[2].getnewaddress()
@@ -474,13 +479,18 @@ class RawTransactionsTest(BitcoinTestFramework):
addr1Obj = self.nodes[2].getaddressinfo(addr1)
addr2Obj = self.nodes[2].getaddressinfo(addr2)
- mSigObj = self.nodes[2].addmultisigaddress(
+ self.nodes[2].createwallet(wallet_name='wmulti', disable_private_keys=True)
+ wmulti = self.nodes[2].get_wallet_rpc('wmulti')
+ w2 = self.nodes[2].get_wallet_rpc(self.default_wallet_name)
+ mSigObj = wmulti.addmultisigaddress(
2,
[
addr1Obj['pubkey'],
addr2Obj['pubkey'],
]
)['address']
+ if not self.options.descriptors:
+ wmulti.importaddress(mSigObj)
# Send 1.2 BTC to msig addr.
self.nodes[0].sendtoaddress(mSigObj, 1.2)
@@ -490,22 +500,39 @@ class RawTransactionsTest(BitcoinTestFramework):
oldBalance = self.nodes[1].getbalance()
inputs = []
outputs = {self.nodes[1].getnewaddress():1.1}
- rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
- fundedTx = self.nodes[2].fundrawtransaction(rawtx)
+ funded_psbt = wmulti.walletcreatefundedpsbt(inputs=inputs, outputs=outputs, options={'changeAddress': w2.getrawchangeaddress()})['psbt']
- signedTx = self.nodes[2].signrawtransactionwithwallet(fundedTx['hex'])
- self.nodes[2].sendrawtransaction(signedTx['hex'])
+ signed_psbt = w2.walletprocesspsbt(funded_psbt)
+ final_psbt = w2.finalizepsbt(signed_psbt['psbt'])
+ self.nodes[2].sendrawtransaction(final_psbt['hex'])
self.nodes[2].generate(1)
self.sync_all()
# Make sure funds are received at node1.
assert_equal(oldBalance+Decimal('1.10000000'), self.nodes[1].getbalance())
+ wmulti.unloadwallet()
+
def test_locked_wallet(self):
- self.log.info("Test fundrawtxn with locked wallet")
+ self.log.info("Test fundrawtxn with locked wallet and hardened derivation")
self.nodes[1].encryptwallet("test")
+ if self.options.descriptors:
+ self.nodes[1].walletpassphrase('test', 10)
+ self.nodes[1].importdescriptors([{
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/0h/*h)'),
+ 'timestamp': 'now',
+ 'active': True
+ },
+ {
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/1h/*h)'),
+ 'timestamp': 'now',
+ 'active': True,
+ 'internal': True
+ }])
+ self.nodes[1].walletlock()
+
# Drain the keypool.
self.nodes[1].getnewaddress()
self.nodes[1].getrawchangeaddress()
@@ -622,7 +649,25 @@ class RawTransactionsTest(BitcoinTestFramework):
outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount / 2}
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
- result = self.nodes[3].fundrawtransaction(rawtx, {'includeWatching': True })
+ self.nodes[3].loadwallet('wwatch')
+ wwatch = self.nodes[3].get_wallet_rpc('wwatch')
+ # Setup change addresses for the watchonly wallet
+ desc_import = [{
+ "desc": descsum_create("wpkh(tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H/1/*)"),
+ "timestamp": "now",
+ "internal": True,
+ "active": True,
+ "keypool": True,
+ "range": [0, 100],
+ "watchonly": True,
+ }]
+ if self.options.descriptors:
+ wwatch.importdescriptors(desc_import)
+ else:
+ wwatch.importmulti(desc_import)
+
+ # Backward compatibility test (2nd params is includeWatching)
+ result = wwatch.fundrawtransaction(rawtx, True)
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
assert_equal(len(res_dec["vin"]), 1)
assert_equal(res_dec["vin"][0]["txid"], self.watchonly_txid)
@@ -630,6 +675,8 @@ class RawTransactionsTest(BitcoinTestFramework):
assert "fee" in result.keys()
assert_greater_than(result["changepos"], -1)
+ wwatch.unloadwallet()
+
def test_all_watched_funds(self):
self.log.info("Test fundrawtxn using entirety of watched funds")
@@ -637,17 +684,19 @@ class RawTransactionsTest(BitcoinTestFramework):
outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount}
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
- # Backward compatibility test (2nd param is includeWatching).
- result = self.nodes[3].fundrawtransaction(rawtx, True)
+ self.nodes[3].loadwallet('wwatch')
+ wwatch = self.nodes[3].get_wallet_rpc('wwatch')
+ w3 = self.nodes[3].get_wallet_rpc(self.default_wallet_name)
+ result = wwatch.fundrawtransaction(rawtx, {'includeWatching': True, 'changeAddress': w3.getrawchangeaddress(), 'subtractFeeFromOutputs': [0]})
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
- assert_equal(len(res_dec["vin"]), 2)
- assert res_dec["vin"][0]["txid"] == self.watchonly_txid or res_dec["vin"][1]["txid"] == self.watchonly_txid
+ assert_equal(len(res_dec["vin"]), 1)
+ assert res_dec["vin"][0]["txid"] == self.watchonly_txid
assert_greater_than(result["fee"], 0)
- assert_greater_than(result["changepos"], -1)
- assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], self.watchonly_amount / 10)
+ assert_equal(result["changepos"], -1)
+ assert_equal(result["fee"] + res_dec["vout"][0]["value"], self.watchonly_amount)
- signedtx = self.nodes[3].signrawtransactionwithwallet(result["hex"])
+ signedtx = wwatch.signrawtransactionwithwallet(result["hex"])
assert not signedtx["complete"]
signedtx = self.nodes[0].signrawtransactionwithwallet(signedtx["hex"])
assert signedtx["complete"]
@@ -655,22 +704,91 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all()
- def test_option_feerate(self):
- self.log.info("Test fundrawtxn feeRate option")
+ wwatch.unloadwallet()
+ def test_option_feerate(self):
+ self.log.info("Test fundrawtxn with explicit fee rates (fee_rate sat/vB and feeRate BTC/kvB)")
+ node = self.nodes[3]
# Make sure there is exactly one input so coin selection can't skew the result.
assert_equal(len(self.nodes[3].listunspent(1)), 1)
-
inputs = []
- outputs = {self.nodes[3].getnewaddress() : 1}
- rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
- result = self.nodes[3].fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee)
- result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee})
- result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee})
- assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[3].fundrawtransaction, rawtx, {"feeRate": 1})
+ outputs = {node.getnewaddress() : 1}
+ rawtx = node.createrawtransaction(inputs, outputs)
+
+ result = node.fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee)
+ btc_kvb_to_sat_vb = 100000 # (1e5)
+ result1 = node.fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
+ result2 = node.fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee})
+ result3 = node.fundrawtransaction(rawtx, {"fee_rate": 10 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
+ result4 = node.fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee})
+ # Test that funding non-standard "zero-fee" transactions is valid.
+ result5 = self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 0})
+ result6 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 0})
+
result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
+ assert_fee_amount(result1['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
+ assert_fee_amount(result4['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
+ assert_fee_amount(result5['fee'], count_bytes(result5['hex']), 0)
+ assert_fee_amount(result6['fee'], count_bytes(result6['hex']), 0)
+
+ # With no arguments passed, expect fee of 141 satoshis.
+ assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001)
+ # Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified.
+ result = node.fundrawtransaction(rawtx, {"fee_rate": 10000})
+ assert_approx(result["fee"], vexp=0.0141, vspan=0.0001)
+
+ self.log.info("Test fundrawtxn with invalid estimate_mode settings")
+ for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
+ node.fundrawtransaction, rawtx, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True})
+ for mode in ["", "foo", Decimal("3.141592")]:
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True})
+
+ self.log.info("Test fundrawtxn with invalid conf_target settings")
+ for mode in ["unset", "economical", "conservative"]:
+ self.log.debug("{}".format(mode))
+ for k, v in {"string": "", "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
+ node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
+ for n in [-1, 0, 1009]:
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
+ node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": n, "add_inputs": True})
+
+ self.log.info("Test invalid fee rate settings")
+ for param, value in {("fee_rate", 100000), ("feeRate", 1.000)}:
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
+ node.fundrawtransaction, rawtx, {param: value, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Amount out of range",
+ node.fundrawtransaction, rawtx, {param: -1, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Amount is not a number or string",
+ node.fundrawtransaction, rawtx, {param: {"foo": "bar"}, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Invalid amount",
+ node.fundrawtransaction, rawtx, {param: "", "add_inputs": True})
+
+ self.log.info("Test min fee rate checks are bypassed with fundrawtxn, e.g. a fee_rate under 1 sat/vB is allowed")
+ node.fundrawtransaction(rawtx, {"fee_rate": 0.99999999, "add_inputs": True})
+ node.fundrawtransaction(rawtx, {"feeRate": 0.00000999, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both feeRate and fee_rate are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both fee_rate (sat/vB) and feeRate (BTC/kvB)",
+ node.fundrawtransaction, rawtx, {"fee_rate": 0.1, "feeRate": 0.1, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both feeRate and estimate_mode passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and feeRate",
+ node.fundrawtransaction, rawtx, {"estimate_mode": "economical", "feeRate": 0.1, "add_inputs": True})
+
+ for param in ["feeRate", "fee_rate"]:
+ self.log.info("- raises RPC error if both {} and conf_target are passed".format(param))
+ assert_raises_rpc_error(-8, "Cannot specify both conf_target and {}. Please provide either a confirmation "
+ "target in blocks for automatic fee estimation, or an explicit fee rate.".format(param),
+ node.fundrawtransaction, rawtx, {param: 1, "conf_target": 1, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both fee_rate and estimate_mode are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and fee_rate",
+ node.fundrawtransaction, rawtx, {"fee_rate": 1, "estimate_mode": "economical", "add_inputs": True})
def test_address_reuse(self):
"""Test no address reuse occurs."""
@@ -698,12 +816,32 @@ class RawTransactionsTest(BitcoinTestFramework):
outputs = {self.nodes[2].getnewaddress(): 1}
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
+ # Test subtract fee from outputs with feeRate (BTC/kvB)
result = [self.nodes[3].fundrawtransaction(rawtx), # uses self.min_relay_tx_fee (set by settxfee)
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee)
self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee}),
self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),]
+ dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result]
+ output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)]
+ change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)]
+ assert_equal(result[0]['fee'], result[1]['fee'], result[2]['fee'])
+ assert_equal(result[3]['fee'], result[4]['fee'])
+ assert_equal(change[0], change[1])
+ assert_equal(output[0], output[1])
+ assert_equal(output[0], output[2] + result[2]['fee'])
+ assert_equal(change[0] + result[0]['fee'], change[2])
+ assert_equal(output[3], output[4] + result[4]['fee'])
+ assert_equal(change[3] + result[3]['fee'], change[4])
+
+ # Test subtract fee from outputs with fee_rate (sat/vB)
+ btc_kvb_to_sat_vb = 100000 # (1e5)
+ result = [self.nodes[3].fundrawtransaction(rawtx), # uses self.min_relay_tx_fee (set by settxfee)
+ self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list
+ self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee)
+ self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee}),
+ self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),]
dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result]
output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)]
change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)]
diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py
index 8fa36445cd..c3c3622cf9 100755
--- a/test/functional/rpc_getblockfilter.py
+++ b/test/functional/rpc_getblockfilter.py
@@ -7,7 +7,6 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal, assert_is_hex_string, assert_raises_rpc_error,
- connect_nodes, disconnect_nodes
)
FILTER_TYPES = ["basic"]
@@ -20,7 +19,7 @@ class GetBlockFilterTest(BitcoinTestFramework):
def run_test(self):
# Create two chains by disconnecting nodes 0 & 1, mining, then reconnecting
- disconnect_nodes(self.nodes[0], 1)
+ self.disconnect_nodes(0, 1)
self.nodes[0].generate(3)
self.nodes[1].generate(4)
@@ -29,7 +28,7 @@ class GetBlockFilterTest(BitcoinTestFramework):
chain0_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)]
# Reorg node 0 to a new chain
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_blocks()
assert_equal(self.nodes[0].getblockcount(), 4)
diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py
index 977dc805ef..ea064f9763 100755
--- a/test/functional/rpc_getdescriptorinfo.py
+++ b/test/functional/rpc_getdescriptorinfo.py
@@ -17,6 +17,7 @@ class DescriptorTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [["-disablewallet"]]
+ self.wallet_names = []
def test_desc(self, desc, isrange, issolvable, hasprivatekeys):
info = self.nodes[0].getdescriptorinfo(desc)
diff --git a/test/functional/rpc_getpeerinfo_deprecation.py b/test/functional/rpc_getpeerinfo_deprecation.py
index 287c40ae3e..340a66e12f 100755
--- a/test/functional/rpc_getpeerinfo_deprecation.py
+++ b/test/functional/rpc_getpeerinfo_deprecation.py
@@ -5,7 +5,6 @@
"""Test deprecation of getpeerinfo RPC fields."""
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import connect_nodes
class GetpeerinfoDeprecationTest(BitcoinTestFramework):
@@ -26,7 +25,7 @@ class GetpeerinfoDeprecationTest(BitcoinTestFramework):
def test_addnode_deprecation(self):
self.restart_node(1, ["-deprecatedrpc=getpeerinfo_addnode"])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.log.info("Test getpeerinfo by default no longer returns an addnode field")
assert "addnode" not in self.nodes[0].getpeerinfo()[0].keys()
diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py
index e788e75557..f884b8d293 100755
--- a/test/functional/rpc_invalidateblock.py
+++ b/test/functional/rpc_invalidateblock.py
@@ -8,7 +8,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR
from test_framework.util import (
assert_equal,
- connect_nodes,
)
@@ -32,7 +31,7 @@ class InvalidateTest(BitcoinTestFramework):
assert_equal(self.nodes[1].getblockcount(), 6)
self.log.info("Connect nodes to force a reorg")
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_blocks(self.nodes[0:2])
assert_equal(self.nodes[0].getblockcount(), 6)
badhash = self.nodes[1].getblockhash(2)
@@ -43,7 +42,7 @@ class InvalidateTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getbestblockhash(), besthash_n0)
self.log.info("Make sure we won't reorg to a lower work chain:")
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(1, 2)
self.log.info("Sync node 2 to node 1 so both have 6 blocks")
self.sync_blocks(self.nodes[1:3])
assert_equal(self.nodes[2].getblockcount(), 6)
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index b8a04f494d..2efd090733 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -21,10 +21,8 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
assert_equal,
- assert_greater_than_or_equal,
assert_greater_than,
assert_raises_rpc_error,
- connect_nodes,
p2p_port,
)
@@ -52,9 +50,13 @@ class NetTest(BitcoinTestFramework):
def run_test(self):
# Get out of IBD for the minfeefilter and getpeerinfo tests.
self.nodes[0].generate(101)
- # Connect nodes both ways.
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 0)
+
+ # By default, the test framework sets up an addnode connection from
+ # node 1 --> node0. By connecting node0 --> node 1, we're left with
+ # the two nodes being connected both ways.
+ # Topology will look like: node0 <--> node1
+ self.connect_nodes(0, 1)
+ self.sync_all()
self.test_connection_count()
self.test_getpeerinfo()
@@ -69,36 +71,52 @@ class NetTest(BitcoinTestFramework):
# After using `connect_nodes` to connect nodes 0 and 1 to each other.
assert_equal(self.nodes[0].getconnectioncount(), 2)
+ def test_getpeerinfo(self):
+ self.log.info("Test getpeerinfo")
+ # Create a few getpeerinfo last_block/last_transaction values.
+ if self.is_wallet_compiled():
+ self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
+ self.nodes[1].generate(1)
+ self.sync_all()
+ time_now = int(time.time())
+ peer_info = [x.getpeerinfo() for x in self.nodes]
+ # Verify last_block and last_transaction keys/values.
+ for node, peer, field in product(range(self.num_nodes), range(2), ['last_block', 'last_transaction']):
+ assert field in peer_info[node][peer].keys()
+ if peer_info[node][peer][field] != 0:
+ assert_approx(peer_info[node][peer][field], time_now, vspan=60)
+ # check both sides of bidirectional connection between nodes
+ # the address bound to on one side will be the source address for the other node
+ assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr'])
+ assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr'])
+ assert_equal(peer_info[0][0]['minfeefilter'], Decimal("0.00000500"))
+ assert_equal(peer_info[1][0]['minfeefilter'], Decimal("0.00001000"))
+ # check the `servicesnames` field
+ for info in peer_info:
+ assert_net_servicesnames(int(info[0]["services"], 0x10), info[0]["servicesnames"])
+
+ assert_equal(peer_info[0][0]['connection_type'], 'inbound')
+ assert_equal(peer_info[0][1]['connection_type'], 'manual')
+
+ assert_equal(peer_info[1][0]['connection_type'], 'manual')
+ assert_equal(peer_info[1][1]['connection_type'], 'inbound')
+
def test_getnettotals(self):
self.log.info("Test getnettotals")
- # getnettotals totalbytesrecv and totalbytessent should be
- # consistent with getpeerinfo. Since the RPC calls are not atomic,
- # and messages might have been recvd or sent between RPC calls, call
- # getnettotals before and after and verify that the returned values
- # from getpeerinfo are bounded by those values.
+ # Test getnettotals and getpeerinfo by doing a ping. The bytes
+ # sent/received should increase by at least the size of one ping (32
+ # bytes) and one pong (32 bytes).
net_totals_before = self.nodes[0].getnettotals()
- peer_info = self.nodes[0].getpeerinfo()
- net_totals_after = self.nodes[0].getnettotals()
- assert_equal(len(peer_info), 2)
- peers_recv = sum([peer['bytesrecv'] for peer in peer_info])
- peers_sent = sum([peer['bytessent'] for peer in peer_info])
-
- assert_greater_than_or_equal(peers_recv, net_totals_before['totalbytesrecv'])
- assert_greater_than_or_equal(net_totals_after['totalbytesrecv'], peers_recv)
- assert_greater_than_or_equal(peers_sent, net_totals_before['totalbytessent'])
- assert_greater_than_or_equal(net_totals_after['totalbytessent'], peers_sent)
-
- # test getnettotals and getpeerinfo by doing a ping
- # the bytes sent/received should change
- # note ping and pong are 32 bytes each
+ peer_info_before = self.nodes[0].getpeerinfo()
+
self.nodes[0].ping()
- self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytessent'] >= net_totals_after['totalbytessent'] + 32 * 2), timeout=1)
- self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytesrecv'] >= net_totals_after['totalbytesrecv'] + 32 * 2), timeout=1)
+ self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytessent'] >= net_totals_before['totalbytessent'] + 32 * 2), timeout=1)
+ self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytesrecv'] >= net_totals_before['totalbytesrecv'] + 32 * 2), timeout=1)
- peer_info_after_ping = self.nodes[0].getpeerinfo()
- for before, after in zip(peer_info, peer_info_after_ping):
- assert_greater_than_or_equal(after['bytesrecv_per_msg'].get('pong', 0), before['bytesrecv_per_msg'].get('pong', 0) + 32)
- assert_greater_than_or_equal(after['bytessent_per_msg'].get('ping', 0), before['bytessent_per_msg'].get('ping', 0) + 32)
+ for peer_before in peer_info_before:
+ peer_after = lambda: next(p for p in self.nodes[0].getpeerinfo() if p['id'] == peer_before['id'])
+ self.wait_until(lambda: peer_after()['bytesrecv_per_msg'].get('pong', 0) >= peer_before['bytesrecv_per_msg'].get('pong', 0) + 32, timeout=1)
+ self.wait_until(lambda: peer_after()['bytessent_per_msg'].get('ping', 0) >= peer_before['bytessent_per_msg'].get('ping', 0) + 32, timeout=1)
def test_getnetworkinfo(self):
self.log.info("Test getnetworkinfo")
@@ -117,8 +135,8 @@ class NetTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
self.nodes[0].setnetworkactive(state=True)
# Connect nodes both ways.
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 0)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 0)
info = self.nodes[0].getnetworkinfo()
assert_equal(info['networkactive'], True)
@@ -151,36 +169,6 @@ class NetTest(BitcoinTestFramework):
# check that a non-existent node returns an error
assert_raises_rpc_error(-24, "Node has not been added", self.nodes[0].getaddednodeinfo, '1.1.1.1')
- def test_getpeerinfo(self):
- self.log.info("Test getpeerinfo")
- # Create a few getpeerinfo last_block/last_transaction values.
- if self.is_wallet_compiled():
- self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
- self.nodes[1].generate(1)
- self.sync_all()
- time_now = int(time.time())
- peer_info = [x.getpeerinfo() for x in self.nodes]
- # Verify last_block and last_transaction keys/values.
- for node, peer, field in product(range(self.num_nodes), range(2), ['last_block', 'last_transaction']):
- assert field in peer_info[node][peer].keys()
- if peer_info[node][peer][field] != 0:
- assert_approx(peer_info[node][peer][field], time_now, vspan=60)
- # check both sides of bidirectional connection between nodes
- # the address bound to on one side will be the source address for the other node
- assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr'])
- assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr'])
- assert_equal(peer_info[0][0]['minfeefilter'], Decimal("0.00000500"))
- assert_equal(peer_info[1][0]['minfeefilter'], Decimal("0.00001000"))
- # check the `servicesnames` field
- for info in peer_info:
- assert_net_servicesnames(int(info[0]["services"], 0x10), info[0]["servicesnames"])
-
- assert_equal(peer_info[0][0]['connection_type'], 'inbound')
- assert_equal(peer_info[0][1]['connection_type'], 'manual')
-
- assert_equal(peer_info[1][0]['connection_type'], 'manual')
- assert_equal(peer_info[1][1]['connection_type'], 'inbound')
-
def test_service_flags(self):
self.log.info("Test service flags")
self.nodes[0].add_p2p_connection(P2PInterface(), services=(1 << 4) | (1 << 63))
diff --git a/test/functional/rpc_preciousblock.py b/test/functional/rpc_preciousblock.py
index 8386e47411..04d55b103f 100755
--- a/test/functional/rpc_preciousblock.py
+++ b/test/functional/rpc_preciousblock.py
@@ -7,7 +7,6 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
)
def unidirectional_node_sync_via_rpc(node_src, node_dest):
@@ -61,7 +60,7 @@ class PreciousTest(BitcoinTestFramework):
self.log.info("Connect nodes and check no reorg occurs")
# Submit competing blocks via RPC so any reorg should occur before we proceed (no way to wait on inaction for p2p sync)
node_sync_via_rpc(self.nodes[0:2])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
assert_equal(self.nodes[0].getbestblockhash(), hashC)
assert_equal(self.nodes[1].getbestblockhash(), hashG)
self.log.info("Make Node0 prefer block G")
@@ -98,8 +97,8 @@ class PreciousTest(BitcoinTestFramework):
hashL = self.nodes[2].getbestblockhash()
self.log.info("Connect nodes and check no reorg occurs")
node_sync_via_rpc(self.nodes[1:3])
- connect_nodes(self.nodes[1], 2)
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(1, 2)
+ self.connect_nodes(0, 2)
assert_equal(self.nodes[0].getbestblockhash(), hashH)
assert_equal(self.nodes[1].getbestblockhash(), hashH)
assert_equal(self.nodes[2].getbestblockhash(), hashL)
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 781a49dfac..5840801b00 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -12,8 +12,6 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
- connect_nodes,
- disconnect_nodes,
find_output,
)
@@ -30,7 +28,7 @@ class PSBTTest(BitcoinTestFramework):
self.num_nodes = 3
self.extra_args = [
["-walletrbf=1"],
- ["-walletrbf=0"],
+ ["-walletrbf=0", "-changetype=legacy"],
[]
]
self.supports_cli = False
@@ -46,7 +44,7 @@ class PSBTTest(BitcoinTestFramework):
# Disconnect offline node from others
# Topology of test network is linear, so this one call is enough
- disconnect_nodes(offline_node, 1)
+ self.disconnect_nodes(0, 1)
# Create watchonly on online_node
online_node.createwallet(wallet_name='wonline', disable_private_keys=True)
@@ -80,8 +78,16 @@ class PSBTTest(BitcoinTestFramework):
wonline.unloadwallet()
# Reconnect
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(0, 2)
+
+ def assert_change_type(self, psbtx, expected_type):
+ """Assert that the given PSBT has a change output with the given type."""
+
+ # The decodepsbt RPC is stateless and independent of any settings, we can always just call it on the first node
+ decoded_psbt = self.nodes[0].decodepsbt(psbtx["psbt"])
+ changepos = psbtx["changepos"]
+ assert_equal(decoded_psbt["tx"]["vout"][changepos]["scriptPubKey"]["type"], expected_type)
def run_test(self):
# Create and fund a raw tx for sending 10 BTC
@@ -166,8 +172,11 @@ class PSBTTest(BitcoinTestFramework):
elif out['scriptPubKey']['addresses'][0] == p2pkh:
p2pkh_pos = out['n']
+ inputs = [{"txid": txid, "vout": p2wpkh_pos}, {"txid": txid, "vout": p2sh_p2wpkh_pos}, {"txid": txid, "vout": p2pkh_pos}]
+ outputs = [{self.nodes[1].getnewaddress(): 29.99}]
+
# spend single key from node 1
- created_psbt = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99})
+ created_psbt = self.nodes[1].walletcreatefundedpsbt(inputs, outputs)
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(created_psbt['psbt'])
# Make sure it has both types of UTXOs
decoded = self.nodes[1].decodepsbt(walletprocesspsbt_out['psbt'])
@@ -178,15 +187,77 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
- # feeRate of 0.1 BTC / KB produces a total fee slightly below -maxtxfee (~0.05280000):
- res = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 0.1, "add_inputs": True})
- assert_approx(res["fee"], 0.055, 0.005)
-
- # feeRate of 10 BTC / KB produces a total fee well above -maxtxfee
+ self.log.info("Test walletcreatefundedpsbt fee rate of 10000 sat/vB and 0.1 BTC/kvB produces a total fee at or slightly below -maxtxfee (~0.05290000)")
+ res1 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 10000, "add_inputs": True})
+ assert_approx(res1["fee"], 0.055, 0.005)
+ res2 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, "add_inputs": True})
+ assert_approx(res2["fee"], 0.055, 0.005)
+
+ self.log.info("Test min fee rate checks with walletcreatefundedpsbt are bypassed, e.g. a fee_rate under 1 sat/vB is allowed")
+ res3 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 0.99999999, "add_inputs": True})
+ assert_approx(res3["fee"], 0.00000381, 0.0000001)
+ res4 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.00000999, "add_inputs": True})
+ assert_approx(res4["fee"], 0.00000381, 0.0000001)
+
+ self.log.info("Test min fee rate checks with walletcreatefundedpsbt are bypassed and that funding non-standard 'zero-fee' transactions is valid")
+ for param in ["fee_rate", "feeRate"]:
+ assert_equal(self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {param: 0, "add_inputs": True})["fee"], 0)
+
+ self.log.info("Test invalid fee rate settings")
+ for param, value in {("fee_rate", 100000), ("feeRate", 1)}:
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: value, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: -1, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Amount is not a number or string",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: {"foo": "bar"}, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Invalid amount",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: "", "add_inputs": True})
+
+ self.log.info("- raises RPC error if both feeRate and fee_rate are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both fee_rate (sat/vB) and feeRate (BTC/kvB)",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": 0.1, "feeRate": 0.1, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both feeRate and estimate_mode passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and feeRate",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": "economical", "feeRate": 0.1, "add_inputs": True})
+
+ for param in ["feeRate", "fee_rate"]:
+ self.log.info("- raises RPC error if both {} and conf_target are passed".format(param))
+ assert_raises_rpc_error(-8, "Cannot specify both conf_target and {}. Please provide either a confirmation "
+ "target in blocks for automatic fee estimation, or an explicit fee rate.".format(param),
+ self.nodes[1].walletcreatefundedpsbt ,inputs, outputs, 0, {param: 1, "conf_target": 1, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both fee_rate and estimate_mode are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and fee_rate",
+ self.nodes[1].walletcreatefundedpsbt ,inputs, outputs, 0, {"fee_rate": 1, "estimate_mode": "economical", "add_inputs": True})
+
+ self.log.info("- raises RPC error with invalid estimate_mode settings")
+ for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True})
+ for mode in ["", "foo", Decimal("3.141592")]:
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True})
+
+ self.log.info("- raises RPC error with invalid conf_target settings")
+ for mode in ["unset", "economical", "conservative"]:
+ self.log.debug("{}".format(mode))
+ for k, v in {"string": "", "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
+ for n in [-1, 0, 1009]:
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": n, "add_inputs": True})
+
+ self.log.info("Test walletcreatefundedpsbt with too-high fee rate produces total fee well above -maxtxfee and raises RPC error")
# previously this was silently capped at -maxtxfee
- assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10, "add_inputs": True})
- assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():1}, 0, {"feeRate": 10, "add_inputs": False})
+ for bool_add, outputs_array in {True: outputs, False: [{self.nodes[1].getnewaddress(): 1}]}.items():
+ msg = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"
+ assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"fee_rate": 1000000, "add_inputs": bool_add})
+ assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"feeRate": 1, "add_inputs": bool_add})
+ self.log.info("Test various PSBT operations")
# partially sign multisig things with node 1
psbtx = wmulti.walletcreatefundedpsbt(inputs=[{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], outputs={self.nodes[1].getnewaddress():29.99}, options={'changeAddress': self.nodes[1].getrawchangeaddress()})['psbt']
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
@@ -301,6 +372,21 @@ class PSBTTest(BitcoinTestFramework):
# when attempting BnB coin selection
self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False)
+ # Make sure the wallet's change type is respected by default
+ small_output = {self.nodes[0].getnewaddress():0.1}
+ psbtx_native = self.nodes[0].walletcreatefundedpsbt([], [small_output])
+ self.assert_change_type(psbtx_native, "witness_v0_keyhash")
+ psbtx_legacy = self.nodes[1].walletcreatefundedpsbt([], [small_output])
+ self.assert_change_type(psbtx_legacy, "pubkeyhash")
+
+ # Make sure the change type of the wallet can also be overwritten
+ psbtx_np2wkh = self.nodes[1].walletcreatefundedpsbt([], [small_output], 0, {"change_type":"p2sh-segwit"})
+ self.assert_change_type(psbtx_np2wkh, "scripthash")
+
+ # Make sure the change type cannot be specified if a change address is given
+ invalid_options = {"change_type":"legacy","changeAddress":self.nodes[0].getnewaddress()}
+ assert_raises_rpc_error(-8, "both change address and address type options", self.nodes[0].walletcreatefundedpsbt, [], [small_output], 0, invalid_options)
+
# Regression test for 14473 (mishandling of already-signed witness transaction):
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], 0, {"add_inputs": True})
complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"])
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 23b5e647d6..554c30c0d2 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -20,7 +20,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
+ find_vout_for_address,
hex_str_to_bytes,
)
@@ -60,7 +60,7 @@ class RawTransactionsTest(BitcoinTestFramework):
def setup_network(self):
super().setup_network()
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 2)
def run_test(self):
self.log.info('prepare some coins for multiple *rawtransaction commands')
@@ -96,7 +96,7 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "txid must be hexadecimal string (not 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844')", self.nodes[0].createrawtransaction, [{'txid': 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844'}], {})
assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': txid}], {})
assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': 'foo'}], {})
- assert_raises_rpc_error(-8, "Invalid parameter, vout must be positive", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': -1}], {})
+ assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': -1}], {})
assert_raises_rpc_error(-8, "Invalid parameter, sequence number is out of range", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': 0, 'sequence': -1}], {})
# Test `createrawtransaction` invalid `outputs`
@@ -243,121 +243,124 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[0].reconsiderblock(block1)
assert_equal(self.nodes[0].getbestblockhash(), block2)
- #########################
- # RAW TX MULTISIG TESTS #
- #########################
- # 2of2 test
- addr1 = self.nodes[2].getnewaddress()
- addr2 = self.nodes[2].getnewaddress()
-
- addr1Obj = self.nodes[2].getaddressinfo(addr1)
- addr2Obj = self.nodes[2].getaddressinfo(addr2)
-
- # Tests for createmultisig and addmultisigaddress
- assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"])
- self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) # createmultisig can only take public keys
- assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1]) # addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here.
-
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
-
- #use balance deltas instead of absolute values
- bal = self.nodes[2].getbalance()
-
- # send 1.2 BTC to msig adr
- txId = self.nodes[0].sendtoaddress(mSigObj, 1.2)
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
- assert_equal(self.nodes[2].getbalance(), bal+Decimal('1.20000000')) #node2 has both keys of the 2of2 ms addr., tx should affect the balance
-
-
- # 2of3 test from different nodes
- bal = self.nodes[2].getbalance()
- addr1 = self.nodes[1].getnewaddress()
- addr2 = self.nodes[2].getnewaddress()
- addr3 = self.nodes[2].getnewaddress()
-
- addr1Obj = self.nodes[1].getaddressinfo(addr1)
- addr2Obj = self.nodes[2].getaddressinfo(addr2)
- addr3Obj = self.nodes[2].getaddressinfo(addr3)
-
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address']
-
- txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
- decTx = self.nodes[0].gettransaction(txId)
- rawTx = self.nodes[0].decoderawtransaction(decTx['hex'])
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
-
- #THIS IS AN INCOMPLETE FEATURE
- #NODE2 HAS TWO OF THREE KEY AND THE FUNDS SHOULD BE SPENDABLE AND COUNT AT BALANCE CALCULATION
- assert_equal(self.nodes[2].getbalance(), bal) #for now, assume the funds of a 2of3 multisig tx are not marked as spendable
-
- txDetails = self.nodes[0].gettransaction(txId, True)
- rawTx = self.nodes[0].decoderawtransaction(txDetails['hex'])
- vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000'))
-
- bal = self.nodes[0].getbalance()
- inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "amount" : vout['value']}]
- outputs = { self.nodes[0].getnewaddress() : 2.19 }
- rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
- rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet(rawTx, inputs)
- assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx
-
- rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs)
- assert_equal(rawTxSigned['complete'], True) #node2 can sign the tx compl., own two of three keys
- self.nodes[2].sendrawtransaction(rawTxSigned['hex'])
- rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex'])
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
- assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
-
- # 2of2 test for combining transactions
- bal = self.nodes[2].getbalance()
- addr1 = self.nodes[1].getnewaddress()
- addr2 = self.nodes[2].getnewaddress()
-
- addr1Obj = self.nodes[1].getaddressinfo(addr1)
- addr2Obj = self.nodes[2].getaddressinfo(addr2)
-
- self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
- mSigObjValid = self.nodes[2].getaddressinfo(mSigObj)
-
- txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
- decTx = self.nodes[0].gettransaction(txId)
- rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex'])
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
-
- assert_equal(self.nodes[2].getbalance(), bal) # the funds of a 2of2 multisig tx should not be marked as spendable
-
- txDetails = self.nodes[0].gettransaction(txId, True)
- rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex'])
- vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000'))
-
- bal = self.nodes[0].getbalance()
- inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex'], "amount" : vout['value']}]
- outputs = { self.nodes[0].getnewaddress() : 2.19 }
- rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs)
- rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs)
- self.log.debug(rawTxPartialSigned1)
- assert_equal(rawTxPartialSigned1['complete'], False) #node1 only has one key, can't comp. sign the tx
-
- 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
- rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
- self.log.debug(rawTxComb)
- self.nodes[2].sendrawtransaction(rawTxComb)
- rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb)
- self.sync_all()
- self.nodes[0].generate(1)
- self.sync_all()
- assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
+ if not self.options.descriptors:
+ # The traditional multisig workflow does not work with descriptor wallets so these are legacy only.
+ # The multisig workflow with descriptor wallets uses PSBTs and is tested elsewhere, no need to do them here.
+ #########################
+ # RAW TX MULTISIG TESTS #
+ #########################
+ # 2of2 test
+ addr1 = self.nodes[2].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[2].getaddressinfo(addr1)
+ addr2Obj = self.nodes[2].getaddressinfo(addr2)
+
+ # Tests for createmultisig and addmultisigaddress
+ assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"])
+ self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) # createmultisig can only take public keys
+ assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1]) # addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here.
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
+
+ #use balance deltas instead of absolute values
+ bal = self.nodes[2].getbalance()
+
+ # send 1.2 BTC to msig adr
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.2)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(self.nodes[2].getbalance(), bal+Decimal('1.20000000')) #node2 has both keys of the 2of2 ms addr., tx should affect the balance
+
+
+ # 2of3 test from different nodes
+ bal = self.nodes[2].getbalance()
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+ addr3 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[1].getaddressinfo(addr1)
+ addr2Obj = self.nodes[2].getaddressinfo(addr2)
+ addr3Obj = self.nodes[2].getaddressinfo(addr3)
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address']
+
+ txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
+ decTx = self.nodes[0].gettransaction(txId)
+ rawTx = self.nodes[0].decoderawtransaction(decTx['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ #THIS IS AN INCOMPLETE FEATURE
+ #NODE2 HAS TWO OF THREE KEY AND THE FUNDS SHOULD BE SPENDABLE AND COUNT AT BALANCE CALCULATION
+ assert_equal(self.nodes[2].getbalance(), bal) #for now, assume the funds of a 2of3 multisig tx are not marked as spendable
+
+ txDetails = self.nodes[0].gettransaction(txId, True)
+ rawTx = self.nodes[0].decoderawtransaction(txDetails['hex'])
+ vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000'))
+
+ bal = self.nodes[0].getbalance()
+ inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "amount" : vout['value']}]
+ outputs = { self.nodes[0].getnewaddress() : 2.19 }
+ rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
+ rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet(rawTx, inputs)
+ assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx
+
+ rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs)
+ assert_equal(rawTxSigned['complete'], True) #node2 can sign the tx compl., own two of three keys
+ self.nodes[2].sendrawtransaction(rawTxSigned['hex'])
+ rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
+
+ # 2of2 test for combining transactions
+ bal = self.nodes[2].getbalance()
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[1].getaddressinfo(addr1)
+ addr2Obj = self.nodes[2].getaddressinfo(addr2)
+
+ self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
+ mSigObjValid = self.nodes[2].getaddressinfo(mSigObj)
+
+ txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
+ decTx = self.nodes[0].gettransaction(txId)
+ rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ assert_equal(self.nodes[2].getbalance(), bal) # the funds of a 2of2 multisig tx should not be marked as spendable
+
+ txDetails = self.nodes[0].gettransaction(txId, True)
+ rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex'])
+ vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000'))
+
+ bal = self.nodes[0].getbalance()
+ inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex'], "amount" : vout['value']}]
+ outputs = { self.nodes[0].getnewaddress() : 2.19 }
+ rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs)
+ rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs)
+ self.log.debug(rawTxPartialSigned1)
+ assert_equal(rawTxPartialSigned1['complete'], False) #node1 only has one key, can't comp. sign the tx
+
+ 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
+ rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']])
+ self.log.debug(rawTxComb)
+ self.nodes[2].sendrawtransaction(rawTxComb)
+ rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb)
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(self.nodes[0].getbalance(), bal+Decimal('50.00000000')+Decimal('2.19000000')) #block reward + tx
# decoderawtransaction tests
# witness transaction
@@ -370,9 +373,20 @@ class RawTransactionsTest(BitcoinTestFramework):
decrawtx = self.nodes[0].decoderawtransaction(encrawtx, False) # decode as non-witness transaction
assert_equal(decrawtx['vout'][0]['value'], Decimal('1.00000000'))
+ # Basic signrawtransaction test
+ addr = self.nodes[1].getnewaddress()
+ txid = self.nodes[0].sendtoaddress(addr, 10)
+ self.nodes[0].generate(1)
+ self.sync_all()
+ vout = find_vout_for_address(self.nodes[1], txid, addr)
+ rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999})
+ rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx)
+ txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex'])
+ self.nodes[0].generate(1)
+ self.sync_all()
+
# getrawtransaction tests
# 1. valid parameters - only supply txid
- txId = rawTx["txid"]
assert_equal(self.nodes[0].getrawtransaction(txId), rawTxSigned['hex'])
# 2. valid parameters - supply txid and 0 for non-verbose
@@ -456,9 +470,9 @@ class RawTransactionsTest(BitcoinTestFramework):
# Thus, testmempoolaccept should reject
testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']], 0.00001000)[0]
assert_equal(testres['allowed'], False)
- assert_equal(testres['reject-reason'], 'absurdly-high-fee')
+ assert_equal(testres['reject-reason'], 'max-fee-exceeded')
# and sendrawtransaction should throw
- assert_raises_rpc_error(-26, "absurdly-high-fee", self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000)
+ assert_raises_rpc_error(-25, 'Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)', self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000)
# and the following calls should both succeed
testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']])[0]
assert_equal(testres['allowed'], True)
@@ -480,9 +494,9 @@ class RawTransactionsTest(BitcoinTestFramework):
# Thus, testmempoolaccept should reject
testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']])[0]
assert_equal(testres['allowed'], False)
- assert_equal(testres['reject-reason'], 'absurdly-high-fee')
+ assert_equal(testres['reject-reason'], 'max-fee-exceeded')
# and sendrawtransaction should throw
- assert_raises_rpc_error(-26, "absurdly-high-fee", self.nodes[2].sendrawtransaction, rawTxSigned['hex'])
+ assert_raises_rpc_error(-25, 'Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)', self.nodes[2].sendrawtransaction, rawTxSigned['hex'])
# and the following calls should both succeed
testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate='0.20000000')[0]
assert_equal(testres['allowed'], True)
diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py
index 861b394e70..070f59d314 100755
--- a/test/functional/rpc_scantxoutset.py
+++ b/test/functional/rpc_scantxoutset.py
@@ -55,7 +55,8 @@ class ScantxoutsetTest(BitcoinTestFramework):
self.log.info("Stop node, remove wallet, mine again some blocks...")
self.stop_node(0)
shutil.rmtree(os.path.join(self.nodes[0].datadir, self.chain, 'wallets'))
- self.start_node(0)
+ self.start_node(0, ['-nowallet'])
+ self.import_deterministic_coinbase_privkeys()
self.nodes[0].generate(110)
scan = self.nodes[0].scantxoutset("start", [])
diff --git a/test/functional/rpc_setban.py b/test/functional/rpc_setban.py
index 1cc1fb164b..bc48449084 100755
--- a/test/functional/rpc_setban.py
+++ b/test/functional/rpc_setban.py
@@ -6,7 +6,6 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
- connect_nodes,
p2p_port
)
@@ -18,7 +17,7 @@ class SetBanTests(BitcoinTestFramework):
def run_test(self):
# Node 0 connects to Node 1, check that the noban permission is not granted
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
peerinfo = self.nodes[1].getpeerinfo()[0]
assert(not 'noban' in peerinfo['permissions'])
@@ -32,14 +31,14 @@ class SetBanTests(BitcoinTestFramework):
# However, node 0 should be able to reconnect if it has noban permission
self.restart_node(1, ['-whitelist=127.0.0.1'])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
peerinfo = self.nodes[1].getpeerinfo()[0]
assert('noban' in peerinfo['permissions'])
# If we remove the ban, Node 0 should be able to reconnect even without noban permission
self.nodes[1].setban("127.0.0.1", "remove")
self.restart_node(1, [])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
peerinfo = self.nodes[1].getpeerinfo()[0]
assert(not 'noban' in peerinfo['permissions'])
diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py
index 704b65c060..2fbbdbbdf0 100755
--- a/test/functional/rpc_signrawtransaction.py
+++ b/test/functional/rpc_signrawtransaction.py
@@ -5,10 +5,13 @@
"""Test transaction signing using the signrawtransaction* RPCs."""
from test_framework.address import check_script, script_to_p2sh
+from test_framework.key import ECKey
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, find_vout_for_address, hex_str_to_bytes
from test_framework.messages import sha256
from test_framework.script import CScript, OP_0, OP_CHECKSIG
+from test_framework.script_util import key_to_p2pkh_script, script_to_p2sh_p2wsh_script, script_to_p2wsh_script
+from test_framework.wallet_util import bytes_to_wif
from decimal import Decimal
@@ -148,24 +151,40 @@ class SignRawTransactionsTest(BitcoinTestFramework):
assert_equal(rawTxSigned['errors'][1]['witness'], ["304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01", "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"])
assert not rawTxSigned['errors'][0]['witness']
+ def test_fully_signed_tx(self):
+ self.log.info("Test signing a fully signed transaction does nothing")
+ self.nodes[0].walletpassphrase("password", 9999)
+ self.nodes[0].generate(101)
+ rawtx = self.nodes[0].createrawtransaction([], [{self.nodes[0].getnewaddress(): 10}])
+ fundedtx = self.nodes[0].fundrawtransaction(rawtx)
+ signedtx = self.nodes[0].signrawtransactionwithwallet(fundedtx["hex"])
+ assert_equal(signedtx["complete"], True)
+ signedtx2 = self.nodes[0].signrawtransactionwithwallet(signedtx["hex"])
+ assert_equal(signedtx2["complete"], True)
+ assert_equal(signedtx["hex"], signedtx2["hex"])
+ self.nodes[0].walletlock()
+
def witness_script_test(self):
self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet")
# Create a new P2SH-P2WSH 1-of-1 multisig address:
- embedded_address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())
- embedded_privkey = self.nodes[1].dumpprivkey(embedded_address["address"])
- p2sh_p2wsh_address = self.nodes[1].addmultisigaddress(1, [embedded_address["pubkey"]], "", "p2sh-segwit")
+ eckey = ECKey()
+ eckey.generate()
+ embedded_privkey = bytes_to_wif(eckey.get_bytes())
+ embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
+ p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit")
# send transaction to P2SH-P2WSH 1-of-1 multisig address
self.nodes[0].generate(101)
self.nodes[0].sendtoaddress(p2sh_p2wsh_address["address"], 49.999)
self.nodes[0].generate(1)
self.sync_all()
- # Find the UTXO for the transaction node[1] should have received, check witnessScript matches
- unspent_output = self.nodes[1].listunspent(0, 999999, [p2sh_p2wsh_address["address"]])[0]
- assert_equal(unspent_output["witnessScript"], p2sh_p2wsh_address["redeemScript"])
- p2sh_redeemScript = CScript([OP_0, sha256(hex_str_to_bytes(p2sh_p2wsh_address["redeemScript"]))])
- assert_equal(unspent_output["redeemScript"], p2sh_redeemScript.hex())
+ # Get the UTXO info from scantxoutset
+ unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0]
+ spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex()
+ unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript']
+ unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex()
+ assert_equal(spk, unspent_output['scriptPubKey'])
# Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
- spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].getnewaddress(): Decimal("49.998")})
+ spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].get_wallet_rpc(self.default_wallet_name).getnewaddress(): Decimal("49.998")})
spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output])
# Check the signing completed successfully
assert 'complete' in spending_tx_signed
@@ -177,11 +196,13 @@ class SignRawTransactionsTest(BitcoinTestFramework):
def verify_txn_with_witness_script(self, tx_type):
self.log.info("Test with a {} script as the witnessScript".format(tx_type))
- embedded_addr_info = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress('', 'legacy'))
- embedded_privkey = self.nodes[1].dumpprivkey(embedded_addr_info['address'])
+ eckey = ECKey()
+ eckey.generate()
+ embedded_privkey = bytes_to_wif(eckey.get_bytes())
+ embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
witness_script = {
- 'P2PKH': embedded_addr_info['scriptPubKey'],
- 'P2PK': CScript([hex_str_to_bytes(embedded_addr_info['pubkey']), OP_CHECKSIG]).hex()
+ 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(),
+ 'P2PK': CScript([hex_str_to_bytes(embedded_pubkey), OP_CHECKSIG]).hex()
}.get(tx_type, "Invalid tx_type")
redeem_script = CScript([OP_0, sha256(check_script(witness_script))]).hex()
addr = script_to_p2sh(redeem_script)
@@ -223,6 +244,7 @@ class SignRawTransactionsTest(BitcoinTestFramework):
self.witness_script_test()
self.OP_1NEGATE_test()
self.test_with_lock_outputs()
+ self.test_fully_signed_tx()
if __name__ == '__main__':
diff --git a/test/functional/rpc_txoutproof.py b/test/functional/rpc_txoutproof.py
index 93fb62c5d6..2d6ce77613 100755
--- a/test/functional/rpc_txoutproof.py
+++ b/test/functional/rpc_txoutproof.py
@@ -78,7 +78,7 @@ class MerkleBlockTest(BitcoinTestFramework):
# We can't get a proof if we specify transactions from different blocks
assert_raises_rpc_error(-5, "Not all transactions found in specified or retrieved block", self.nodes[0].gettxoutproof, [txid1, txid3])
# Test empty list
- assert_raises_rpc_error(-5, "Transaction not yet in block", self.nodes[0].gettxoutproof, [])
+ assert_raises_rpc_error(-8, "Parameter 'txids' cannot be empty", self.nodes[0].gettxoutproof, [])
# Test duplicate txid
assert_raises_rpc_error(-8, 'Invalid parameter, duplicated txid', self.nodes[0].gettxoutproof, [txid1, txid1])
diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py
index daf02fc4f3..108af2cac8 100755
--- a/test/functional/rpc_users.py
+++ b/test/functional/rpc_users.py
@@ -99,11 +99,18 @@ class HTTPBasicsTest(BitcoinTestFramework):
self.test_auth(self.nodes[1], self.rpcuser, self.rpcpassword)
- self.log.info('Check that failure to write cookie file will abort the node gracefully')
+ init_error = 'Error: Unable to start HTTP server. See debug log for details.'
+
+ self.log.info('Check -rpcauth are validated')
+ # Empty -rpcauth= are ignored
+ self.restart_node(0, extra_args=['-rpcauth='])
self.stop_node(0)
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo'])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar'])
+
+ self.log.info('Check that failure to write cookie file will abort the node gracefully')
cookie_file = os.path.join(get_datadir_path(self.options.tmpdir, 0), self.chain, '.cookie.tmp')
os.mkdir(cookie_file)
- init_error = 'Error: Unable to start HTTP server. See debug log for details.'
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error)
diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py
index 9506b63f82..360962b8da 100644
--- a/test/functional/test_framework/address.py
+++ b/test/functional/test_framework/address.py
@@ -2,17 +2,17 @@
# Copyright (c) 2016-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-"""Encode and decode BASE58, P2PKH and P2SH addresses."""
+"""Encode and decode Bitcoin addresses.
+
+- base58 P2PKH and P2SH addresses.
+- bech32 segwit v0 P2WPKH and P2WSH addresses."""
import enum
import unittest
from .script import hash256, hash160, sha256, CScript, OP_0
-from .util import hex_str_to_bytes
-
-from . import segwit_addr
-
-from test_framework.util import assert_equal
+from .segwit_addr import encode_segwit_address
+from .util import assert_equal, hex_str_to_bytes
ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj'
ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR = 'addr(bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj)#juyq9d97'
@@ -35,7 +35,7 @@ def byte_to_base58(b, version):
str = chr(version).encode('latin-1').hex() + str
checksum = hash256(hex_str_to_bytes(str)).hex()
str += checksum[:8]
- value = int('0x'+str,0)
+ value = int('0x' + str, 0)
while value > 0:
result = chars[value % 58] + result
value //= 58
@@ -45,7 +45,10 @@ def byte_to_base58(b, version):
return result
-def base58_to_byte(s, verify_checksum=True):
+def base58_to_byte(s):
+ """Converts a base58-encoded string to its data and version.
+
+ Throws if the base58 checksum is invalid."""
if not s:
return b''
n = 0
@@ -65,66 +68,67 @@ def base58_to_byte(s, verify_checksum=True):
else:
break
res = b'\x00' * pad + res
- if verify_checksum:
- assert_equal(hash256(res[:-4])[:4], res[-4:])
+
+ # Assert if the checksum is invalid
+ assert_equal(hash256(res[:-4])[:4], res[-4:])
return res[1:-4], int(res[0])
-def keyhash_to_p2pkh(hash, main = False):
+def keyhash_to_p2pkh(hash, main=False):
assert len(hash) == 20
version = 0 if main else 111
return byte_to_base58(hash, version)
-def scripthash_to_p2sh(hash, main = False):
+def scripthash_to_p2sh(hash, main=False):
assert len(hash) == 20
version = 5 if main else 196
return byte_to_base58(hash, version)
-def key_to_p2pkh(key, main = False):
+def key_to_p2pkh(key, main=False):
key = check_key(key)
return keyhash_to_p2pkh(hash160(key), main)
-def script_to_p2sh(script, main = False):
+def script_to_p2sh(script, main=False):
script = check_script(script)
return scripthash_to_p2sh(hash160(script), main)
-def key_to_p2sh_p2wpkh(key, main = False):
+def key_to_p2sh_p2wpkh(key, main=False):
key = check_key(key)
p2shscript = CScript([OP_0, hash160(key)])
return script_to_p2sh(p2shscript, main)
-def program_to_witness(version, program, main = False):
+def program_to_witness(version, program, main=False):
if (type(program) is str):
program = hex_str_to_bytes(program)
assert 0 <= version <= 16
assert 2 <= len(program) <= 40
assert version > 0 or len(program) in [20, 32]
- return segwit_addr.encode("bc" if main else "bcrt", version, program)
+ return encode_segwit_address("bc" if main else "bcrt", version, program)
-def script_to_p2wsh(script, main = False):
+def script_to_p2wsh(script, main=False):
script = check_script(script)
return program_to_witness(0, sha256(script), main)
-def key_to_p2wpkh(key, main = False):
+def key_to_p2wpkh(key, main=False):
key = check_key(key)
return program_to_witness(0, hash160(key), main)
-def script_to_p2sh_p2wsh(script, main = False):
+def script_to_p2sh_p2wsh(script, main=False):
script = check_script(script)
p2shscript = CScript([OP_0, sha256(script)])
return script_to_p2sh(p2shscript, main)
def check_key(key):
if (type(key) is str):
- key = hex_str_to_bytes(key) # Assuming this is hex string
+ key = hex_str_to_bytes(key) # Assuming this is hex string
if (type(key) is bytes and (len(key) == 33 or len(key) == 65)):
return key
assert False
def check_script(script):
if (type(script) is str):
- script = hex_str_to_bytes(script) # Assuming this is hex string
+ script = hex_str_to_bytes(script) # Assuming this is hex string
if (type(script) is bytes or type(script) is CScript):
return script
assert False
@@ -135,15 +139,15 @@ class TestFrameworkScript(unittest.TestCase):
def check_base58(data, version):
self.assertEqual(base58_to_byte(byte_to_base58(data, version)), (data, version))
- check_base58(b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 111)
- check_base58(b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 111)
- check_base58(b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111)
- check_base58(b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111)
- check_base58(b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111)
- check_base58(b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111)
- check_base58(b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 0)
- check_base58(b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 0)
- check_base58(b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0)
- check_base58(b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0)
- check_base58(b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0)
- check_base58(b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0)
+ check_base58(bytes.fromhex('1f8ea1702a7bd4941bca0941b852c4bbfedb2e05'), 111)
+ check_base58(bytes.fromhex('3a0b05f4d7f66c3ba7009f453530296c845cc9cf'), 111)
+ check_base58(bytes.fromhex('41c1eaf111802559bad61b60d62b1f897c63928a'), 111)
+ check_base58(bytes.fromhex('0041c1eaf111802559bad61b60d62b1f897c63928a'), 111)
+ check_base58(bytes.fromhex('000041c1eaf111802559bad61b60d62b1f897c63928a'), 111)
+ check_base58(bytes.fromhex('00000041c1eaf111802559bad61b60d62b1f897c63928a'), 111)
+ check_base58(bytes.fromhex('1f8ea1702a7bd4941bca0941b852c4bbfedb2e05'), 0)
+ check_base58(bytes.fromhex('3a0b05f4d7f66c3ba7009f453530296c845cc9cf'), 0)
+ check_base58(bytes.fromhex('41c1eaf111802559bad61b60d62b1f897c63928a'), 0)
+ check_base58(bytes.fromhex('0041c1eaf111802559bad61b60d62b1f897c63928a'), 0)
+ check_base58(bytes.fromhex('000041c1eaf111802559bad61b60d62b1f897c63928a'), 0)
+ check_base58(bytes.fromhex('00000041c1eaf111802559bad61b60d62b1f897c63928a'), 0)
diff --git a/test/functional/test_framework/bdb.py b/test/functional/test_framework/bdb.py
new file mode 100644
index 0000000000..9de358aa0a
--- /dev/null
+++ b/test/functional/test_framework/bdb.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""
+Utilities for working directly with the wallet's BDB database file
+
+This is specific to the configuration of BDB used in this project:
+ - pagesize: 4096 bytes
+ - Outer database contains single subdatabase named 'main'
+ - btree
+ - btree leaf pages
+
+Each key-value pair is two entries in a btree leaf. The first is the key, the one that follows
+is the value. And so on. Note that the entry data is itself not in the correct order. Instead
+entry offsets are stored in the correct order and those offsets are needed to then retrieve
+the data itself.
+
+Page format can be found in BDB source code dbinc/db_page.h
+This only implements the deserialization of btree metadata pages and normal btree pages. Overflow
+pages are not implemented but may be needed in the future if dealing with wallets with large
+transactions.
+
+`db_dump -da wallet.dat` is useful to see the data in a wallet.dat BDB file
+"""
+
+import binascii
+import struct
+
+# Important constants
+PAGESIZE = 4096
+OUTER_META_PAGE = 0
+INNER_META_PAGE = 2
+
+# Page type values
+BTREE_INTERNAL = 3
+BTREE_LEAF = 5
+BTREE_META = 9
+
+# Some magic numbers for sanity checking
+BTREE_MAGIC = 0x053162
+DB_VERSION = 9
+
+# Deserializes a leaf page into a dict.
+# Btree internal pages have the same header, for those, return None.
+# For the btree leaf pages, deserialize them and put all the data into a dict
+def dump_leaf_page(data):
+ page_info = {}
+ page_header = data[0:26]
+ _, pgno, prev_pgno, next_pgno, entries, hf_offset, level, pg_type = struct.unpack('QIIIHHBB', page_header)
+ page_info['pgno'] = pgno
+ page_info['prev_pgno'] = prev_pgno
+ page_info['next_pgno'] = next_pgno
+ page_info['entries'] = entries
+ page_info['hf_offset'] = hf_offset
+ page_info['level'] = level
+ page_info['pg_type'] = pg_type
+ page_info['entry_offsets'] = struct.unpack('{}H'.format(entries), data[26:26 + entries * 2])
+ page_info['entries'] = []
+
+ if pg_type == BTREE_INTERNAL:
+ # Skip internal pages. These are the internal nodes of the btree and don't contain anything relevant to us
+ return None
+
+ assert pg_type == BTREE_LEAF, 'A non-btree leaf page has been encountered while dumping leaves'
+
+ for i in range(0, entries):
+ offset = page_info['entry_offsets'][i]
+ entry = {'offset': offset}
+ page_data_header = data[offset:offset + 3]
+ e_len, pg_type = struct.unpack('HB', page_data_header)
+ entry['len'] = e_len
+ entry['pg_type'] = pg_type
+ entry['data'] = data[offset + 3:offset + 3 + e_len]
+ page_info['entries'].append(entry)
+
+ return page_info
+
+# Deserializes a btree metadata page into a dict.
+# Does a simple sanity check on the magic value, type, and version
+def dump_meta_page(page):
+ # metadata page
+ # general metadata
+ metadata = {}
+ meta_page = page[0:72]
+ _, pgno, magic, version, pagesize, encrypt_alg, pg_type, metaflags, _, free, last_pgno, nparts, key_count, record_count, flags, uid = struct.unpack('QIIIIBBBBIIIIII20s', meta_page)
+ metadata['pgno'] = pgno
+ metadata['magic'] = magic
+ metadata['version'] = version
+ metadata['pagesize'] = pagesize
+ metadata['encrypt_alg'] = encrypt_alg
+ metadata['pg_type'] = pg_type
+ metadata['metaflags'] = metaflags
+ metadata['free'] = free
+ metadata['last_pgno'] = last_pgno
+ metadata['nparts'] = nparts
+ metadata['key_count'] = key_count
+ metadata['record_count'] = record_count
+ metadata['flags'] = flags
+ metadata['uid'] = binascii.hexlify(uid)
+
+ assert magic == BTREE_MAGIC, 'bdb magic does not match bdb btree magic'
+ assert pg_type == BTREE_META, 'Metadata page is not a btree metadata page'
+ assert version == DB_VERSION, 'Database too new'
+
+ # btree metadata
+ btree_meta_page = page[72:512]
+ _, minkey, re_len, re_pad, root, _, crypto_magic, _, iv, chksum = struct.unpack('IIIII368sI12s16s20s', btree_meta_page)
+ metadata['minkey'] = minkey
+ metadata['re_len'] = re_len
+ metadata['re_pad'] = re_pad
+ metadata['root'] = root
+ metadata['crypto_magic'] = crypto_magic
+ metadata['iv'] = binascii.hexlify(iv)
+ metadata['chksum'] = binascii.hexlify(chksum)
+ return metadata
+
+# Given the dict from dump_leaf_page, get the key-value pairs and put them into a dict
+def extract_kv_pairs(page_data):
+ out = {}
+ last_key = None
+ for i, entry in enumerate(page_data['entries']):
+ # By virtue of these all being pairs, even number entries are keys, and odd are values
+ if i % 2 == 0:
+ out[entry['data']] = b''
+ last_key = entry['data']
+ else:
+ out[last_key] = entry['data']
+ return out
+
+# Extract the key-value pairs of the BDB file given in filename
+def dump_bdb_kv(filename):
+ # Read in the BDB file and start deserializing it
+ pages = []
+ with open(filename, 'rb') as f:
+ data = f.read(PAGESIZE)
+ while len(data) > 0:
+ pages.append(data)
+ data = f.read(PAGESIZE)
+
+ # Sanity check the meta pages
+ dump_meta_page(pages[OUTER_META_PAGE])
+ dump_meta_page(pages[INNER_META_PAGE])
+
+ # Fetch the kv pairs from the leaf pages
+ kv = {}
+ for i in range(3, len(pages)):
+ info = dump_leaf_page(pages[i])
+ if info is not None:
+ info_kv = extract_kv_pairs(info)
+ kv = {**kv, **info_kv}
+ return kv
diff --git a/test/functional/test_framework/bip340_test_vectors.csv b/test/functional/test_framework/bip340_test_vectors.csv
new file mode 100644
index 0000000000..e068322deb
--- /dev/null
+++ b/test/functional/test_framework/bip340_test_vectors.csv
@@ -0,0 +1,16 @@
+index,secret key,public key,aux_rand,message,signature,verification result,comment
+0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE,
+1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE,
+2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE,
+3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n
+4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE,
+5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve
+6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false
+7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message
+8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value
+9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0
+10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1
+11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve
+12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size
+13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order
+14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index afc1995009..6b7214f03a 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -4,6 +4,9 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Utilities for manipulating blocks and transactions."""
+from binascii import a2b_hex
+import struct
+import time
import unittest
from .address import (
@@ -41,9 +44,10 @@ from .script import (
hash160,
)
from .util import assert_equal
-from io import BytesIO
+WITNESS_SCALE_FACTOR = 4
MAX_BLOCK_SIGOPS = 20000
+MAX_BLOCK_SIGOPS_WEIGHT = MAX_BLOCK_SIGOPS * WITNESS_SCALE_FACTOR
# Genesis block time (regtest)
TIME_GENESIS_BLOCK = 1296688602
@@ -51,19 +55,29 @@ TIME_GENESIS_BLOCK = 1296688602
# From BIP141
WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed"
+NORMAL_GBT_REQUEST_PARAMS = {"rules": ["segwit"]}
-def create_block(hashprev, coinbase, ntime=None, *, version=1):
+
+def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl=None, txlist=None):
"""Create a block (with regtest difficulty)."""
block = CBlock()
- block.nVersion = version
- if ntime is None:
- import time
- block.nTime = int(time.time() + 600)
+ if tmpl is None:
+ tmpl = {}
+ block.nVersion = version or tmpl.get('version') or 1
+ block.nTime = ntime or tmpl.get('curtime') or int(time.time() + 600)
+ block.hashPrevBlock = hashprev or int(tmpl['previousblockhash'], 0x10)
+ if tmpl and not tmpl.get('bits') is None:
+ block.nBits = struct.unpack('>I', a2b_hex(tmpl['bits']))[0]
else:
- block.nTime = ntime
- block.hashPrevBlock = hashprev
- block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams
+ block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams
+ if coinbase is None:
+ coinbase = create_coinbase(height=tmpl['height'])
block.vtx.append(coinbase)
+ if txlist:
+ for tx in txlist:
+ if not hasattr(tx, 'calc_sha256'):
+ tx = FromHex(CTransaction(), tx)
+ block.vtx.append(tx)
block.hashMerkleRoot = block.calc_merkle_root()
block.calc_sha256()
return block
@@ -101,22 +115,31 @@ def script_BIP34_coinbase_height(height):
return CScript([CScriptNum(height)])
-def create_coinbase(height, pubkey=None):
- """Create a coinbase transaction, assuming no miner fees.
+def create_coinbase(height, pubkey=None, extra_output_script=None, fees=0):
+ """Create a coinbase transaction.
If pubkey is passed in, the coinbase output will be a P2PK output;
- otherwise an anyone-can-spend output."""
+ otherwise an anyone-can-spend output.
+
+ If extra_output_script is given, make a 0-value output to that
+ script. This is useful to pad block weight/sigops as needed. """
coinbase = CTransaction()
coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff))
coinbaseoutput = CTxOut()
coinbaseoutput.nValue = 50 * COIN
halvings = int(height / 150) # regtest
coinbaseoutput.nValue >>= halvings
- if (pubkey is not None):
+ coinbaseoutput.nValue += fees
+ if pubkey is not None:
coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG])
else:
coinbaseoutput.scriptPubKey = CScript([OP_TRUE])
coinbase.vout = [coinbaseoutput]
+ if extra_output_script is not None:
+ coinbaseoutput2 = CTxOut()
+ coinbaseoutput2.nValue = 0
+ coinbaseoutput2.scriptPubKey = extra_output_script
+ coinbase.vout.append(coinbaseoutput2)
coinbase.calc_sha256()
return coinbase
@@ -135,25 +158,27 @@ def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=C
def create_transaction(node, txid, to_address, *, amount):
""" Return signed transaction spending the first output of the
- input txid. Note that the node must be able to sign for the
- output that is being spent, and the node must not be running
- multiple wallets.
+ input txid. Note that the node must have a wallet that can
+ sign for the output that is being spent.
"""
raw_tx = create_raw_transaction(node, txid, to_address, amount=amount)
- tx = CTransaction()
- tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx)))
+ tx = FromHex(CTransaction(), raw_tx)
return tx
def create_raw_transaction(node, txid, to_address, *, amount):
""" Return raw signed transaction spending the first output of the
- input txid. Note that the node must be able to sign for the
- output that is being spent, and the node must not be running
- multiple wallets.
+ input txid. Note that the node must have a wallet that can sign
+ for the output that is being spent.
"""
- rawtx = node.createrawtransaction(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount})
- signresult = node.signrawtransactionwithwallet(rawtx)
- assert_equal(signresult["complete"], True)
- return signresult['hex']
+ psbt = node.createpsbt(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount})
+ for _ in range(2):
+ for w in node.listwallets():
+ wrpc = node.get_wallet_rpc(w)
+ signed_psbt = wrpc.walletprocesspsbt(psbt)
+ psbt = signed_psbt['psbt']
+ final_psbt = node.finalizepsbt(psbt)
+ assert_equal(final_psbt["complete"], True)
+ return final_psbt['hex']
def get_legacy_sigopcount_block(block, accurate=True):
count = 0
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
index adbffb7dc7..f3d13c049b 100644
--- a/test/functional/test_framework/key.py
+++ b/test/functional/test_framework/key.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2019 Pieter Wuille
+# Copyright (c) 2019-2020 Pieter Wuille
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test-only secp256k1 elliptic curve implementation
@@ -6,10 +6,24 @@
WARNING: This code is slow, uses bad randomness, does not properly protect
keys, and is trivially vulnerable to side channel attacks. Do not use for
anything but tests."""
+import csv
+import hashlib
+import os
import random
+import unittest
from .util import modinv
+def TaggedHash(tag, data):
+ ss = hashlib.sha256(tag.encode('utf-8')).digest()
+ ss += ss
+ ss += data
+ return hashlib.sha256(ss).digest()
+
+def xor_bytes(b0, b1):
+ assert len(b0) == len(b1)
+ return bytes(x ^ y for (x, y) in zip(b0, b1))
+
def jacobi_symbol(n, k):
"""Compute the Jacobi symbol of n modulo k
@@ -68,6 +82,10 @@ class EllipticCurve:
inv_3 = (inv_2 * inv) % self.p
return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1)
+ def has_even_y(self, p1):
+ """Whether the point p1 has an even Y coordinate when expressed in affine coordinates."""
+ return not (p1[2] == 0 or self.affine(p1)[1] & 1)
+
def negate(self, p1):
"""Negate a Jacobian point tuple p1."""
x1, y1, z1 = p1
@@ -86,13 +104,13 @@ class EllipticCurve:
return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
def lift_x(self, x):
- """Given an X coordinate on the curve, return a corresponding affine point."""
+ """Given an X coordinate on the curve, return a corresponding affine point for which the Y coordinate is even."""
x_3 = pow(x, 3, self.p)
v = x_3 + self.a * x + self.b
y = modsqrt(v, self.p)
if y is None:
return None
- return (x, y, 1)
+ return (x, self.p - y if y & 1 else y, 1)
def double(self, p1):
"""Double a Jacobian tuple p1
@@ -197,7 +215,8 @@ class EllipticCurve:
r = self.add(r, p)
return r
-SECP256K1 = EllipticCurve(2**256 - 2**32 - 977, 0, 7)
+SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977
+SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7)
SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1)
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2
@@ -221,9 +240,9 @@ class ECPubKey():
x = int.from_bytes(data[1:33], 'big')
if SECP256K1.is_x_coord(x):
p = SECP256K1.lift_x(x)
- # if the oddness of the y co-ord isn't correct, find the other
- # valid y
- if (p[1] & 1) != (data[0] & 1):
+ # Make the Y coordinate odd if required (lift_x always produces
+ # a point with an even Y coordinate).
+ if data[0] & 1:
p = SECP256K1.negate(p)
self.p = p
self.valid = True
@@ -303,10 +322,14 @@ class ECPubKey():
u1 = z*w % SECP256K1_ORDER
u2 = r*w % SECP256K1_ORDER
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)]))
- if R is None or R[0] != r:
+ if R is None or (R[0] % SECP256K1_ORDER) != r:
return False
return True
+def generate_privkey():
+ """Generate a valid random 32-byte private key."""
+ return random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big')
+
class ECKey():
"""A secp256k1 private key"""
@@ -324,7 +347,7 @@ class ECKey():
def generate(self, compressed=True):
"""Generate a random private key (compressed or uncompressed)."""
- self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed)
+ self.set(generate_privkey(), compressed)
def get_bytes(self):
"""Retrieve the 32-byte representation of this key."""
@@ -369,3 +392,162 @@ class ECKey():
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
+
+def compute_xonly_pubkey(key):
+ """Compute an x-only (32 byte) public key from a (32 byte) private key.
+
+ This also returns whether the resulting public key was negated.
+ """
+
+ assert len(key) == 32
+ x = int.from_bytes(key, 'big')
+ if x == 0 or x >= SECP256K1_ORDER:
+ return (None, None)
+ P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, x)]))
+ return (P[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(P))
+
+def tweak_add_privkey(key, tweak):
+ """Tweak a private key (after negating it if needed)."""
+
+ assert len(key) == 32
+ assert len(tweak) == 32
+
+ x = int.from_bytes(key, 'big')
+ if x == 0 or x >= SECP256K1_ORDER:
+ return None
+ if not SECP256K1.has_even_y(SECP256K1.mul([(SECP256K1_G, x)])):
+ x = SECP256K1_ORDER - x
+ t = int.from_bytes(tweak, 'big')
+ if t >= SECP256K1_ORDER:
+ return None
+ x = (x + t) % SECP256K1_ORDER
+ if x == 0:
+ return None
+ return x.to_bytes(32, 'big')
+
+def tweak_add_pubkey(key, tweak):
+ """Tweak a public key and return whether the result had to be negated."""
+
+ assert len(key) == 32
+ assert len(tweak) == 32
+
+ x_coord = int.from_bytes(key, 'big')
+ if x_coord >= SECP256K1_FIELD_SIZE:
+ return None
+ P = SECP256K1.lift_x(x_coord)
+ if P is None:
+ return None
+ t = int.from_bytes(tweak, 'big')
+ if t >= SECP256K1_ORDER:
+ return None
+ Q = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, t), (P, 1)]))
+ if Q is None:
+ return None
+ return (Q[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(Q))
+
+def verify_schnorr(key, sig, msg):
+ """Verify a Schnorr signature (see BIP 340).
+
+ - key is a 32-byte xonly pubkey (computed using compute_xonly_pubkey).
+ - sig is a 64-byte Schnorr signature
+ - msg is a 32-byte message
+ """
+ assert len(key) == 32
+ assert len(msg) == 32
+ assert len(sig) == 64
+
+ x_coord = int.from_bytes(key, 'big')
+ if x_coord == 0 or x_coord >= SECP256K1_FIELD_SIZE:
+ return False
+ P = SECP256K1.lift_x(x_coord)
+ if P is None:
+ return False
+ r = int.from_bytes(sig[0:32], 'big')
+ if r >= SECP256K1_FIELD_SIZE:
+ return False
+ s = int.from_bytes(sig[32:64], 'big')
+ if s >= SECP256K1_ORDER:
+ return False
+ e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + key + msg), 'big') % SECP256K1_ORDER
+ R = SECP256K1.mul([(SECP256K1_G, s), (P, SECP256K1_ORDER - e)])
+ if not SECP256K1.has_even_y(R):
+ return False
+ if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]:
+ return False
+ return True
+
+def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False):
+ """Create a Schnorr signature (see BIP 340)."""
+
+ if aux is None:
+ aux = bytes(32)
+
+ assert len(key) == 32
+ assert len(msg) == 32
+ assert len(aux) == 32
+
+ sec = int.from_bytes(key, 'big')
+ if sec == 0 or sec >= SECP256K1_ORDER:
+ return None
+ P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, sec)]))
+ if SECP256K1.has_even_y(P) == flip_p:
+ sec = SECP256K1_ORDER - sec
+ t = (sec ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), 'big')).to_bytes(32, 'big')
+ kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER
+ assert kp != 0
+ R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)]))
+ k = kp if SECP256K1.has_even_y(R) != flip_r else SECP256K1_ORDER - kp
+ e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, 'big') + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER
+ return R[0].to_bytes(32, 'big') + ((k + e * sec) % SECP256K1_ORDER).to_bytes(32, 'big')
+
+class TestFrameworkKey(unittest.TestCase):
+ def test_schnorr(self):
+ """Test the Python Schnorr implementation."""
+ byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, SECP256K1_ORDER - 1, SECP256K1_ORDER, 2**256 - 1]]
+ keys = {}
+ for privkey in byte_arrays: # build array of key/pubkey pairs
+ pubkey, _ = compute_xonly_pubkey(privkey)
+ if pubkey is not None:
+ keys[privkey] = pubkey
+ for msg in byte_arrays: # test every combination of message, signing key, verification key
+ for sign_privkey, sign_pubkey in keys.items():
+ sig = sign_schnorr(sign_privkey, msg)
+ for verify_privkey, verify_pubkey in keys.items():
+ if verify_privkey == sign_privkey:
+ self.assertTrue(verify_schnorr(verify_pubkey, sig, msg))
+ sig = list(sig)
+ sig[random.randrange(64)] ^= (1 << (random.randrange(8))) # damaging signature should break things
+ sig = bytes(sig)
+ self.assertFalse(verify_schnorr(verify_pubkey, sig, msg))
+
+ def test_schnorr_testvectors(self):
+ """Implement the BIP340 test vectors (read from bip340_test_vectors.csv)."""
+ num_tests = 0
+ vectors_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'bip340_test_vectors.csv')
+ with open(vectors_file, newline='', encoding='utf8') as csvfile:
+ reader = csv.reader(csvfile)
+ next(reader)
+ for row in reader:
+ (i_str, seckey_hex, pubkey_hex, aux_rand_hex, msg_hex, sig_hex, result_str, comment) = row
+ i = int(i_str)
+ pubkey = bytes.fromhex(pubkey_hex)
+ msg = bytes.fromhex(msg_hex)
+ sig = bytes.fromhex(sig_hex)
+ result = result_str == 'TRUE'
+ if seckey_hex != '':
+ seckey = bytes.fromhex(seckey_hex)
+ pubkey_actual = compute_xonly_pubkey(seckey)[0]
+ self.assertEqual(pubkey.hex(), pubkey_actual.hex(), "BIP340 test vector %i (%s): pubkey mismatch" % (i, comment))
+ aux_rand = bytes.fromhex(aux_rand_hex)
+ try:
+ sig_actual = sign_schnorr(seckey, msg, aux_rand)
+ self.assertEqual(sig.hex(), sig_actual.hex(), "BIP340 test vector %i (%s): sig mismatch" % (i, comment))
+ except RuntimeError as e:
+ self.fail("BIP340 test vector %i (%s): signing raised exception %s" % (i, comment, e))
+ result_actual = verify_schnorr(pubkey, sig, msg)
+ if result:
+ self.assertEqual(result, result_actual, "BIP340 test vector %i (%s): verification failed" % (i, comment))
+ else:
+ self.assertEqual(result, result_actual, "BIP340 test vector %i (%s): verification succeeded unexpectedly" % (i, comment))
+ num_tests += 1
+ self.assertTrue(num_tests >= 15) # expect at least 15 test vectors
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 00cf1ef66d..bab4ad0008 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -51,7 +51,6 @@ MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result
MAX_INV_SIZE = 50000 # Maximum number of entries in an 'inv' protocol message
NODE_NETWORK = (1 << 0)
-NODE_GETUTXO = (1 << 1)
NODE_BLOOM = (1 << 2)
NODE_WITNESS = (1 << 3)
NODE_COMPACT_FILTERS = (1 << 6)
@@ -136,12 +135,17 @@ def uint256_from_compact(c):
return v
-def deser_vector(f, c):
+# deser_function_name: Allow for an alternate deserialization function on the
+# entries in the vector.
+def deser_vector(f, c, deser_function_name=None):
nit = deser_compact_size(f)
r = []
for _ in range(nit):
t = c()
- t.deserialize(f)
+ if deser_function_name:
+ getattr(t, deser_function_name)(f)
+ else:
+ t.deserialize(f)
r.append(t)
return r
@@ -204,38 +208,82 @@ def ToHex(obj):
class CAddress:
- __slots__ = ("ip", "nServices", "pchReserved", "port", "time")
+ __slots__ = ("net", "ip", "nServices", "port", "time")
+
+ # see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
+ NET_IPV4 = 1
+
+ ADDRV2_NET_NAME = {
+ NET_IPV4: "IPv4"
+ }
+
+ ADDRV2_ADDRESS_LENGTH = {
+ NET_IPV4: 4
+ }
def __init__(self):
self.time = 0
self.nServices = 1
- self.pchReserved = b"\x00" * 10 + b"\xff" * 2
+ self.net = self.NET_IPV4
self.ip = "0.0.0.0"
self.port = 0
def deserialize(self, f, *, with_time=True):
+ """Deserialize from addrv1 format (pre-BIP155)"""
if with_time:
# VERSION messages serialize CAddress objects without time
- self.time = struct.unpack("<i", f.read(4))[0]
+ self.time = struct.unpack("<I", f.read(4))[0]
self.nServices = struct.unpack("<Q", f.read(8))[0]
- self.pchReserved = f.read(12)
+ # We only support IPv4 which means skip 12 bytes and read the next 4 as IPv4 address.
+ f.read(12)
+ self.net = self.NET_IPV4
self.ip = socket.inet_ntoa(f.read(4))
self.port = struct.unpack(">H", f.read(2))[0]
def serialize(self, *, with_time=True):
+ """Serialize in addrv1 format (pre-BIP155)"""
+ assert self.net == self.NET_IPV4
r = b""
if with_time:
# VERSION messages serialize CAddress objects without time
- r += struct.pack("<i", self.time)
+ r += struct.pack("<I", self.time)
r += struct.pack("<Q", self.nServices)
- r += self.pchReserved
+ r += b"\x00" * 10 + b"\xff" * 2
+ r += socket.inet_aton(self.ip)
+ r += struct.pack(">H", self.port)
+ return r
+
+ def deserialize_v2(self, f):
+ """Deserialize from addrv2 format (BIP155)"""
+ self.time = struct.unpack("<I", f.read(4))[0]
+
+ self.nServices = deser_compact_size(f)
+
+ self.net = struct.unpack("B", f.read(1))[0]
+ assert self.net == self.NET_IPV4
+
+ address_length = deser_compact_size(f)
+ assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net]
+
+ self.ip = socket.inet_ntoa(f.read(4))
+
+ self.port = struct.unpack(">H", f.read(2))[0]
+
+ def serialize_v2(self):
+ """Serialize in addrv2 format (BIP155)"""
+ assert self.net == self.NET_IPV4
+ r = b""
+ r += struct.pack("<I", self.time)
+ r += ser_compact_size(self.nServices)
+ r += struct.pack("B", self.net)
+ r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net])
r += socket.inet_aton(self.ip)
r += struct.pack(">H", self.port)
return r
def __repr__(self):
- return "CAddress(nServices=%i ip=%s port=%i)" % (self.nServices,
- self.ip, self.port)
+ return ("CAddress(nServices=%i net=%s addr=%s port=%i)"
+ % (self.nServices, self.ADDRV2_NET_NAME[self.net], self.ip, self.port))
class CInv:
@@ -1064,6 +1112,40 @@ class msg_addr:
return "msg_addr(addrs=%s)" % (repr(self.addrs))
+class msg_addrv2:
+ __slots__ = ("addrs",)
+ msgtype = b"addrv2"
+
+ def __init__(self):
+ self.addrs = []
+
+ def deserialize(self, f):
+ self.addrs = deser_vector(f, CAddress, "deserialize_v2")
+
+ def serialize(self):
+ return ser_vector(self.addrs, "serialize_v2")
+
+ def __repr__(self):
+ return "msg_addrv2(addrs=%s)" % (repr(self.addrs))
+
+
+class msg_sendaddrv2:
+ __slots__ = ()
+ msgtype = b"sendaddrv2"
+
+ def __init__(self):
+ pass
+
+ def deserialize(self, f):
+ pass
+
+ def serialize(self):
+ return b""
+
+ def __repr__(self):
+ return "msg_sendaddrv2()"
+
+
class msg_inv:
__slots__ = ("inv",)
msgtype = b"inv"
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index 5f9b316b18..8b79a4dc2f 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -33,6 +33,7 @@ from test_framework.messages import (
MAX_HEADERS_RESULTS,
MIN_VERSION_SUPPORTED,
msg_addr,
+ msg_addrv2,
msg_block,
MSG_BLOCK,
msg_blocktxn,
@@ -56,6 +57,7 @@ from test_framework.messages import (
msg_notfound,
msg_ping,
msg_pong,
+ msg_sendaddrv2,
msg_sendcmpct,
msg_sendheaders,
msg_tx,
@@ -75,6 +77,7 @@ logger = logging.getLogger("TestFramework.p2p")
MESSAGEMAP = {
b"addr": msg_addr,
+ b"addrv2": msg_addrv2,
b"block": msg_block,
b"blocktxn": msg_blocktxn,
b"cfcheckpt": msg_cfcheckpt,
@@ -97,6 +100,7 @@ MESSAGEMAP = {
b"notfound": msg_notfound,
b"ping": msg_ping,
b"pong": msg_pong,
+ b"sendaddrv2": msg_sendaddrv2,
b"sendcmpct": msg_sendcmpct,
b"sendheaders": msg_sendheaders,
b"tx": msg_tx,
@@ -285,7 +289,7 @@ class P2PInterface(P2PConnection):
Individual testcases should subclass this and override the on_* methods
if they want to alter message handling behaviour."""
- def __init__(self):
+ def __init__(self, support_addrv2=False):
super().__init__()
# Track number of messages of each type received.
@@ -303,6 +307,8 @@ class P2PInterface(P2PConnection):
# The network services received from the peer
self.nServices = 0
+ self.support_addrv2 = support_addrv2
+
def peer_connect(self, *args, services=NODE_NETWORK|NODE_WITNESS, send_version=True, **kwargs):
create_conn = super().peer_connect(*args, **kwargs)
@@ -345,6 +351,7 @@ class P2PInterface(P2PConnection):
pass
def on_addr(self, message): pass
+ def on_addrv2(self, message): pass
def on_block(self, message): pass
def on_blocktxn(self, message): pass
def on_cfcheckpt(self, message): pass
@@ -365,6 +372,7 @@ class P2PInterface(P2PConnection):
def on_merkleblock(self, message): pass
def on_notfound(self, message): pass
def on_pong(self, message): pass
+ def on_sendaddrv2(self, message): pass
def on_sendcmpct(self, message): pass
def on_sendheaders(self, message): pass
def on_tx(self, message): pass
@@ -388,6 +396,8 @@ class P2PInterface(P2PConnection):
assert message.nVersion >= MIN_VERSION_SUPPORTED, "Version {} received. Test framework only supports versions greater than {}".format(message.nVersion, MIN_VERSION_SUPPORTED)
if message.nVersion >= 70016:
self.send_message(msg_wtxidrelay())
+ if self.support_addrv2:
+ self.send_message(msg_sendaddrv2())
self.send_message(msg_verack())
self.nServices = message.nServices
diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py
index 5e35ba0fce..26ccab3039 100644
--- a/test/functional/test_framework/script.py
+++ b/test/functional/test_framework/script.py
@@ -6,11 +6,15 @@
This file is modified from python-bitcoinlib.
"""
+
+from collections import namedtuple
import hashlib
import struct
import unittest
from typing import List, Dict
+from .key import TaggedHash, tweak_add_pubkey
+
from .messages import (
CTransaction,
CTxOut,
@@ -22,8 +26,13 @@ from .messages import (
)
MAX_SCRIPT_ELEMENT_SIZE = 520
+LOCKTIME_THRESHOLD = 500000000
+ANNEX_TAG = 0x50
+
OPCODE_NAMES = {} # type: Dict[CScriptOp, str]
+LEAF_VERSION_TAPSCRIPT = 0xc0
+
def hash160(s):
return hashlib.new('ripemd160', sha256(s)).digest()
@@ -239,11 +248,8 @@ OP_NOP8 = CScriptOp(0xb7)
OP_NOP9 = CScriptOp(0xb8)
OP_NOP10 = CScriptOp(0xb9)
-# template matching params
-OP_SMALLINTEGER = CScriptOp(0xfa)
-OP_PUBKEYS = CScriptOp(0xfb)
-OP_PUBKEYHASH = CScriptOp(0xfd)
-OP_PUBKEY = CScriptOp(0xfe)
+# BIP 342 opcodes (Tapscript)
+OP_CHECKSIGADD = CScriptOp(0xba)
OP_INVALIDOPCODE = CScriptOp(0xff)
@@ -359,10 +365,7 @@ OPCODE_NAMES.update({
OP_NOP8: 'OP_NOP8',
OP_NOP9: 'OP_NOP9',
OP_NOP10: 'OP_NOP10',
- OP_SMALLINTEGER: 'OP_SMALLINTEGER',
- OP_PUBKEYS: 'OP_PUBKEYS',
- OP_PUBKEYHASH: 'OP_PUBKEYHASH',
- OP_PUBKEY: 'OP_PUBKEY',
+ OP_CHECKSIGADD: 'OP_CHECKSIGADD',
OP_INVALIDOPCODE: 'OP_INVALIDOPCODE',
})
@@ -593,6 +596,7 @@ class CScript(bytes):
return n
+SIGHASH_DEFAULT = 0 # Taproot-only default, semantics same as SIGHASH_ALL
SIGHASH_ALL = 1
SIGHASH_NONE = 2
SIGHASH_SINGLE = 3
@@ -615,7 +619,6 @@ def FindAndDelete(script, sig):
r += script[last_sop_idx:]
return CScript(r)
-
def LegacySignatureHash(script, txTo, inIdx, hashtype):
"""Consensus-correct SignatureHash
@@ -738,3 +741,125 @@ class TestFrameworkScript(unittest.TestCase):
values = [0, 1, -1, -2, 127, 128, -255, 256, (1 << 15) - 1, -(1 << 16), (1 << 24) - 1, (1 << 31), 1 - (1 << 32), 1 << 40, 1500, -1500]
for value in values:
self.assertEqual(CScriptNum.decode(CScriptNum.encode(CScriptNum(value))), value)
+
+def TaprootSignatureHash(txTo, spent_utxos, hash_type, input_index = 0, scriptpath = False, script = CScript(), codeseparator_pos = -1, annex = None, leaf_ver = LEAF_VERSION_TAPSCRIPT):
+ assert (len(txTo.vin) == len(spent_utxos))
+ assert (input_index < len(txTo.vin))
+ out_type = SIGHASH_ALL if hash_type == 0 else hash_type & 3
+ in_type = hash_type & SIGHASH_ANYONECANPAY
+ spk = spent_utxos[input_index].scriptPubKey
+ ss = bytes([0, hash_type]) # epoch, hash_type
+ ss += struct.pack("<i", txTo.nVersion)
+ ss += struct.pack("<I", txTo.nLockTime)
+ if in_type != SIGHASH_ANYONECANPAY:
+ ss += sha256(b"".join(i.prevout.serialize() for i in txTo.vin))
+ ss += sha256(b"".join(struct.pack("<q", u.nValue) for u in spent_utxos))
+ ss += sha256(b"".join(ser_string(u.scriptPubKey) for u in spent_utxos))
+ ss += sha256(b"".join(struct.pack("<I", i.nSequence) for i in txTo.vin))
+ if out_type == SIGHASH_ALL:
+ ss += sha256(b"".join(o.serialize() for o in txTo.vout))
+ spend_type = 0
+ if annex is not None:
+ spend_type |= 1
+ if (scriptpath):
+ spend_type |= 2
+ ss += bytes([spend_type])
+ if in_type == SIGHASH_ANYONECANPAY:
+ ss += txTo.vin[input_index].prevout.serialize()
+ ss += struct.pack("<q", spent_utxos[input_index].nValue)
+ ss += ser_string(spk)
+ ss += struct.pack("<I", txTo.vin[input_index].nSequence)
+ else:
+ ss += struct.pack("<I", input_index)
+ if (spend_type & 1):
+ ss += sha256(ser_string(annex))
+ if out_type == SIGHASH_SINGLE:
+ if input_index < len(txTo.vout):
+ ss += sha256(txTo.vout[input_index].serialize())
+ else:
+ ss += bytes(0 for _ in range(32))
+ if (scriptpath):
+ ss += TaggedHash("TapLeaf", bytes([leaf_ver]) + ser_string(script))
+ ss += bytes([0])
+ ss += struct.pack("<i", codeseparator_pos)
+ assert len(ss) == 175 - (in_type == SIGHASH_ANYONECANPAY) * 49 - (out_type != SIGHASH_ALL and out_type != SIGHASH_SINGLE) * 32 + (annex is not None) * 32 + scriptpath * 37
+ return TaggedHash("TapSighash", ss)
+
+def taproot_tree_helper(scripts):
+ if len(scripts) == 0:
+ return ([], bytes(0 for _ in range(32)))
+ if len(scripts) == 1:
+ # One entry: treat as a leaf
+ script = scripts[0]
+ assert(not callable(script))
+ if isinstance(script, list):
+ return taproot_tree_helper(script)
+ assert(isinstance(script, tuple))
+ version = LEAF_VERSION_TAPSCRIPT
+ name = script[0]
+ code = script[1]
+ if len(script) == 3:
+ version = script[2]
+ assert version & 1 == 0
+ assert isinstance(code, bytes)
+ h = TaggedHash("TapLeaf", bytes([version]) + ser_string(code))
+ if name is None:
+ return ([], h)
+ return ([(name, version, code, bytes())], h)
+ elif len(scripts) == 2 and callable(scripts[1]):
+ # Two entries, and the right one is a function
+ left, left_h = taproot_tree_helper(scripts[0:1])
+ right_h = scripts[1](left_h)
+ left = [(name, version, script, control + right_h) for name, version, script, control in left]
+ right = []
+ else:
+ # Two or more entries: descend into each side
+ split_pos = len(scripts) // 2
+ left, left_h = taproot_tree_helper(scripts[0:split_pos])
+ right, right_h = taproot_tree_helper(scripts[split_pos:])
+ left = [(name, version, script, control + right_h) for name, version, script, control in left]
+ right = [(name, version, script, control + left_h) for name, version, script, control in right]
+ if right_h < left_h:
+ right_h, left_h = left_h, right_h
+ h = TaggedHash("TapBranch", left_h + right_h)
+ return (left + right, h)
+
+# A TaprootInfo object has the following fields:
+# - scriptPubKey: the scriptPubKey (witness v1 CScript)
+# - inner_pubkey: the inner pubkey (32 bytes)
+# - negflag: whether the pubkey in the scriptPubKey was negated from inner_pubkey+tweak*G (bool).
+# - tweak: the tweak (32 bytes)
+# - leaves: a dict of name -> TaprootLeafInfo objects for all known leaves
+TaprootInfo = namedtuple("TaprootInfo", "scriptPubKey,inner_pubkey,negflag,tweak,leaves")
+
+# A TaprootLeafInfo object has the following fields:
+# - script: the leaf script (CScript or bytes)
+# - version: the leaf version (0xc0 for BIP342 tapscript)
+# - merklebranch: the merkle branch to use for this leaf (32*N bytes)
+TaprootLeafInfo = namedtuple("TaprootLeafInfo", "script,version,merklebranch")
+
+def taproot_construct(pubkey, scripts=None):
+ """Construct a tree of Taproot spending conditions
+
+ pubkey: a 32-byte xonly pubkey for the internal pubkey (bytes)
+ scripts: a list of items; each item is either:
+ - a (name, CScript or bytes, leaf version) tuple
+ - a (name, CScript or bytes) tuple (defaulting to leaf version 0xc0)
+ - another list of items (with the same structure)
+ - a list of two items; the first of which is an item itself, and the
+ second is a function. The function takes as input the Merkle root of the
+ first item, and produces a (fictitious) partner to hash with.
+
+ Returns: a TaprootInfo object
+ """
+ if scripts is None:
+ scripts = []
+
+ ret, h = taproot_tree_helper(scripts)
+ tweak = TaggedHash("TapTweak", pubkey + h)
+ tweaked, negated = tweak_add_pubkey(pubkey, tweak)
+ leaves = dict((name, TaprootLeafInfo(script, version, merklebranch)) for name, version, script, merklebranch in ret)
+ return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves)
+
+def is_op_success(o):
+ return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbb and o <= 0xfe)
diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py
index 80fbae70bf..318a438705 100755
--- a/test/functional/test_framework/script_util.py
+++ b/test/functional/test_framework/script_util.py
@@ -3,7 +3,8 @@
# 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."""
-from test_framework.script import CScript
+from test_framework.script import CScript, hash160, sha256, OP_0, OP_DUP, OP_HASH160, OP_CHECKSIG, OP_EQUAL, OP_EQUALVERIFY
+from test_framework.util import hex_str_to_bytes
# To prevent a "tx-size-small" policy rule error, a transaction has to have a
# non-witness size of at least 82 bytes (MIN_STANDARD_TX_NONWITNESS_SIZE in
@@ -24,3 +25,59 @@ from test_framework.script import CScript
# met.
DUMMY_P2WPKH_SCRIPT = CScript([b'a' * 21])
DUMMY_2_P2WPKH_SCRIPT = CScript([b'b' * 21])
+
+def keyhash_to_p2pkh_script(hash, main = False):
+ assert len(hash) == 20
+ return CScript([OP_DUP, OP_HASH160, hash, OP_EQUALVERIFY, OP_CHECKSIG])
+
+def scripthash_to_p2sh_script(hash, main = False):
+ assert len(hash) == 20
+ return CScript([OP_HASH160, hash, OP_EQUAL])
+
+def key_to_p2pkh_script(key, main = False):
+ key = check_key(key)
+ return keyhash_to_p2pkh_script(hash160(key), main)
+
+def script_to_p2sh_script(script, main = False):
+ script = check_script(script)
+ return scripthash_to_p2sh_script(hash160(script), main)
+
+def key_to_p2sh_p2wpkh_script(key, main = False):
+ key = check_key(key)
+ p2shscript = CScript([OP_0, hash160(key)])
+ return script_to_p2sh_script(p2shscript, main)
+
+def program_to_witness_script(version, program, main = False):
+ if isinstance(program, str):
+ program = hex_str_to_bytes(program)
+ assert 0 <= version <= 16
+ assert 2 <= len(program) <= 40
+ assert version > 0 or len(program) in [20, 32]
+ return CScript([version, program])
+
+def script_to_p2wsh_script(script, main = False):
+ script = check_script(script)
+ return program_to_witness_script(0, sha256(script), main)
+
+def key_to_p2wpkh_script(key, main = False):
+ key = check_key(key)
+ return program_to_witness_script(0, hash160(key), main)
+
+def script_to_p2sh_p2wsh_script(script, main = False):
+ script = check_script(script)
+ p2shscript = CScript([OP_0, sha256(script)])
+ return script_to_p2sh_script(p2shscript, main)
+
+def check_key(key):
+ if isinstance(key, str):
+ key = hex_str_to_bytes(key) # Assuming this is hex string
+ if isinstance(key, bytes) and (len(key) == 33 or len(key) == 65):
+ return key
+ assert False
+
+def check_script(script):
+ if isinstance(script, str):
+ script = hex_str_to_bytes(script) # Assuming this is hex string
+ if isinstance(script, bytes) or isinstance(script, CScript):
+ return script
+ assert False
diff --git a/test/functional/test_framework/segwit_addr.py b/test/functional/test_framework/segwit_addr.py
index 02368e938f..00c0d8a919 100644
--- a/test/functional/test_framework/segwit_addr.py
+++ b/test/functional/test_framework/segwit_addr.py
@@ -3,7 +3,7 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Reference implementation for Bech32 and segwit addresses."""
-
+import unittest
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
@@ -84,7 +84,7 @@ def convertbits(data, frombits, tobits, pad=True):
return ret
-def decode(hrp, addr):
+def decode_segwit_address(hrp, addr):
"""Decode a segwit address."""
hrpgot, data = bech32_decode(addr)
if hrpgot != hrp:
@@ -99,9 +99,23 @@ def decode(hrp, addr):
return (data[0], decoded)
-def encode(hrp, witver, witprog):
+def encode_segwit_address(hrp, witver, witprog):
"""Encode a segwit address."""
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
- if decode(hrp, ret) == (None, None):
+ if decode_segwit_address(hrp, ret) == (None, None):
return None
return ret
+
+class TestFrameworkScript(unittest.TestCase):
+ def test_segwit_encode_decode(self):
+ def test_python_bech32(addr):
+ hrp = addr[:4]
+ self.assertEqual(hrp, "bcrt")
+ (witver, witprog) = decode_segwit_address(hrp, addr)
+ self.assertEqual(encode_segwit_address(hrp, witver, witprog), addr)
+
+ # P2WPKH
+ test_python_bech32('bcrt1qthmht0k2qnh3wy7336z05lu2km7emzfpm3wg46')
+ # P2WSH
+ test_python_bech32('bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj')
+ test_python_bech32('bcrt1qft5p2uhsdcdc3l2ua4ap5qqfg4pjaqlp250x7us7a8qqhrxrxfsqseac85')
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index f41f5129b8..bf047c5f68 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -27,10 +27,9 @@ from .util import (
PortSeed,
assert_equal,
check_json_precision,
- connect_nodes,
- disconnect_nodes,
get_datadir_path,
initialize_datadir,
+ p2p_port,
wait_until_helper,
)
@@ -102,8 +101,17 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.rpc_timeout = 60 # Wait for up to 60 seconds for the RPC server to respond
self.supports_cli = True
self.bind_to_localhost_only = True
- self.set_test_params()
self.parse_args()
+ self.default_wallet_name = "default_wallet" if self.options.descriptors else ""
+ self.wallet_data_filename = "wallet.dat"
+ # Optional list of wallet names that can be set in set_test_params to
+ # create and import keys to. If unset, default is len(nodes) *
+ # [default_wallet_name]. If wallet names are None, wallet creation is
+ # skipped. If list is truncated, wallet creation is skipped and keys
+ # are not imported.
+ self.wallet_names = None
+ 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
@@ -175,9 +183,14 @@ 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")
parser.add_argument("--randomseed", type=int,
help="set a random seed for deterministically reproducing a previous test run")
- parser.add_argument("--descriptors", default=False, action="store_true",
- help="Run test using a descriptor wallet")
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')
+
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument("--descriptors", default=False, action="store_true",
+ help="Run test using a descriptor wallet", dest='descriptors')
+ group.add_argument("--legacy-wallet", default=False, action="store_false",
+ help="Run test using legacy wallets", dest='descriptors')
+
self.add_options(parser)
self.options = parser.parse_args()
self.options.previous_releases_path = previous_releases_path
@@ -362,23 +375,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def setup_nodes(self):
"""Override this method to customize test node setup"""
extra_args = [[]] * self.num_nodes
- wallets = [[]] * self.num_nodes
if hasattr(self, "extra_args"):
extra_args = self.extra_args
- wallets = [[x for x in eargs if x.startswith('-wallet=')] for eargs in extra_args]
- extra_args = [x + ['-nowallet'] for x in extra_args]
self.add_nodes(self.num_nodes, extra_args)
self.start_nodes()
- for i, n in enumerate(self.nodes):
- n.extra_args.pop()
- if '-wallet=0' in n.extra_args or '-nowallet' in n.extra_args or '-disablewallet' in n.extra_args or not self.is_wallet_compiled():
- continue
- if '-wallet=' not in wallets[i] and not any([x.startswith('-wallet=') for x in wallets[i]]):
- wallets[i].append('-wallet=')
- for w in wallets[i]:
- wallet_name = w.split('=', 1)[1]
- n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors)
- self.import_deterministic_coinbase_privkeys()
+ if self.is_wallet_compiled():
+ self.import_deterministic_coinbase_privkeys()
if not self.setup_clean_chain:
for n in self.nodes:
assert_equal(n.getblockchaininfo()["blocks"], 199)
@@ -394,13 +396,15 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
assert_equal(chain_info["initialblockdownload"], False)
def import_deterministic_coinbase_privkeys(self):
- for n in self.nodes:
- try:
- n.getwalletinfo()
- except JSONRPCException as e:
- assert str(e).startswith('Method not found')
- continue
-
+ for i in range(self.num_nodes):
+ self.init_wallet(i)
+
+ def init_wallet(self, i):
+ wallet_name = self.default_wallet_name if self.wallet_names is None else self.wallet_names[i] if i < len(self.wallet_names) else False
+ if wallet_name is not False:
+ n = self.nodes[i]
+ if wallet_name is not None:
+ n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors, load_on_startup=True)
n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase')
def run_test(self):
@@ -534,10 +538,49 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.nodes[i].process.wait(timeout)
def connect_nodes(self, a, b):
- connect_nodes(self.nodes[a], b)
+ def connect_nodes_helper(from_connection, node_num):
+ ip_port = "127.0.0.1:" + str(p2p_port(node_num))
+ from_connection.addnode(ip_port, "onetry")
+ # 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
+ wait_until_helper(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
+ wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()))
+
+ connect_nodes_helper(self.nodes[a], b)
def disconnect_nodes(self, a, b):
- disconnect_nodes(self.nodes[a], b)
+ def disconnect_nodes_helper(from_connection, node_num):
+ def get_peer_ids():
+ result = []
+ for peer in from_connection.getpeerinfo():
+ if "testnode{}".format(node_num) in peer['subver']:
+ result.append(peer['id'])
+ return result
+
+ peer_ids = get_peer_ids()
+ if not peer_ids:
+ self.log.warning("disconnect_nodes: {} and {} were not connected".format(
+ from_connection.index,
+ node_num,
+ ))
+ return
+ for peer_id in peer_ids:
+ try:
+ from_connection.disconnectnode(nodeid=peer_id)
+ except JSONRPCException as e:
+ # If this node is disconnected between calculating the peer id
+ # and issuing the disconnect, don't worry about it.
+ # This avoids a race condition if we're mass-disconnecting peers.
+ if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
+ raise
+
+ # wait to disconnect
+ wait_until_helper(lambda: not get_peer_ids(), timeout=5)
+
+ disconnect_nodes_helper(self.nodes[a], b)
def split_network(self):
"""
@@ -731,6 +774,18 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
"""Skip the running test if wallet has not been compiled."""
if not self.is_wallet_compiled():
raise SkipTest("wallet has not been compiled.")
+ if self.options.descriptors:
+ self.skip_if_no_sqlite()
+
+ def skip_if_no_sqlite(self):
+ """Skip the running test if sqlite has not been compiled."""
+ if not self.is_sqlite_compiled():
+ raise SkipTest("sqlite has not been compiled.")
+
+ def skip_if_no_bdb(self):
+ """Skip the running test if BDB has not been compiled."""
+ if not self.is_bdb_compiled():
+ raise SkipTest("BDB has not been compiled.")
def skip_if_no_wallet_tool(self):
"""Skip the running test if bitcoin-wallet has not been compiled."""
@@ -770,3 +825,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def is_zmq_compiled(self):
"""Checks whether the zmq module was compiled."""
return self.config["components"].getboolean("ENABLE_ZMQ")
+
+ def is_sqlite_compiled(self):
+ """Checks whether the wallet module was compiled with Sqlite support."""
+ return self.config["components"].getboolean("USE_SQLITE")
+
+ def is_bdb_compiled(self):
+ """Checks whether the wallet module was compiled with BDB support."""
+ return self.config["components"].getboolean("USE_BDB")
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 046efe730e..0a5b7f551c 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -544,7 +544,7 @@ class TestNode():
def num_test_p2p_connections(self):
"""Return number of test framework p2p connections to the node."""
- return len([peer for peer in self.getpeerinfo() if peer['subver'] == MY_SUBVERSION])
+ return len([peer for peer in self.getpeerinfo() if peer['subver'] == MY_SUBVERSION.decode("utf-8")])
def disconnect_p2ps(self):
"""Close all p2p connections to the node."""
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index af7f0b62f4..62ff5c6e33 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -8,11 +8,11 @@ from base64 import b64encode
from binascii import unhexlify
from decimal import Decimal, ROUND_DOWN
from subprocess import CalledProcessError
+import hashlib
import inspect
import json
import logging
import os
-import random
import re
import time
import unittest
@@ -261,6 +261,14 @@ def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'),
raise AssertionError("Predicate {} not true after {} seconds".format(predicate_source, timeout))
raise RuntimeError('Unreachable')
+def sha256sum_file(filename):
+ h = hashlib.sha256()
+ with open(filename, 'rb') as f:
+ d = f.read(4096)
+ while len(d) > 0:
+ h.update(d)
+ d = f.read(4096)
+ return h.digest()
# RPC/P2P connection constants and functions
############################################
@@ -412,47 +420,6 @@ def set_node_times(nodes, t):
node.setmocktime(t)
-def disconnect_nodes(from_connection, node_num):
- def get_peer_ids():
- result = []
- for peer in from_connection.getpeerinfo():
- if "testnode{}".format(node_num) in peer['subver']:
- result.append(peer['id'])
- return result
-
- peer_ids = get_peer_ids()
- if not peer_ids:
- logger.warning("disconnect_nodes: {} and {} were not connected".format(
- from_connection.index,
- node_num,
- ))
- return
- for peer_id in peer_ids:
- try:
- from_connection.disconnectnode(nodeid=peer_id)
- except JSONRPCException as e:
- # If this node is disconnected between calculating the peer id
- # and issuing the disconnect, don't worry about it.
- # This avoids a race condition if we're mass-disconnecting peers.
- if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
- raise
-
- # wait to disconnect
- wait_until_helper(lambda: not get_peer_ids(), timeout=5)
-
-
-def connect_nodes(from_connection, node_num):
- ip_port = "127.0.0.1:" + str(p2p_port(node_num))
- from_connection.addnode(ip_port, "onetry")
- # 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
- wait_until_helper(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
- wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()))
-
-
# Transaction/Block functions
#############################
@@ -469,62 +436,6 @@ def find_output(node, txid, amount, *, blockhash=None):
raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount)))
-def gather_inputs(from_node, amount_needed, confirmations_required=1):
- """
- Return a random set of unspent txouts that are enough to pay amount_needed
- """
- assert confirmations_required >= 0
- utxo = from_node.listunspent(confirmations_required)
- random.shuffle(utxo)
- inputs = []
- total_in = Decimal("0.00000000")
- while total_in < amount_needed and len(utxo) > 0:
- t = utxo.pop()
- total_in += t["amount"]
- inputs.append({"txid": t["txid"], "vout": t["vout"], "address": t["address"]})
- if total_in < amount_needed:
- raise RuntimeError("Insufficient funds: need %d, have %d" % (amount_needed, total_in))
- return (total_in, inputs)
-
-
-def make_change(from_node, amount_in, amount_out, fee):
- """
- Create change output(s), return them
- """
- outputs = {}
- amount = amount_out + fee
- change = amount_in - amount
- if change > amount * 2:
- # Create an extra change output to break up big inputs
- change_address = from_node.getnewaddress()
- # Split change in two, being careful of rounding:
- outputs[change_address] = Decimal(change / 2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
- change = amount_in - amount - outputs[change_address]
- if change > 0:
- outputs[from_node.getnewaddress()] = change
- return outputs
-
-
-def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
- """
- Create a random transaction.
- Returns (txid, hex-encoded-transaction-data, fee)
- """
- from_node = random.choice(nodes)
- to_node = random.choice(nodes)
- fee = min_fee + fee_increment * random.randint(0, fee_variants)
-
- (total_in, inputs) = gather_inputs(from_node, amount + fee)
- outputs = make_change(from_node, total_in, amount, fee)
- outputs[to_node.getnewaddress()] = float(amount)
-
- rawtx = from_node.createrawtransaction(inputs, outputs)
- signresult = from_node.signrawtransactionwithwallet(rawtx)
- txid = from_node.sendrawtransaction(signresult["hex"], 0)
-
- return (txid, signresult["hex"], fee)
-
-
# Helper to create at least "count" utxos
# Pass in a fee that is sufficient for relay and mining new transactions.
def create_confirmed_utxos(fee, node, count):
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index 39b3bf2a5b..a71f2c69cb 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -40,9 +40,20 @@ class MiniWallet:
self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value']})
return blocks
- def get_utxo(self):
- """Return the last utxo. Can be used to get the change output immediately after a send_self_transfer"""
- return self._utxos.pop()
+ def get_utxo(self, *, txid=''):
+ """
+ Returns a utxo and marks it as spent (pops it from the internal list)
+
+ Args:
+ txid (string), optional: get the first utxo we find from a specific transaction
+
+ Note: Can be used to get the change output immediately after a send_self_transfer
+ """
+ index = -1 # by default the last utxo
+ if txid:
+ utxo = next(filter(lambda utxo: txid == utxo['txid'], self._utxos))
+ index = self._utxos.index(utxo)
+ return self._utxos.pop(index)
def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None):
"""Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index c8cf173d5f..5b3db282e1 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -70,7 +70,9 @@ TEST_FRAMEWORK_MODULES = [
"address",
"blocktools",
"muhash",
+ "key",
"script",
+ "segwit_addr",
"util",
]
@@ -87,13 +89,15 @@ BASE_SCRIPTS = [
'wallet_hd.py',
'wallet_hd.py --descriptors',
'wallet_backup.py',
+ 'wallet_backup.py --descriptors',
# vv Tests less than 5m vv
'mining_getblocktemplate_longpoll.py',
'feature_maxuploadtarget.py',
'feature_block.py',
'rpc_fundrawtransaction.py',
+ 'rpc_fundrawtransaction.py --descriptors',
'p2p_compactblocks.py',
- 'feature_segwit.py',
+ 'feature_segwit.py --legacy-wallet',
# vv Tests less than 2m vv
'wallet_basic.py',
'wallet_basic.py --descriptors',
@@ -103,18 +107,24 @@ BASE_SCRIPTS = [
'p2p_timeouts.py',
'p2p_tx_download.py',
'mempool_updatefromblock.py',
- 'wallet_dump.py',
+ 'wallet_dump.py --legacy-wallet',
'wallet_listtransactions.py',
+ 'wallet_listtransactions.py --descriptors',
+ 'feature_taproot.py',
# vv Tests less than 60s vv
'p2p_sendheaders.py',
- 'wallet_importmulti.py',
+ 'wallet_importmulti.py --legacy-wallet',
'mempool_limit.py',
'rpc_txoutproof.py',
'wallet_listreceivedby.py',
+ 'wallet_listreceivedby.py --descriptors',
'wallet_abandonconflict.py',
+ 'wallet_abandonconflict.py --descriptors',
'feature_csv_activation.py',
'rpc_rawtransaction.py',
+ 'rpc_rawtransaction.py --descriptors',
'wallet_address_types.py',
+ 'wallet_address_types.py --descriptors',
'feature_bip68_sequence.py',
'p2p_feefilter.py',
'feature_reindex.py',
@@ -128,6 +138,7 @@ BASE_SCRIPTS = [
'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock',
'tool_wallet.py',
+ 'tool_wallet.py --descriptors',
'wallet_txn_clone.py',
'wallet_txn_clone.py --segwit',
'rpc_getchaintips.py',
@@ -139,11 +150,13 @@ BASE_SCRIPTS = [
'mempool_reorg.py',
'mempool_persist.py',
'wallet_multiwallet.py',
+ 'wallet_multiwallet.py --descriptors',
'wallet_multiwallet.py --usecli',
'wallet_createwallet.py',
'wallet_createwallet.py --usecli',
- 'wallet_watchonly.py',
- 'wallet_watchonly.py --usecli',
+ 'wallet_createwallet.py --descriptors',
+ 'wallet_watchonly.py --legacy-wallet',
+ 'wallet_watchonly.py --usecli --legacy-wallet',
'wallet_reorgsrestore.py',
'interface_http.py',
'interface_rpc.py',
@@ -153,19 +166,23 @@ BASE_SCRIPTS = [
'rpc_whitelist.py',
'feature_proxy.py',
'rpc_signrawtransaction.py',
+ 'rpc_signrawtransaction.py --descriptors',
'wallet_groups.py',
+ 'p2p_addrv2_relay.py',
+ 'wallet_groups.py --descriptors',
'p2p_disconnect_ban.py',
'rpc_decodescript.py',
'rpc_blockchain.py',
'rpc_deprecated.py',
'wallet_disable.py',
+ 'wallet_disable.py --descriptors',
'p2p_addr_relay.py',
'p2p_getaddr_caching.py',
'p2p_getdata.py',
'rpc_net.py',
'wallet_keypool.py',
'wallet_keypool.py --descriptors',
- 'wallet_descriptor.py',
+ 'wallet_descriptor.py --descriptors',
'p2p_nobloomfilter_messages.py',
'p2p_filter.py',
'rpc_setban.py',
@@ -178,7 +195,9 @@ BASE_SCRIPTS = [
'feature_assumevalid.py',
'example_test.py',
'wallet_txn_doublespend.py',
+ 'wallet_txn_doublespend.py --descriptors',
'feature_backwards_compatibility.py',
+ 'feature_backwards_compatibility.py --descriptors',
'wallet_txn_clone.py --mineblock',
'feature_notifications.py',
'rpc_getblockfilter.py',
@@ -191,18 +210,21 @@ BASE_SCRIPTS = [
'feature_versionbits_warning.py',
'rpc_preciousblock.py',
'wallet_importprunedfunds.py',
+ 'wallet_importprunedfunds.py --descriptors',
'p2p_leak_tx.py',
'p2p_eviction.py',
'rpc_signmessage.py',
'rpc_generateblock.py',
'rpc_generate.py',
'wallet_balance.py',
+ 'wallet_balance.py --descriptors',
'feature_nulldummy.py',
+ 'feature_nulldummy.py --descriptors',
'mempool_accept.py',
'mempool_expiry.py',
- 'wallet_import_rescan.py',
- 'wallet_import_with_label.py',
- 'wallet_importdescriptors.py',
+ 'wallet_import_rescan.py --legacy-wallet',
+ 'wallet_import_with_label.py --legacy-wallet',
+ 'wallet_importdescriptors.py --descriptors',
'wallet_upgradewallet.py',
'rpc_bind.py --ipv4',
'rpc_bind.py --ipv6',
@@ -210,9 +232,11 @@ BASE_SCRIPTS = [
'mining_basic.py',
'feature_signet.py',
'wallet_bumpfee.py',
- 'wallet_implicitsegwit.py',
+ 'wallet_bumpfee.py --descriptors',
+ 'wallet_implicitsegwit.py --legacy-wallet',
'rpc_named_arguments.py',
'wallet_listsinceblock.py',
+ 'wallet_listsinceblock.py --descriptors',
'p2p_leak.py',
'wallet_encryption.py',
'wallet_encryption.py --descriptors',
@@ -220,16 +244,20 @@ BASE_SCRIPTS = [
'feature_cltv.py',
'rpc_uptime.py',
'wallet_resendwallettransactions.py',
+ 'wallet_resendwallettransactions.py --descriptors',
'wallet_fallbackfee.py',
+ 'wallet_fallbackfee.py --descriptors',
'rpc_dumptxoutset.py',
'feature_minchainwork.py',
'rpc_estimatefee.py',
'rpc_getblockstats.py',
'wallet_create_tx.py',
'wallet_send.py',
+ 'wallet_create_tx.py --descriptors',
'p2p_fingerprint.py',
'feature_uacomment.py',
'wallet_coinbase_category.py',
+ 'wallet_coinbase_category.py --descriptors',
'feature_filelock.py',
'feature_loadblock.py',
'p2p_dos_header_tree.py',
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index fa5b5c10ff..615b772dc8 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -71,9 +71,12 @@ class ToolWalletTest(BitcoinTestFramework):
self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create')
self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')
locked_dir = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets")
+ error = 'Error initializing wallet database environment "{}"!'.format(locked_dir)
+ if self.options.descriptors:
+ error = "SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?"
self.assert_raises_tool_error(
- 'Error initializing wallet database environment "{}"!'.format(locked_dir),
- '-wallet=wallet.dat',
+ error,
+ '-wallet=' + self.default_wallet_name,
'info',
)
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "nonexistent.dat")
@@ -95,16 +98,34 @@ class ToolWalletTest(BitcoinTestFramework):
# shasum_before = self.wallet_shasum()
timestamp_before = self.wallet_timestamp()
self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
- out = textwrap.dedent('''\
- Wallet info
- ===========
- Encrypted: no
- HD (hd seed available): yes
- Keypool Size: 2
- Transactions: 0
- Address Book: 3
- ''')
- self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
+ if self.options.descriptors:
+ out = textwrap.dedent('''\
+ Wallet info
+ ===========
+ Name: default_wallet
+ Format: sqlite
+ Descriptors: yes
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 6
+ Transactions: 0
+ Address Book: 1
+ ''')
+ else:
+ out = textwrap.dedent('''\
+ Wallet info
+ ===========
+ Name: \
+
+ Format: bdb
+ Descriptors: no
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 2
+ Transactions: 0
+ Address Book: 3
+ ''')
+ self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
timestamp_after = self.wallet_timestamp()
self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
@@ -134,16 +155,34 @@ class ToolWalletTest(BitcoinTestFramework):
shasum_before = self.wallet_shasum()
timestamp_before = self.wallet_timestamp()
self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
- out = textwrap.dedent('''\
- Wallet info
- ===========
- Encrypted: no
- HD (hd seed available): yes
- Keypool Size: 2
- Transactions: 1
- Address Book: 3
- ''')
- self.assert_tool_output(out, '-wallet=wallet.dat', 'info')
+ if self.options.descriptors:
+ out = textwrap.dedent('''\
+ Wallet info
+ ===========
+ Name: default_wallet
+ Format: sqlite
+ Descriptors: yes
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 6
+ Transactions: 1
+ Address Book: 1
+ ''')
+ else:
+ out = textwrap.dedent('''\
+ Wallet info
+ ===========
+ Name: \
+
+ Format: bdb
+ Descriptors: no
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 2
+ Transactions: 1
+ Address Book: 3
+ ''')
+ self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
shasum_after = self.wallet_shasum()
timestamp_after = self.wallet_timestamp()
self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
@@ -164,6 +203,9 @@ class ToolWalletTest(BitcoinTestFramework):
Topping up keypool...
Wallet info
===========
+ Name: foo
+ Format: bdb
+ Descriptors: no
Encrypted: no
HD (hd seed available): yes
Keypool Size: 2000
@@ -181,7 +223,7 @@ class ToolWalletTest(BitcoinTestFramework):
def test_getwalletinfo_on_different_wallet(self):
self.log.info('Starting node with arg -wallet=foo')
- self.start_node(0, ['-wallet=foo'])
+ self.start_node(0, ['-nowallet', '-wallet=foo'])
self.log.info('Calling getwalletinfo on a different wallet ("foo"), testing output')
shasum_before = self.wallet_shasum()
@@ -207,20 +249,24 @@ class ToolWalletTest(BitcoinTestFramework):
def test_salvage(self):
# TODO: Check salvage actually salvages and doesn't break things. https://github.com/bitcoin/bitcoin/issues/7463
self.log.info('Check salvage')
- self.start_node(0, ['-wallet=salvage'])
+ self.start_node(0)
+ self.nodes[0].createwallet("salvage")
self.stop_node(0)
self.assert_tool_output('', '-wallet=salvage', 'salvage')
def run_test(self):
- self.wallet_path = os.path.join(self.nodes[0].datadir, self.chain, 'wallets', 'wallet.dat')
+ self.wallet_path = os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename)
self.test_invalid_tool_commands_and_args()
# Warning: The following tests are order-dependent.
self.test_tool_wallet_info()
self.test_tool_wallet_info_after_transaction()
- self.test_tool_wallet_create_on_existing_wallet()
- self.test_getwalletinfo_on_different_wallet()
- self.test_salvage()
+ if not self.options.descriptors:
+ # TODO: Wallet tool needs more create options at which point these can be enabled.
+ self.test_tool_wallet_create_on_existing_wallet()
+ self.test_getwalletinfo_on_different_wallet()
+ # Salvage is a legacy wallet only thing
+ self.test_salvage()
if __name__ == '__main__':
ToolWalletTest().main()
diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py
index 8837e13005..2e0edcfa38 100755
--- a/test/functional/wallet_abandonconflict.py
+++ b/test/functional/wallet_abandonconflict.py
@@ -16,8 +16,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
- disconnect_nodes,
)
@@ -50,7 +48,7 @@ class AbandonConflictTest(BitcoinTestFramework):
balance = newbalance
# Disconnect nodes so node0's transactions don't get into node1's mempool
- disconnect_nodes(self.nodes[0], 1)
+ self.disconnect_nodes(0, 1)
# Identify the 10btc outputs
nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10"))
@@ -161,7 +159,7 @@ class AbandonConflictTest(BitcoinTestFramework):
self.nodes[1].sendrawtransaction(signed["hex"])
self.nodes[1].generate(1)
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_blocks()
# Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted
diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py
index 68e22b7e86..2db5eae33b 100755
--- a/test/functional/wallet_address_types.py
+++ b/test/functional/wallet_address_types.py
@@ -62,11 +62,6 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
- connect_nodes,
-)
-from test_framework.segwit_addr import (
- encode,
- decode,
)
class AddressTypeTest(BitcoinTestFramework):
@@ -94,20 +89,13 @@ class AddressTypeTest(BitcoinTestFramework):
# Fully mesh-connect nodes for faster mempool sync
for i, j in itertools.product(range(self.num_nodes), repeat=2):
if i > j:
- connect_nodes(self.nodes[i], j)
+ self.connect_nodes(i, j)
self.sync_all()
def get_balances(self, key='trusted'):
"""Return a list of balances."""
return [self.nodes[i].getbalances()['mine'][key] for i in range(4)]
- # Quick test of python bech32 implementation
- def test_python_bech32(self, addr):
- hrp = addr[:4]
- assert_equal(hrp, "bcrt")
- (witver, witprog) = decode(hrp, addr)
- assert_equal(encode(hrp, witver, witprog), addr)
-
def test_address(self, node, address, multisig, typ):
"""Run sanity checks on an address."""
info = self.nodes[node].getaddressinfo(address)
@@ -132,7 +120,6 @@ class AddressTypeTest(BitcoinTestFramework):
assert_equal(info['witness_version'], 0)
assert_equal(len(info['witness_program']), 40)
assert 'pubkey' in info
- self.test_python_bech32(info["address"])
elif typ == 'legacy':
# P2SH-multisig
assert info['isscript']
@@ -158,7 +145,6 @@ class AddressTypeTest(BitcoinTestFramework):
assert_equal(info['witness_version'], 0)
assert_equal(len(info['witness_program']), 64)
assert 'pubkeys' in info
- self.test_python_bech32(info["address"])
else:
# Unknown type
assert False
@@ -242,18 +228,25 @@ class AddressTypeTest(BitcoinTestFramework):
compressed_1 = "0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"
compressed_2 = "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"
- # addmultisigaddress with at least 1 uncompressed key should return a legacy address.
- for node in range(4):
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2])['address'], True, 'legacy')
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2])['address'], True, 'legacy')
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2])['address'], True, 'legacy')
- # addmultisigaddress with all compressed keys should return the appropriate address type (even when the keys are not ours).
- self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'legacy')
- self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
- self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
- self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'bech32')
-
- for explicit_type, multisig, from_node in itertools.product([False, True], [False, True], range(4)):
+ if not self.options.descriptors:
+ # Tests for addmultisigaddress's address type behavior is only for legacy wallets.
+ # Descriptor wallets do not have addmultsigaddress so these tests are not needed for those.
+ # addmultisigaddress with at least 1 uncompressed key should return a legacy address.
+ for node in range(4):
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2])['address'], True, 'legacy')
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2])['address'], True, 'legacy')
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2])['address'], True, 'legacy')
+ # addmultisigaddress with all compressed keys should return the appropriate address type (even when the keys are not ours).
+ self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'legacy')
+ self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
+ self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
+ self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'bech32')
+
+ do_multisigs = [False]
+ if not self.options.descriptors:
+ do_multisigs.append(True)
+
+ for explicit_type, multisig, from_node in itertools.product([False, True], do_multisigs, range(4)):
address_type = None
if explicit_type and not multisig:
if from_node == 1:
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
index eddd938847..229c134a4b 100755
--- a/test/functional/wallet_avoidreuse.py
+++ b/test/functional/wallet_avoidreuse.py
@@ -9,7 +9,6 @@ from test_framework.util import (
assert_approx,
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
)
def reset_balance(node, discardaddr):
@@ -111,7 +110,7 @@ class AvoidReuseTest(BitcoinTestFramework):
assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
self.restart_node(1)
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
# Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true
assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py
index bcbac18d57..f34c1345e0 100755
--- a/test/functional/wallet_backup.py
+++ b/test/functional/wallet_backup.py
@@ -39,7 +39,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
)
@@ -50,10 +49,10 @@ class WalletBackupTest(BitcoinTestFramework):
# nodes 1, 2,3 are spenders, let's give them a keypool=100
# whitelist all peers to speed up tx relay / mempool sync
self.extra_args = [
- ["-whitelist=noban@127.0.0.1", "-keypool=100", "-wallet="],
- ["-whitelist=noban@127.0.0.1", "-keypool=100", "-wallet="],
- ["-whitelist=noban@127.0.0.1", "-keypool=100", "-wallet="],
- ["-whitelist=noban@127.0.0.1", "-wallet="],
+ ["-whitelist=noban@127.0.0.1", "-keypool=100"],
+ ["-whitelist=noban@127.0.0.1", "-keypool=100"],
+ ["-whitelist=noban@127.0.0.1", "-keypool=100"],
+ ["-whitelist=noban@127.0.0.1"],
]
self.rpc_timeout = 120
@@ -62,10 +61,10 @@ class WalletBackupTest(BitcoinTestFramework):
def setup_network(self):
self.setup_nodes()
- connect_nodes(self.nodes[0], 3)
- connect_nodes(self.nodes[1], 3)
- connect_nodes(self.nodes[2], 3)
- connect_nodes(self.nodes[2], 0)
+ self.connect_nodes(0, 3)
+ self.connect_nodes(1, 3)
+ self.connect_nodes(2, 3)
+ self.connect_nodes(2, 0)
self.sync_all()
def one_send(self, from_node, to_address):
@@ -92,14 +91,14 @@ class WalletBackupTest(BitcoinTestFramework):
self.sync_blocks()
# As above, this mirrors the original bash test.
- def start_three(self):
- self.start_node(0)
- self.start_node(1)
- self.start_node(2)
- connect_nodes(self.nodes[0], 3)
- connect_nodes(self.nodes[1], 3)
- connect_nodes(self.nodes[2], 3)
- connect_nodes(self.nodes[2], 0)
+ def start_three(self, args=()):
+ self.start_node(0, self.extra_args[0] + list(args))
+ self.start_node(1, self.extra_args[1] + list(args))
+ self.start_node(2, self.extra_args[2] + list(args))
+ self.connect_nodes(0, 3)
+ self.connect_nodes(1, 3)
+ self.connect_nodes(2, 3)
+ self.connect_nodes(2, 0)
def stop_three(self):
self.stop_node(0)
@@ -107,9 +106,14 @@ class WalletBackupTest(BitcoinTestFramework):
self.stop_node(2)
def erase_three(self):
- os.remove(os.path.join(self.nodes[0].datadir, self.chain, 'wallets', 'wallet.dat'))
- os.remove(os.path.join(self.nodes[1].datadir, self.chain, 'wallets', 'wallet.dat'))
- os.remove(os.path.join(self.nodes[2].datadir, self.chain, 'wallets', 'wallet.dat'))
+ os.remove(os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
+ os.remove(os.path.join(self.nodes[1].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
+ os.remove(os.path.join(self.nodes[2].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
+
+ def init_three(self):
+ self.init_wallet(0)
+ self.init_wallet(1)
+ self.init_wallet(2)
def run_test(self):
self.log.info("Generating initial blockchain")
@@ -135,11 +139,13 @@ class WalletBackupTest(BitcoinTestFramework):
self.log.info("Backing up")
self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, 'wallet.bak'))
- self.nodes[0].dumpwallet(os.path.join(self.nodes[0].datadir, 'wallet.dump'))
self.nodes[1].backupwallet(os.path.join(self.nodes[1].datadir, 'wallet.bak'))
- self.nodes[1].dumpwallet(os.path.join(self.nodes[1].datadir, 'wallet.dump'))
self.nodes[2].backupwallet(os.path.join(self.nodes[2].datadir, 'wallet.bak'))
- self.nodes[2].dumpwallet(os.path.join(self.nodes[2].datadir, 'wallet.dump'))
+
+ if not self.options.descriptors:
+ self.nodes[0].dumpwallet(os.path.join(self.nodes[0].datadir, 'wallet.dump'))
+ self.nodes[1].dumpwallet(os.path.join(self.nodes[1].datadir, 'wallet.dump'))
+ self.nodes[2].dumpwallet(os.path.join(self.nodes[2].datadir, 'wallet.dump'))
self.log.info("More transactions")
for _ in range(5):
@@ -171,9 +177,9 @@ class WalletBackupTest(BitcoinTestFramework):
shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'chainstate'))
# Restore wallets from backup
- shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[0].datadir, self.chain, 'wallets', 'wallet.dat'))
- shutil.copyfile(os.path.join(self.nodes[1].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, self.chain, 'wallets', 'wallet.dat'))
- shutil.copyfile(os.path.join(self.nodes[2].datadir, 'wallet.bak'), os.path.join(self.nodes[2].datadir, self.chain, 'wallets', 'wallet.dat'))
+ shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
+ shutil.copyfile(os.path.join(self.nodes[1].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
+ shutil.copyfile(os.path.join(self.nodes[2].datadir, 'wallet.bak'), os.path.join(self.nodes[2].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename))
self.log.info("Re-starting nodes")
self.start_three()
@@ -183,35 +189,37 @@ class WalletBackupTest(BitcoinTestFramework):
assert_equal(self.nodes[1].getbalance(), balance1)
assert_equal(self.nodes[2].getbalance(), balance2)
- self.log.info("Restoring using dumped wallet")
- self.stop_three()
- self.erase_three()
+ if not self.options.descriptors:
+ self.log.info("Restoring using dumped wallet")
+ self.stop_three()
+ self.erase_three()
- #start node2 with no chain
- shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'blocks'))
- shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'chainstate'))
+ #start node2 with no chain
+ shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'blocks'))
+ shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'chainstate'))
- self.start_three()
+ self.start_three(["-nowallet"])
+ self.init_three()
- assert_equal(self.nodes[0].getbalance(), 0)
- assert_equal(self.nodes[1].getbalance(), 0)
- assert_equal(self.nodes[2].getbalance(), 0)
+ assert_equal(self.nodes[0].getbalance(), 0)
+ assert_equal(self.nodes[1].getbalance(), 0)
+ assert_equal(self.nodes[2].getbalance(), 0)
- self.nodes[0].importwallet(os.path.join(self.nodes[0].datadir, 'wallet.dump'))
- self.nodes[1].importwallet(os.path.join(self.nodes[1].datadir, 'wallet.dump'))
- self.nodes[2].importwallet(os.path.join(self.nodes[2].datadir, 'wallet.dump'))
+ self.nodes[0].importwallet(os.path.join(self.nodes[0].datadir, 'wallet.dump'))
+ self.nodes[1].importwallet(os.path.join(self.nodes[1].datadir, 'wallet.dump'))
+ self.nodes[2].importwallet(os.path.join(self.nodes[2].datadir, 'wallet.dump'))
- self.sync_blocks()
+ self.sync_blocks()
- assert_equal(self.nodes[0].getbalance(), balance0)
- assert_equal(self.nodes[1].getbalance(), balance1)
- assert_equal(self.nodes[2].getbalance(), balance2)
+ assert_equal(self.nodes[0].getbalance(), balance0)
+ assert_equal(self.nodes[1].getbalance(), balance1)
+ assert_equal(self.nodes[2].getbalance(), balance2)
# Backup to source wallet file must fail
sourcePaths = [
- os.path.join(self.nodes[0].datadir, self.chain, 'wallets', 'wallet.dat'),
- os.path.join(self.nodes[0].datadir, self.chain, '.', 'wallets', 'wallet.dat'),
- os.path.join(self.nodes[0].datadir, self.chain, 'wallets', ''),
+ os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename),
+ os.path.join(self.nodes[0].datadir, self.chain, '.', 'wallets', self.default_wallet_name, self.wallet_data_filename),
+ os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name),
os.path.join(self.nodes[0].datadir, self.chain, 'wallets')]
for sourcePath in sourcePaths:
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 31829a18b3..433b40faee 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -11,7 +11,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
)
@@ -58,14 +57,16 @@ class WalletTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def run_test(self):
- self.nodes[0].importaddress(ADDRESS_WATCHONLY)
- # Check that nodes don't own any UTXOs
- assert_equal(len(self.nodes[0].listunspent()), 0)
- assert_equal(len(self.nodes[1].listunspent()), 0)
+ if not self.options.descriptors:
+ # Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets
+ self.nodes[0].importaddress(ADDRESS_WATCHONLY)
+ # Check that nodes don't own any UTXOs
+ assert_equal(len(self.nodes[0].listunspent()), 0)
+ assert_equal(len(self.nodes[1].listunspent()), 0)
- self.log.info("Check that only node 0 is watching an address")
- assert 'watchonly' in self.nodes[0].getbalances()
- assert 'watchonly' not in self.nodes[1].getbalances()
+ self.log.info("Check that only node 0 is watching an address")
+ assert 'watchonly' in self.nodes[0].getbalances()
+ assert 'watchonly' not in self.nodes[1].getbalances()
self.log.info("Mining blocks ...")
self.nodes[0].generate(1)
@@ -74,22 +75,28 @@ class WalletTest(BitcoinTestFramework):
self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY)
self.sync_all()
- assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50)
- assert_equal(self.nodes[0].getwalletinfo()['balance'], 50)
- assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50)
+ if not self.options.descriptors:
+ # Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets
+ assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50)
+ assert_equal(self.nodes[0].getwalletinfo()['balance'], 50)
+ assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50)
- assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 5000)
- assert 'watchonly' not in self.nodes[1].getbalances()
+ assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 5000)
+ assert 'watchonly' not in self.nodes[1].getbalances()
- assert_equal(self.nodes[0].getbalance(), 50)
- assert_equal(self.nodes[1].getbalance(), 50)
+ assert_equal(self.nodes[0].getbalance(), 50)
+ assert_equal(self.nodes[1].getbalance(), 50)
self.log.info("Test getbalance with different arguments")
assert_equal(self.nodes[0].getbalance("*"), 50)
assert_equal(self.nodes[0].getbalance("*", 1), 50)
- assert_equal(self.nodes[0].getbalance("*", 1, True), 100)
assert_equal(self.nodes[0].getbalance(minconf=1), 50)
- assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 100)
+ if not self.options.descriptors:
+ assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 100)
+ assert_equal(self.nodes[0].getbalance("*", 1, True), 100)
+ else:
+ assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 50)
+ assert_equal(self.nodes[0].getbalance("*", 1, True), 50)
assert_equal(self.nodes[1].getbalance(minconf=0, include_watchonly=True), 50)
# Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0.
@@ -157,6 +164,8 @@ class WalletTest(BitcoinTestFramework):
expected_balances_1 = {'mine': {'immature': Decimal('0E-8'),
'trusted': Decimal('0E-8'), # node 1's send had an unsafe input
'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent
+ if self.options.descriptors:
+ del expected_balances_0["watchonly"]
assert_equal(self.nodes[0].getbalances(), expected_balances_0)
assert_equal(self.nodes[1].getbalances(), expected_balances_1)
# getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
@@ -215,10 +224,10 @@ class WalletTest(BitcoinTestFramework):
# dynamically loading the wallet.
before = self.nodes[1].getbalances()['mine']['untrusted_pending']
dst = self.nodes[1].getnewaddress()
- self.nodes[1].unloadwallet('')
+ self.nodes[1].unloadwallet(self.default_wallet_name)
self.nodes[0].sendtoaddress(dst, 0.1)
self.sync_all()
- self.nodes[1].loadwallet('')
+ self.nodes[1].loadwallet(self.default_wallet_name)
after = self.nodes[1].getbalances()['mine']['untrusted_pending']
assert_equal(before + Decimal('0.1'), after)
@@ -254,15 +263,13 @@ class WalletTest(BitcoinTestFramework):
self.log.info('Put txs back into mempool of node 1 (not node 0)')
self.nodes[0].invalidateblock(block_reorg)
self.nodes[1].invalidateblock(block_reorg)
- self.sync_blocks()
- self.nodes[0].syncwithvalidationinterfacequeue()
assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted
self.nodes[0].generatetoaddress(1, ADDRESS_WATCHONLY)
assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted
# Now confirm tx_orig
self.restart_node(1, ['-persistmempool=0'])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_blocks()
self.nodes[1].sendrawtransaction(tx_orig)
self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY)
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index bb208341a0..ac4a6e4948 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the wallet."""
from decimal import Decimal
+from itertools import product
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -11,10 +12,11 @@ from test_framework.util import (
assert_equal,
assert_fee_amount,
assert_raises_rpc_error,
- connect_nodes,
)
from test_framework.wallet_util import test_address
+OUT_OF_RANGE = "Amount out of range"
+
class WalletTest(BitcoinTestFramework):
def set_test_params(self):
@@ -32,9 +34,9 @@ class WalletTest(BitcoinTestFramework):
self.setup_nodes()
# Only need nodes 0-2 running at start of test
self.stop_node(3)
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 2)
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 2)
+ self.connect_nodes(0, 2)
self.sync_all(self.nodes[0:3])
def check_fee_amount(self, curr_balance, balance_with_fee, fee_per_byte, tx_size):
@@ -75,7 +77,7 @@ class WalletTest(BitcoinTestFramework):
assert_equal(len(self.nodes[1].listunspent()), 1)
assert_equal(len(self.nodes[2].listunspent()), 0)
- self.log.info("test gettxout")
+ self.log.info("Test gettxout")
confirmed_txid, confirmed_index = utxos[0]["txid"], utxos[0]["vout"]
# First, outputs that are unspent both in the chain and in the
# mempool should appear with or without include_mempool
@@ -88,7 +90,7 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
mempool_txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
- self.log.info("test gettxout (second part)")
+ self.log.info("Test gettxout (second part)")
# utxo spent in mempool should be visible if you exclude mempool
# but invisible if you include mempool
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False)
@@ -210,6 +212,8 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ self.log.info("Test sendmany")
+
# Sendmany 10 BTC
txid = self.nodes[2].sendmany('', {address: 10}, 0, "", [])
self.nodes[2].generate(1)
@@ -226,62 +230,43 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
- # Sendmany with explicit fee (BTC/kB)
- # Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
- self.nodes[2].sendmany,
- amounts={ address: 10 },
- estimate_mode='bTc/kB')
- # Throw if negative feerate
- assert_raises_rpc_error(-3, "Amount out of range",
- self.nodes[2].sendmany,
- amounts={ address: 10 },
- conf_target=-1,
- estimate_mode='bTc/kB')
- fee_per_kb = 0.0002500
- explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
- txid = self.nodes[2].sendmany(
- amounts={ address: 10 },
- conf_target=fee_per_kb,
- estimate_mode='bTc/kB',
- )
- self.nodes[2].generate(1)
- self.sync_all(self.nodes[0:3])
- node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
- assert_equal(self.nodes[2].getbalance(), node_2_bal)
- node_0_bal += Decimal('10')
- assert_equal(self.nodes[0].getbalance(), node_0_bal)
+ self.log.info("Test sendmany with fee_rate param (explicit fee rate in sat/vB)")
+ fee_rate_sat_vb = 2
+ fee_rate_btc_kvb = fee_rate_sat_vb * 1e3 / 1e8
+ explicit_fee_rate_btc_kvb = Decimal(fee_rate_btc_kvb) / 1000
- # Sendmany with explicit fee (SAT/B)
- # Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
- self.nodes[2].sendmany,
- amounts={ address: 10 },
- estimate_mode='sat/b')
- # Throw if negative feerate
- assert_raises_rpc_error(-3, "Amount out of range",
- self.nodes[2].sendmany,
- amounts={ address: 10 },
- conf_target=-1,
- estimate_mode='sat/b')
- fee_sat_per_b = 2
- fee_per_kb = fee_sat_per_b / 100000.0
- explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
- txid = self.nodes[2].sendmany(
- amounts={ address: 10 },
- conf_target=fee_sat_per_b,
- estimate_mode='sAT/b',
- )
+ txid = self.nodes[2].sendmany(amounts={address: 10}, fee_rate=fee_rate_sat_vb)
self.nodes[2].generate(1)
self.sync_all(self.nodes[0:3])
balance = self.nodes[2].getbalance()
- node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_rate_btc_kvb, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
assert_equal(balance, node_2_bal)
node_0_bal += Decimal('10')
assert_equal(self.nodes[0].getbalance(), node_0_bal)
+ for key in ["totalFee", "feeRate"]:
+ assert_raises_rpc_error(-8, "Unknown named parameter key", self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=1, key=1)
+
+ # Test setting explicit fee rate just below the minimum.
+ self.log.info("Test sendmany raises 'fee rate too low' if fee_rate of 0.99999999 is passed")
+ assert_raises_rpc_error(-6, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
+ self.nodes[2].sendmany, amounts={address: 10}, fee_rate=0.99999999)
+
+ self.log.info("Test sendmany raises if fee_rate of 0 or -1 is passed")
+ assert_raises_rpc_error(-6, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
+ self.nodes[2].sendmany, amounts={address: 10}, fee_rate=0)
+ assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendmany, amounts={address: 10}, fee_rate=-1)
+
+ self.log.info("Test sendmany raises if an invalid conf_target or estimate_mode is passed")
+ for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
+ self.nodes[2].sendmany, amounts={address: 1}, conf_target=target, estimate_mode=mode)
+ for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ self.nodes[2].sendmany, amounts={address: 1}, conf_target=target, estimate_mode=mode)
+
self.start_node(3, self.nodes[3].extra_args)
- connect_nodes(self.nodes[0], 3)
+ self.connect_nodes(0, 3)
self.sync_all()
# check if we can list zero value tx as available coins
@@ -311,14 +296,14 @@ class WalletTest(BitcoinTestFramework):
assert_equal(uTx['amount'], Decimal('0'))
assert found
- # do some -walletbroadcast tests
+ self.log.info("Test -walletbroadcast")
self.stop_nodes()
self.start_node(0, ["-walletbroadcast=0"])
self.start_node(1, ["-walletbroadcast=0"])
self.start_node(2, ["-walletbroadcast=0"])
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 2)
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 2)
+ self.connect_nodes(0, 2)
self.sync_all(self.nodes[0:3])
txid_not_broadcast = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2)
@@ -343,9 +328,9 @@ class WalletTest(BitcoinTestFramework):
self.start_node(0)
self.start_node(1)
self.start_node(2)
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[1], 2)
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 2)
+ self.connect_nodes(0, 2)
self.sync_blocks(self.nodes[0:3])
self.nodes[0].generate(1)
@@ -371,7 +356,7 @@ class WalletTest(BitcoinTestFramework):
# General checks for errors from incorrect inputs
# This will raise an exception because the amount is negative
- assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
+ assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
# This will raise an exception because the amount type is wrong
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
@@ -413,73 +398,42 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
- # send with explicit btc/kb fee
- self.log.info("test explicit fee (sendtoaddress as btc/kb)")
- self.nodes[0].generate(1)
- self.sync_all(self.nodes[0:3])
+ self.log.info("Test sendtoaddress with fee_rate param (explicit fee rate in sat/vB)")
prebalance = self.nodes[2].getbalance()
assert prebalance > 2
address = self.nodes[1].getnewaddress()
- # Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
- self.nodes[2].sendtoaddress,
- address=address,
- amount=1.0,
- estimate_mode='BTc/Kb')
- # Throw if negative feerate
- assert_raises_rpc_error(-3, "Amount out of range",
- self.nodes[2].sendtoaddress,
- address=address,
- amount=1.0,
- conf_target=-1,
- estimate_mode='btc/kb')
- txid = self.nodes[2].sendtoaddress(
- address=address,
- amount=1.0,
- conf_target=0.00002500,
- estimate_mode='btc/kb',
- )
- tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
- self.sync_all(self.nodes[0:3])
- self.nodes[0].generate(1)
- self.sync_all(self.nodes[0:3])
- postbalance = self.nodes[2].getbalance()
- fee = prebalance - postbalance - Decimal('1')
- assert_fee_amount(fee, tx_size, Decimal('0.00002500'))
+ amount = 3
+ fee_rate_sat_vb = 2
+ fee_rate_btc_kvb = fee_rate_sat_vb * 1e3 / 1e8
- # send with explicit sat/b fee
- self.sync_all(self.nodes[0:3])
- self.log.info("test explicit fee (sendtoaddress as sat/b)")
- self.nodes[0].generate(1)
- prebalance = self.nodes[2].getbalance()
- assert prebalance > 2
- address = self.nodes[1].getnewaddress()
- # Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
- self.nodes[2].sendtoaddress,
- address=address,
- amount=1.0,
- estimate_mode='SAT/b')
- # Throw if negative feerate
- assert_raises_rpc_error(-3, "Amount out of range",
- self.nodes[2].sendtoaddress,
- address=address,
- amount=1.0,
- conf_target=-1,
- estimate_mode='SAT/b')
- txid = self.nodes[2].sendtoaddress(
- address=address,
- amount=1.0,
- conf_target=2,
- estimate_mode='SAT/B',
- )
+ txid = self.nodes[2].sendtoaddress(address=address, amount=amount, fee_rate=fee_rate_sat_vb)
tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
- self.sync_all(self.nodes[0:3])
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
postbalance = self.nodes[2].getbalance()
- fee = prebalance - postbalance - Decimal('1')
- assert_fee_amount(fee, tx_size, Decimal('0.00002000'))
+ fee = prebalance - postbalance - Decimal(amount)
+ assert_fee_amount(fee, tx_size, Decimal(fee_rate_btc_kvb))
+
+ for key in ["totalFee", "feeRate"]:
+ assert_raises_rpc_error(-8, "Unknown named parameter key", self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=1, key=1)
+
+ # Test setting explicit fee rate just below the minimum.
+ self.log.info("Test sendtoaddress raises 'fee rate too low' if fee_rate of 0.99999999 is passed")
+ assert_raises_rpc_error(-6, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
+ self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=0.99999999)
+
+ self.log.info("Test sendtoaddress raises if fee_rate of 0 or -1 is passed")
+ assert_raises_rpc_error(-6, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
+ self.nodes[2].sendtoaddress, address=address, amount=10, fee_rate=0)
+ assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendtoaddress, address=address, amount=1.0, fee_rate=-1)
+
+ self.log.info("Test sendtoaddress raises if an invalid conf_target or estimate_mode is passed")
+ for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
+ self.nodes[2].sendtoaddress, address=address, amount=1, conf_target=target, estimate_mode=mode)
+ for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ self.nodes[2].sendtoaddress, address=address, amount=1, conf_target=target, estimate_mode=mode)
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)
@@ -537,7 +491,7 @@ class WalletTest(BitcoinTestFramework):
]
chainlimit = 6
for m in maintenance:
- self.log.info("check " + m)
+ self.log.info("Test " + m)
self.stop_nodes()
# set lower ancestor limit for later
self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)])
@@ -661,6 +615,18 @@ class WalletTest(BitcoinTestFramework):
assert_array_result(tx["details"], {"category": "receive"}, expected_receive_vout)
assert_equal(tx[verbose_field], self.nodes[0].decoderawtransaction(tx["hex"]))
+ self.log.info("Test send* RPCs with verbose=True")
+ address = self.nodes[0].getnewaddress("test")
+ txid_feeReason_one = self.nodes[2].sendtoaddress(address=address, amount=5, verbose=True)
+ assert_equal(txid_feeReason_one["fee_reason"], "Fallback fee")
+ txid_feeReason_two = self.nodes[2].sendmany(dummy='', amounts={address: 5}, verbose=True)
+ assert_equal(txid_feeReason_two["fee_reason"], "Fallback fee")
+ self.log.info("Test send* RPCs with verbose=False")
+ txid_feeReason_three = self.nodes[2].sendtoaddress(address=address, amount=5, verbose=False)
+ assert_equal(self.nodes[2].gettransaction(txid_feeReason_three)['txid'], txid_feeReason_three)
+ txid_feeReason_four = self.nodes[2].sendmany(dummy='', amounts={address: 5}, verbose=False)
+ assert_equal(self.nodes[2].gettransaction(txid_feeReason_four)['txid'], txid_feeReason_four)
+
if __name__ == '__main__':
WalletTest().main()
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 56d1da60b7..99c9737258 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -29,12 +29,12 @@ from test_framework.util import (
WALLET_PASSPHRASE = "test"
WALLET_PASSPHRASE_TIMEOUT = 3600
-# Fee rates (in BTC per 1000 vbytes)
-INSUFFICIENT = 0.00001000
-ECONOMICAL = 0.00050000
-NORMAL = 0.00100000
-HIGH = 0.00500000
-TOO_HIGH = 1.00000000
+# Fee rates (sat/vB)
+INSUFFICIENT = 1
+ECONOMICAL = 50
+NORMAL = 100
+HIGH = 500
+TOO_HIGH = 100000
class BumpFeeTest(BitcoinTestFramework):
@@ -76,10 +76,9 @@ class BumpFeeTest(BitcoinTestFramework):
self.log.info("Running tests")
dest_address = peer_node.getnewaddress()
- self.test_invalid_parameters(rbf_node, dest_address)
- test_simple_bumpfee_succeeds(self, "default", rbf_node, peer_node, dest_address)
- test_simple_bumpfee_succeeds(self, "fee_rate", rbf_node, peer_node, dest_address)
- test_feerate_args(self, rbf_node, peer_node, dest_address)
+ for mode in ["default", "fee_rate"]:
+ test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address)
+ self.test_invalid_parameters(rbf_node, peer_node, dest_address)
test_segwit_bumpfee_succeeds(self, rbf_node, dest_address)
test_nonrbf_bumpfee_fails(self, peer_node, dest_address)
test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address)
@@ -98,28 +97,48 @@ class BumpFeeTest(BitcoinTestFramework):
test_small_output_with_feerate_succeeds(self, rbf_node, dest_address)
test_no_more_inputs_fails(self, rbf_node, dest_address)
- def test_invalid_parameters(self, node, dest_address):
- txid = spend_one_input(node, dest_address)
- # invalid estimate mode
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", node.bumpfee, txid, {
- "estimate_mode": "moo",
- })
- assert_raises_rpc_error(-3, "Expected type string", node.bumpfee, txid, {
- "estimate_mode": 38,
- })
- assert_raises_rpc_error(-3, "Expected type string", node.bumpfee, txid, {
- "estimate_mode": {
- "foo": "bar",
- },
- })
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", node.bumpfee, txid, {
- "estimate_mode": Decimal("3.141592"),
- })
- # confTarget and conf_target
- assert_raises_rpc_error(-8, "confTarget and conf_target options should not both be set", node.bumpfee, txid, {
- "confTarget": 123,
- "conf_target": 456,
- })
+ def test_invalid_parameters(self, rbf_node, peer_node, dest_address):
+ self.log.info('Test invalid parameters')
+ rbfid = spend_one_input(rbf_node, dest_address)
+ self.sync_mempools((rbf_node, peer_node))
+ assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
+
+ for key in ["totalFee", "feeRate"]:
+ assert_raises_rpc_error(-3, "Unexpected key {}".format(key), rbf_node.bumpfee, rbfid, {key: NORMAL})
+
+ # Bumping to just above minrelay should fail to increase the total fee enough.
+ assert_raises_rpc_error(-8, "Insufficient total fee 0.00000141", rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
+
+ self.log.info("Test invalid fee rate settings")
+ assert_raises_rpc_error(-8, "Insufficient total fee 0.00", rbf_node.bumpfee, rbfid, {"fee_rate": 0})
+ assert_raises_rpc_error(-4, "Specified or calculated fee 0.141 is too high (cannot be higher than -maxtxfee 0.10",
+ rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH})
+ assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, {"fee_rate": -1})
+ for value in [{"foo": "bar"}, True]:
+ assert_raises_rpc_error(-3, "Amount is not a number or string", rbf_node.bumpfee, rbfid, {"fee_rate": value})
+ assert_raises_rpc_error(-3, "Invalid amount", rbf_node.bumpfee, rbfid, {"fee_rate": ""})
+
+ self.log.info("Test explicit fee rate raises RPC error if both fee_rate and conf_target are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both conf_target and fee_rate. Please provide either a confirmation "
+ "target in blocks for automatic fee estimation, or an explicit fee rate.",
+ rbf_node.bumpfee, rbfid, {"conf_target": NORMAL, "fee_rate": NORMAL})
+
+ self.log.info("Test explicit fee rate raises RPC error if both fee_rate and estimate_mode are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and fee_rate",
+ rbf_node.bumpfee, rbfid, {"estimate_mode": "economical", "fee_rate": NORMAL})
+
+ self.log.info("Test invalid conf_target settings")
+ assert_raises_rpc_error(-8, "confTarget and conf_target options should not both be set",
+ rbf_node.bumpfee, rbfid, {"confTarget": 123, "conf_target": 456})
+
+ self.log.info("Test invalid estimate_mode settings")
+ for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
+ rbf_node.bumpfee, rbfid, {"estimate_mode": v})
+ for mode in ["foo", Decimal("3.1415"), "sat/B", "BTC/kB"]:
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ rbf_node.bumpfee, rbfid, {"estimate_mode": mode})
+
self.clear_mempool()
@@ -158,33 +177,13 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
self.clear_mempool()
-def test_feerate_args(self, rbf_node, peer_node, dest_address):
- self.log.info('Test fee_rate args')
- rbfid = spend_one_input(rbf_node, dest_address)
- self.sync_mempools((rbf_node, peer_node))
- assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
-
- assert_raises_rpc_error(-8, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL, "confTarget": 1})
-
- assert_raises_rpc_error(-3, "Unexpected key totalFee", rbf_node.bumpfee, rbfid, {"totalFee": NORMAL})
- assert_raises_rpc_error(-8, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate":0.00001, "confTarget": 1})
-
- # Bumping to just above minrelay should fail to increase total fee enough, at least
- assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
-
- assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, {"fee_rate": -1})
-
- assert_raises_rpc_error(-4, "is too high (cannot be higher than", rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH})
- self.clear_mempool()
-
-
def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address):
self.log.info('Test that segwit-sourcing bumpfee works')
# Create a transaction with segwit output, then create an RBF transaction
# which spends it, and make sure bumpfee can be called on it.
segwit_in = next(u for u in rbf_node.listunspent() if u["amount"] == Decimal("0.001"))
- segwit_out = rbf_node.getaddressinfo(rbf_node.getnewaddress(address_type='p2sh-segwit'))
+ segwit_out = rbf_node.getaddressinfo(rbf_node.getnewaddress(address_type='bech32'))
segwitid = send_to_witness(
use_p2wsh=False,
node=rbf_node,
@@ -303,11 +302,11 @@ def test_dust_to_fee(self, rbf_node, dest_address):
# boundary. Thus expected transaction size (p2wpkh, 1 input, 2 outputs) is 140-141 vbytes, usually 141.
if not 140 <= fulltx["vsize"] <= 141:
raise AssertionError("Invalid tx vsize of {} (140-141 expected), full tx: {}".format(fulltx["vsize"], fulltx))
- # Bump with fee_rate of 0.00350250 BTC per 1000 vbytes to create dust.
+ # Bump with fee_rate of 350.25 sat/vB vbytes to create dust.
# Expected fee is 141 vbytes * fee_rate 0.00350250 BTC / 1000 vbytes = 0.00049385 BTC.
# or occasionally 140 vbytes * fee_rate 0.00350250 BTC / 1000 vbytes = 0.00049035 BTC.
# Dust should be dropped to the fee, so actual bump fee is 0.00050000 BTC.
- bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": 0.00350250})
+ bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": 350.25})
full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1)
assert_equal(bumped_tx["fee"], Decimal("0.00050000"))
assert_equal(len(fulltx["vout"]), 2)
@@ -348,7 +347,7 @@ def test_maxtxfee_fails(self, rbf_node, dest_address):
self.restart_node(1, ['-maxtxfee=0.000025'] + self.extra_args[1])
rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
rbfid = spend_one_input(rbf_node, dest_address)
- assert_raises_rpc_error(-4, "Unable to create transaction. Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid)
+ assert_raises_rpc_error(-4, "Unable to create transaction. Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", rbf_node.bumpfee, rbfid)
self.restart_node(1, self.extra_args[1])
rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
self.connect_nodes(1, 0)
@@ -365,7 +364,7 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
rbf_node.createwallet(wallet_name="signer", disable_private_keys=False, blank=True)
signer = rbf_node.get_wallet_rpc("signer")
assert signer.getwalletinfo()['private_keys_enabled']
- result = signer.importmulti([{
+ reqs = [{
"desc": priv_rec_desc,
"timestamp": 0,
"range": [0,1],
@@ -378,7 +377,11 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
"range": [0, 0],
"internal": True,
"keypool": False
- }])
+ }]
+ if self.options.descriptors:
+ result = signer.importdescriptors(reqs)
+ else:
+ result = signer.importmulti(reqs)
assert_equal(result, [{'success': True}, {'success': True}])
# Create another wallet with just the public keys, which creates PSBTs
@@ -386,21 +389,27 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
watcher = rbf_node.get_wallet_rpc("watcher")
assert not watcher.getwalletinfo()['private_keys_enabled']
- result = watcher.importmulti([{
+ reqs = [{
"desc": pub_rec_desc,
"timestamp": 0,
"range": [0, 10],
"internal": False,
"keypool": True,
- "watchonly": True
+ "watchonly": True,
+ "active": True,
}, {
"desc": pub_change_desc,
"timestamp": 0,
"range": [0, 10],
"internal": True,
"keypool": True,
- "watchonly": True
- }])
+ "watchonly": True,
+ "active": True,
+ }]
+ if self.options.descriptors:
+ result = watcher.importdescriptors(reqs)
+ else:
+ result = watcher.importmulti(reqs)
assert_equal(result, [{'success': True}, {'success': True}])
funding_address1 = watcher.getnewaddress(address_type='bech32')
@@ -410,7 +419,7 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
self.sync_all()
# Create single-input PSBT for transaction to be bumped
- psbt = watcher.walletcreatefundedpsbt([], {dest_address: 0.0005}, 0, {"feeRate": 0.00001}, True)['psbt']
+ psbt = watcher.walletcreatefundedpsbt([], {dest_address: 0.0005}, 0, {"fee_rate": 1}, True)['psbt']
psbt_signed = signer.walletprocesspsbt(psbt=psbt, sign=True, sighashtype="ALL", bip32derivs=True)
psbt_final = watcher.finalizepsbt(psbt_signed["psbt"])
original_txid = watcher.sendrawtransaction(psbt_final["hex"])
diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py
index ed9159726a..0f11aca525 100755
--- a/test/functional/wallet_create_tx.py
+++ b/test/functional/wallet_create_tx.py
@@ -53,12 +53,12 @@ class CreateTxWalletTest(BitcoinTestFramework):
self.restart_node(0, extra_args=[fee_setting])
assert_raises_rpc_error(
-6,
- "Fee exceeds maximum configured by -maxtxfee",
+ "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
lambda: self.nodes[0].sendmany(dummy="", amounts=outputs),
)
assert_raises_rpc_error(
-4,
- "Fee exceeds maximum configured by -maxtxfee",
+ "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx),
)
@@ -67,12 +67,12 @@ class CreateTxWalletTest(BitcoinTestFramework):
self.nodes[0].settxfee(0.01)
assert_raises_rpc_error(
-6,
- "Fee exceeds maximum configured by -maxtxfee",
+ "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
lambda: self.nodes[0].sendmany(dummy="", amounts=outputs),
)
assert_raises_rpc_error(
-4,
- "Fee exceeds maximum configured by -maxtxfee",
+ "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx),
)
self.nodes[0].settxfee(0)
diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py
index 48c0fcb731..cf3317121f 100755
--- a/test/functional/wallet_createwallet.py
+++ b/test/functional/wallet_createwallet.py
@@ -5,11 +5,15 @@
"""Test createwallet arguments.
"""
+from test_framework.address import key_to_p2wpkh
+from test_framework.descriptors import descsum_create
+from test_framework.key import ECKey
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
+from test_framework.wallet_util import bytes_to_wif, generate_wif_key
class CreateWalletTest(BitcoinTestFramework):
def set_test_params(self):
@@ -35,10 +39,14 @@ class CreateWalletTest(BitcoinTestFramework):
w1.importpubkey(w0.getaddressinfo(address1)['pubkey'])
self.log.info('Test that private keys cannot be imported')
- addr = w0.getnewaddress('', 'legacy')
- privkey = w0.dumpprivkey(addr)
+ eckey = ECKey()
+ eckey.generate()
+ privkey = bytes_to_wif(eckey.get_bytes())
assert_raises_rpc_error(-4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey)
- result = w1.importmulti([{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}])
+ if self.options.descriptors:
+ result = w1.importdescriptors([{'desc': descsum_create('wpkh(' + privkey + ')'), 'timestamp': 'now'}])
+ else:
+ result = w1.importmulti([{'scriptPubKey': {'address': key_to_p2wpkh(eckey.get_pubkey().get_bytes())}, 'timestamp': 'now', 'keys': [privkey]}])
assert not result[0]['success']
assert 'warning' not in result[0]
assert_equal(result[0]['error']['code'], -4)
@@ -58,12 +66,25 @@ class CreateWalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getrawchangeaddress)
# Import private key
- w3.importprivkey(w0.dumpprivkey(address1))
+ w3.importprivkey(generate_wif_key())
# Imported private keys are currently ignored by the keypool
assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
# Set the seed
- w3.sethdseed()
+ if self.options.descriptors:
+ w3.importdescriptors([{
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'),
+ 'timestamp': 'now',
+ 'active': True
+ },
+ {
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'),
+ 'timestamp': 'now',
+ 'active': True,
+ 'internal': True
+ }])
+ else:
+ w3.sethdseed()
assert_equal(w3.getwalletinfo()['keypoolsize'], 1)
w3.getnewaddress()
w3.getrawchangeaddress()
@@ -80,7 +101,20 @@ class CreateWalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
# Now set a seed and it should work. Wallet should also be encrypted
w4.walletpassphrase('pass', 60)
- w4.sethdseed()
+ if self.options.descriptors:
+ w4.importdescriptors([{
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'),
+ 'timestamp': 'now',
+ 'active': True
+ },
+ {
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'),
+ 'timestamp': 'now',
+ 'active': True,
+ 'internal': True
+ }])
+ else:
+ w4.sethdseed()
w4.getnewaddress()
w4.getrawchangeaddress()
@@ -111,13 +145,14 @@ class CreateWalletTest(BitcoinTestFramework):
w6.walletpassphrase('thisisapassphrase', 60)
w6.signmessage(w6.getnewaddress('', 'legacy'), "test")
w6.keypoolrefill(1)
- # There should only be 1 key
+ # There should only be 1 key for legacy, 3 for descriptors
walletinfo = w6.getwalletinfo()
- assert_equal(walletinfo['keypoolsize'], 1)
- assert_equal(walletinfo['keypoolsize_hd_internal'], 1)
+ keys = 3 if self.options.descriptors else 1
+ assert_equal(walletinfo['keypoolsize'], keys)
+ assert_equal(walletinfo['keypoolsize_hd_internal'], keys)
# Allow empty passphrase, but there should be a warning
resp = self.nodes[0].createwallet(wallet_name='w7', disable_private_keys=False, blank=False, passphrase='')
- assert_equal(resp['warning'], 'Empty string given as passphrase, wallet will not be encrypted.')
+ assert 'Empty string given as passphrase, wallet will not be encrypted.' in resp['warning']
w7 = node.get_wallet_rpc('w7')
assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60)
diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py
index 9c63e8f7d3..0fec8ea4a4 100755
--- a/test/functional/wallet_descriptor.py
+++ b/test/functional/wallet_descriptor.py
@@ -16,19 +16,27 @@ class WalletDescriptorTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [['-keypool=100']]
+ self.wallet_names = []
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ self.skip_if_no_sqlite()
def run_test(self):
+ # Make a legacy wallet and check it is BDB
+ self.nodes[0].createwallet(wallet_name="legacy1", descriptors=False)
+ wallet_info = self.nodes[0].getwalletinfo()
+ assert_equal(wallet_info['format'], 'bdb')
+ self.nodes[0].unloadwallet("legacy1")
+
# Make a descriptor wallet
self.log.info("Making a descriptor wallet")
self.nodes[0].createwallet(wallet_name="desc1", descriptors=True)
- self.nodes[0].unloadwallet("")
# A descriptor wallet should have 100 addresses * 3 types = 300 keys
self.log.info("Checking wallet info")
wallet_info = self.nodes[0].getwalletinfo()
+ assert_equal(wallet_info['format'], 'sqlite')
assert_equal(wallet_info['keypoolsize'], 300)
assert_equal(wallet_info['keypoolsize_hd_internal'], 300)
assert 'keypoololdest' not in wallet_info
diff --git a/test/functional/wallet_disable.py b/test/functional/wallet_disable.py
index 7c2ec56b5a..c2b30fb35b 100755
--- a/test/functional/wallet_disable.py
+++ b/test/functional/wallet_disable.py
@@ -16,6 +16,7 @@ class DisableWalletTest (BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [["-disablewallet"]]
+ self.wallet_names = []
def run_test (self):
# Make sure wallet is really disabled
diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py
index 09581d864b..eb54da99f5 100755
--- a/test/functional/wallet_dump.py
+++ b/test/functional/wallet_dump.py
@@ -95,7 +95,7 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old):
class WalletDumpTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- self.extra_args = [["-keypool=90", "-addresstype=legacy", "-wallet=dump"]]
+ self.extra_args = [["-keypool=90", "-addresstype=legacy"]]
self.rpc_timeout = 120
def skip_test_if_missing_module(self):
@@ -106,6 +106,8 @@ class WalletDumpTest(BitcoinTestFramework):
self.start_nodes()
def run_test(self):
+ self.nodes[0].createwallet("dump")
+
wallet_unenc_dump = os.path.join(self.nodes[0].datadir, "wallet.unencrypted.dump")
wallet_enc_dump = os.path.join(self.nodes[0].datadir, "wallet.encrypted.dump")
@@ -190,7 +192,8 @@ class WalletDumpTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "already exists", lambda: self.nodes[0].dumpwallet(wallet_enc_dump))
# Restart node with new wallet, and test importwallet
- self.restart_node(0, ['-wallet=w2'])
+ self.restart_node(0)
+ self.nodes[0].createwallet("w2")
# Make sure the address is not IsMine before import
result = self.nodes[0].getaddressinfo(multisig_addr)
diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py
index 3c336623e2..d45cf05689 100755
--- a/test/functional/wallet_hd.py
+++ b/test/functional/wallet_hd.py
@@ -10,7 +10,6 @@ import shutil
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
assert_raises_rpc_error,
)
@@ -84,7 +83,7 @@ class WalletHDTest(BitcoinTestFramework):
shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate"))
shutil.copyfile(
os.path.join(self.nodes[1].datadir, "hd.bak"),
- os.path.join(self.nodes[1].datadir, self.chain, 'wallets', "wallet.dat"),
+ os.path.join(self.nodes[1].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename),
)
self.start_node(1)
@@ -99,7 +98,7 @@ class WalletHDTest(BitcoinTestFramework):
assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/" + str(i) + "'")
assert_equal(hd_info_2["hdmasterfingerprint"], hd_fingerprint)
assert_equal(hd_add, hd_add_2)
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_all()
# Needs rescan
@@ -112,10 +111,10 @@ class WalletHDTest(BitcoinTestFramework):
shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate"))
shutil.copyfile(
os.path.join(self.nodes[1].datadir, "hd.bak"),
- os.path.join(self.nodes[1].datadir, self.chain, "wallets", "wallet.dat"),
+ os.path.join(self.nodes[1].datadir, self.chain, "wallets", self.default_wallet_name, self.wallet_data_filename),
)
self.start_node(1, extra_args=self.extra_args[1])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
self.sync_all()
# Wallet automatically scans blocks older than key on startup
assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)
@@ -183,7 +182,7 @@ class WalletHDTest(BitcoinTestFramework):
# Restart node 1 with keypool of 3 and a different wallet
self.nodes[1].createwallet(wallet_name='origin', blank=True)
self.restart_node(1, extra_args=['-keypool=3', '-wallet=origin'])
- connect_nodes(self.nodes[0], 1)
+ self.connect_nodes(0, 1)
# sethdseed restoring and seeing txs to addresses out of the keypool
origin_rpc = self.nodes[1].get_wallet_rpc('origin')
diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py
index 87deaded09..aad112b499 100755
--- a/test/functional/wallet_import_rescan.py
+++ b/test/functional/wallet_import_rescan.py
@@ -22,7 +22,6 @@ happened previously.
from test_framework.test_framework import BitcoinTestFramework
from test_framework.address import AddressType
from test_framework.util import (
- connect_nodes,
assert_equal,
set_node_times,
)
@@ -151,7 +150,7 @@ class ImportRescanTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def setup_network(self):
- self.extra_args = [["-wallet="] for _ in range(self.num_nodes)]
+ self.extra_args = [[] for _ in range(self.num_nodes)]
for i, import_node in enumerate(IMPORT_NODES, 2):
if import_node.prune:
self.extra_args[i] += ["-prune=1"]
@@ -159,14 +158,13 @@ class ImportRescanTest(BitcoinTestFramework):
self.add_nodes(self.num_nodes, extra_args=self.extra_args)
# Import keys with pruning disabled
- self.start_nodes(extra_args=[["-wallet="]] * self.num_nodes)
- for n in self.nodes:
- n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase')
+ self.start_nodes(extra_args=[[]] * self.num_nodes)
+ self.import_deterministic_coinbase_privkeys()
self.stop_nodes()
self.start_nodes()
for i in range(1, self.num_nodes):
- connect_nodes(self.nodes[i], 0)
+ self.connect_nodes(i, 0)
def run_test(self):
# Create one transaction on node 0 with a unique amount for
@@ -183,6 +181,7 @@ class ImportRescanTest(BitcoinTestFramework):
self.nodes[0].generate(1) # Generate one block for each send
variant.confirmation_height = self.nodes[0].getblockcount()
variant.timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
+ self.sync_all() # Conclude sync before calling setmocktime to avoid timeouts
# Generate a block further in the future (past the rescan window).
assert_equal(self.nodes[0].getrawmempool(), [])
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index 2d982edef8..2903a84998 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -15,6 +15,7 @@ variants.
- `test_address()` is called to call getaddressinfo for an address on node1
and test the values returned."""
+from test_framework.address import key_to_p2pkh
from test_framework.test_framework import BitcoinTestFramework
from test_framework.descriptors import descsum_create
from test_framework.util import (
@@ -34,9 +35,11 @@ class ImportDescriptorsTest(BitcoinTestFramework):
["-addresstype=bech32", "-keypool=5"]
]
self.setup_clean_chain = True
+ self.wallet_names = []
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ self.skip_if_no_sqlite()
def test_importdesc(self, req, success, error_code=None, error_message=None, warnings=None, wallet=None):
"""Run importdescriptors and assert success"""
@@ -58,7 +61,7 @@ class ImportDescriptorsTest(BitcoinTestFramework):
def run_test(self):
self.log.info('Setting up wallets')
- self.nodes[0].createwallet(wallet_name='w0', disable_private_keys=False)
+ self.nodes[0].createwallet(wallet_name='w0', disable_private_keys=False, descriptors=True)
w0 = self.nodes[0].get_wallet_rpc('w0')
self.nodes[1].createwallet(wallet_name='w1', disable_private_keys=True, blank=True, descriptors=True)
@@ -105,6 +108,17 @@ class ImportDescriptorsTest(BitcoinTestFramework):
error_code=-8,
error_message="Internal addresses should not have a label")
+ self.log.info("Internal addresses should be detected as such")
+ key = get_generate_key()
+ addr = key_to_p2pkh(key.pubkey)
+ self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
+ "timestamp": "now",
+ "internal": True},
+ success=True)
+ info = w1.getaddressinfo(addr)
+ assert_equal(info["ismine"], True)
+ assert_equal(info["ischange"], True)
+
# # Test importing of a P2SH-P2WPKH descriptor
key = get_generate_key()
self.log.info("Should not import a p2sh-p2wpkh descriptor without checksum")
@@ -207,6 +221,15 @@ class ImportDescriptorsTest(BitcoinTestFramework):
success=False,
error_code=-4,
error_message='Cannot import private keys to a wallet with private keys disabled')
+
+ self.log.info("Should not import a descriptor with hardened derivations when private keys are disabled")
+ self.test_importdesc({"desc": descsum_create("wpkh(" + xpub + "/1h/*)"),
+ "timestamp": "now",
+ "range": 1},
+ success=False,
+ error_code=-4,
+ error_message='Cannot expand descriptor. Probably because of hardened derivations without private keys provided')
+
for address in addresses:
test_address(w1,
address,
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index bd4fcdabcf..30206349ae 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -64,7 +64,7 @@ class ImportMultiTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.nodes[1].generate(1)
timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime']
- self.nodes[1].syncwithvalidationinterfacequeue()
+ self.nodes[1].syncwithvalidationinterfacequeue() # Sync the timestamp to the wallet, so that importmulti works
node0_address1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())
@@ -820,7 +820,7 @@ class ImportMultiTest(BitcoinTestFramework):
# Cannot import those pubkeys to keypool of wallet with privkeys
self.log.info("Pubkeys cannot be added to the keypool of a wallet with private keys")
- wrpc = self.nodes[1].get_wallet_rpc("")
+ wrpc = self.nodes[1].get_wallet_rpc(self.default_wallet_name)
assert wrpc.getwalletinfo()['private_keys_enabled']
result = wrpc.importmulti(
[{
diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py
index 78426018ef..d10d8ae1c8 100755
--- a/test/functional/wallet_importprunedfunds.py
+++ b/test/functional/wallet_importprunedfunds.py
@@ -5,11 +5,14 @@
"""Test the importprunedfunds and removeprunedfunds RPCs."""
from decimal import Decimal
+from test_framework.address import key_to_p2wpkh
+from test_framework.key import ECKey
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
+from test_framework.wallet_util import bytes_to_wif
class ImportPrunedFundsTest(BitcoinTestFramework):
def set_test_params(self):
@@ -30,8 +33,11 @@ class ImportPrunedFundsTest(BitcoinTestFramework):
# pubkey
address2 = self.nodes[0].getnewaddress()
# privkey
- address3 = self.nodes[0].getnewaddress()
- address3_privkey = self.nodes[0].dumpprivkey(address3) # Using privkey
+ eckey = ECKey()
+ eckey.generate()
+ address3_privkey = bytes_to_wif(eckey.get_bytes())
+ address3 = key_to_p2wpkh(eckey.get_pubkey().get_bytes())
+ self.nodes[0].importprivkey(address3_privkey)
# Check only one address
address_info = self.nodes[0].getaddressinfo(address1)
@@ -80,37 +86,44 @@ class ImportPrunedFundsTest(BitcoinTestFramework):
assert_equal(balance1, Decimal(0))
# Import with affiliated address with no rescan
- self.nodes[1].importaddress(address=address2, rescan=False)
- self.nodes[1].importprunedfunds(rawtransaction=rawtxn2, txoutproof=proof2)
- assert [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2]
+ self.nodes[1].createwallet('wwatch', disable_private_keys=True)
+ wwatch = self.nodes[1].get_wallet_rpc('wwatch')
+ wwatch.importaddress(address=address2, rescan=False)
+ wwatch.importprunedfunds(rawtransaction=rawtxn2, txoutproof=proof2)
+ assert [tx for tx in wwatch.listtransactions(include_watchonly=True) if tx['txid'] == txnid2]
# Import with private key with no rescan
- self.nodes[1].importprivkey(privkey=address3_privkey, rescan=False)
- self.nodes[1].importprunedfunds(rawtxn3, proof3)
- assert [tx for tx in self.nodes[1].listtransactions() if tx['txid'] == txnid3]
- balance3 = self.nodes[1].getbalance()
+ w1 = self.nodes[1].get_wallet_rpc(self.default_wallet_name)
+ w1.importprivkey(privkey=address3_privkey, rescan=False)
+ w1.importprunedfunds(rawtxn3, proof3)
+ assert [tx for tx in w1.listtransactions() if tx['txid'] == txnid3]
+ balance3 = w1.getbalance()
assert_equal(balance3, Decimal('0.025'))
# Addresses Test - after import
- address_info = self.nodes[1].getaddressinfo(address1)
+ address_info = w1.getaddressinfo(address1)
assert_equal(address_info['iswatchonly'], False)
assert_equal(address_info['ismine'], False)
- address_info = self.nodes[1].getaddressinfo(address2)
- assert_equal(address_info['iswatchonly'], True)
- assert_equal(address_info['ismine'], False)
- address_info = self.nodes[1].getaddressinfo(address3)
+ address_info = wwatch.getaddressinfo(address2)
+ if self.options.descriptors:
+ assert_equal(address_info['iswatchonly'], False)
+ assert_equal(address_info['ismine'], True)
+ else:
+ assert_equal(address_info['iswatchonly'], True)
+ assert_equal(address_info['ismine'], False)
+ address_info = w1.getaddressinfo(address3)
assert_equal(address_info['iswatchonly'], False)
assert_equal(address_info['ismine'], True)
# Remove transactions
- assert_raises_rpc_error(-8, "Transaction does not exist in wallet.", self.nodes[1].removeprunedfunds, txnid1)
- assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid1]
+ assert_raises_rpc_error(-8, "Transaction does not exist in wallet.", w1.removeprunedfunds, txnid1)
+ assert not [tx for tx in w1.listtransactions(include_watchonly=True) if tx['txid'] == txnid1]
- self.nodes[1].removeprunedfunds(txnid2)
- assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2]
+ wwatch.removeprunedfunds(txnid2)
+ assert not [tx for tx in wwatch.listtransactions(include_watchonly=True) if tx['txid'] == txnid2]
- self.nodes[1].removeprunedfunds(txnid3)
- assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid3]
+ w1.removeprunedfunds(txnid3)
+ assert not [tx for tx in w1.listtransactions(include_watchonly=True) if tx['txid'] == txnid3]
if __name__ == '__main__':
ImportPrunedFundsTest().main()
diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py
index 40a2b3ab6a..51795aca23 100755
--- a/test/functional/wallet_keypool.py
+++ b/test/functional/wallet_keypool.py
@@ -143,7 +143,7 @@ class KeyPoolTest(BitcoinTestFramework):
w2 = nodes[0].get_wallet_rpc('w2')
# refer to initial wallet as w1
- w1 = nodes[0].get_wallet_rpc('')
+ w1 = nodes[0].get_wallet_rpc(self.default_wallet_name)
# import private key and fund it
address = addr.pop()
diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py
index 102ed23fba..78e06c5916 100755
--- a/test/functional/wallet_keypool_topup.py
+++ b/test/functional/wallet_keypool_topup.py
@@ -16,7 +16,6 @@ import shutil
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
)
@@ -30,7 +29,7 @@ class KeypoolRestoreTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def run_test(self):
- wallet_path = os.path.join(self.nodes[1].datadir, self.chain, "wallets", "wallet.dat")
+ wallet_path = os.path.join(self.nodes[1].datadir, self.chain, "wallets", self.default_wallet_name, self.wallet_data_filename)
wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak")
self.nodes[0].generate(101)
@@ -38,9 +37,9 @@ class KeypoolRestoreTest(BitcoinTestFramework):
self.stop_node(1)
shutil.copyfile(wallet_path, wallet_backup_path)
self.start_node(1, self.extra_args[1])
- connect_nodes(self.nodes[0], 1)
- connect_nodes(self.nodes[0], 2)
- connect_nodes(self.nodes[0], 3)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(0, 2)
+ self.connect_nodes(0, 3)
for i, output_type in enumerate(["legacy", "p2sh-segwit", "bech32"]):
@@ -72,7 +71,7 @@ class KeypoolRestoreTest(BitcoinTestFramework):
self.stop_node(idx)
shutil.copyfile(wallet_backup_path, wallet_path)
self.start_node(idx, self.extra_args[idx])
- connect_nodes(self.nodes[0], idx)
+ self.connect_nodes(0, idx)
self.sync_all()
self.log.info("Verify keypool is restored and balance is correct")
diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py
index cff59bd1c1..883b97561e 100755
--- a/test/functional/wallet_labels.py
+++ b/test/functional/wallet_labels.py
@@ -135,7 +135,7 @@ class WalletLabelsTest(BitcoinTestFramework):
change_label(node, labels[2].addresses[0], labels[2], labels[2])
self.log.info('Check watchonly labels')
- node.createwallet(wallet_name='watch_only', disable_private_keys=True, descriptors=False)
+ node.createwallet(wallet_name='watch_only', disable_private_keys=True)
wallet_watch_only = node.get_wallet_rpc('watch_only')
BECH32_VALID = {
'✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqn2cjv3',
@@ -156,7 +156,7 @@ class WalletLabelsTest(BitcoinTestFramework):
ad = BECH32_INVALID[l]
assert_raises_rpc_error(
-5,
- "Invalid Bitcoin address or script",
+ "Address is not valid" if self.options.descriptors else "Invalid Bitcoin address or script",
lambda: wallet_watch_only.importaddress(label=l, rescan=False, address=ad),
)
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index d4131deabf..6a1b9097c5 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -4,14 +4,16 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the listsinceblock RPC."""
+from test_framework.address import key_to_p2wpkh
+from test_framework.key import ECKey
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import BIP125_SEQUENCE_NUMBER
from test_framework.util import (
assert_array_result,
assert_equal,
assert_raises_rpc_error,
- connect_nodes,
)
+from test_framework.wallet_util import bytes_to_wif
from decimal import Decimal
@@ -26,7 +28,7 @@ class ListSinceBlockTest(BitcoinTestFramework):
def run_test(self):
# All nodes are in IBD from genesis, so they'll need the miner (node2) to be an outbound connection, or have
# only one connection. (See fPreferredDownload in net_processing)
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(1, 2)
self.nodes[2].generate(101)
self.sync_all()
@@ -182,15 +184,22 @@ class ListSinceBlockTest(BitcoinTestFramework):
self.sync_all()
- # Split network into two
- self.split_network()
-
# share utxo between nodes[1] and nodes[2]
+ eckey = ECKey()
+ eckey.generate()
+ privkey = bytes_to_wif(eckey.get_bytes())
+ address = key_to_p2wpkh(eckey.get_pubkey().get_bytes())
+ self.nodes[2].sendtoaddress(address, 10)
+ self.nodes[2].generate(6)
+ self.sync_all()
+ self.nodes[2].importprivkey(privkey)
utxos = self.nodes[2].listunspent()
- utxo = utxos[0]
- privkey = self.nodes[2].dumpprivkey(utxo['address'])
+ utxo = [u for u in utxos if u["address"] == address][0]
self.nodes[1].importprivkey(privkey)
+ # Split network into two
+ self.split_network()
+
# send from nodes[1] using utxo to nodes[0]
change = '%.8f' % (float(utxo['amount']) - 1.0003)
recipient_dict = {
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
index 8ff663ccd2..3e98208431 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -91,18 +91,20 @@ class ListTransactionsTest(BitcoinTestFramework):
{"category": "receive", "amount": Decimal("0.44")},
{"txid": txid})
- pubkey = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey']
- multisig = self.nodes[1].createmultisig(1, [pubkey])
- self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True)
- txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
- self.nodes[1].generate(1)
- self.sync_all()
- assert_equal(len(self.nodes[0].listtransactions(label="watchonly", include_watchonly=True)), 1)
- assert_equal(len(self.nodes[0].listtransactions(dummy="watchonly", include_watchonly=True)), 1)
- assert len(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=False)) == 0
- assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True),
- {"category": "receive", "amount": Decimal("0.1")},
- {"txid": txid, "label": "watchonly"})
+ if not self.options.descriptors:
+ # include_watchonly is a legacy wallet feature, so don't test it for descriptor wallets
+ pubkey = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey']
+ multisig = self.nodes[1].createmultisig(1, [pubkey])
+ self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True)
+ txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
+ self.nodes[1].generate(1)
+ self.sync_all()
+ assert_equal(len(self.nodes[0].listtransactions(label="watchonly", include_watchonly=True)), 1)
+ assert_equal(len(self.nodes[0].listtransactions(dummy="watchonly", include_watchonly=True)), 1)
+ assert len(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=False)) == 0
+ assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True),
+ {"category": "receive", "amount": Decimal("0.1")},
+ {"txid": txid, "label": "watchonly"})
self.run_rbf_opt_in_test()
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index f0be271c66..fb4532bcf6 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -10,6 +10,7 @@ from decimal import Decimal
from threading import Thread
import os
import shutil
+import stat
import time
from test_framework.authproxy import JSONRPCException
@@ -41,7 +42,7 @@ class MultiWalletTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 2
self.rpc_timeout = 120
- self.extra_args = [["-wallet="], ["-wallet="]]
+ self.extra_args = [["-nowallet"], []]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -61,30 +62,44 @@ class MultiWalletTest(BitcoinTestFramework):
wallet = lambda name: node.get_wallet_rpc(name)
def wallet_file(name):
+ if name == self.default_wallet_name:
+ return wallet_dir(self.default_wallet_name, self.wallet_data_filename)
if os.path.isdir(wallet_dir(name)):
return wallet_dir(name, "wallet.dat")
return wallet_dir(name)
- assert_equal(self.nodes[0].listwalletdir(), { 'wallets': [{ 'name': '' }] })
+ assert_equal(self.nodes[0].listwalletdir(), { 'wallets': [{ 'name': self.default_wallet_name }] })
# check wallet.dat is created
self.stop_nodes()
- assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True)
+ assert_equal(os.path.isfile(wallet_dir(self.default_wallet_name, self.wallet_data_filename)), True)
# create symlink to verify wallet directory path can be referenced
# through symlink
os.mkdir(wallet_dir('w7'))
os.symlink('w7', wallet_dir('w7_symlink'))
+ os.symlink('..', wallet_dir('recursive_dir_symlink'))
+
+ os.mkdir(wallet_dir('self_walletdat_symlink'))
+ os.symlink('wallet.dat', wallet_dir('self_walletdat_symlink/wallet.dat'))
+
# rename wallet.dat to make sure plain wallet file paths (as opposed to
# directory paths) can be loaded
- os.rename(wallet_dir("wallet.dat"), wallet_dir("w8"))
-
# create another dummy wallet for use in testing backups later
- self.start_node(0, ["-wallet="])
+ self.start_node(0)
+ node.createwallet("empty")
+ node.createwallet("plain")
+ node.createwallet("created")
self.stop_nodes()
empty_wallet = os.path.join(self.options.tmpdir, 'empty.dat')
- os.rename(wallet_dir("wallet.dat"), empty_wallet)
+ os.rename(wallet_file("empty"), empty_wallet)
+ shutil.rmtree(wallet_dir("empty"))
+ empty_created_wallet = os.path.join(self.options.tmpdir, 'empty.created.dat')
+ os.rename(wallet_dir("created", self.wallet_data_filename), empty_created_wallet)
+ shutil.rmtree(wallet_dir("created"))
+ os.rename(wallet_file("plain"), wallet_dir("w8"))
+ shutil.rmtree(wallet_dir("plain"))
# restart node with a mix of wallet names:
# w1, w2, w3 - to verify new wallets created when non-existing paths specified
@@ -92,35 +107,58 @@ class MultiWalletTest(BitcoinTestFramework):
# sub/w5 - to verify relative wallet path is created correctly
# extern/w6 - to verify absolute wallet path is created correctly
# w7_symlink - to verify symlinked wallet path is initialized correctly
- # w8 - to verify existing wallet file is loaded correctly
+ # w8 - to verify existing wallet file is loaded correctly. Not tested for SQLite wallets as this is a deprecated BDB behavior.
# '' - to verify default wallet file is created correctly
- wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', '']
- extra_args = ['-wallet={}'.format(n) for n in wallet_names]
- self.start_node(0, extra_args)
- assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), ['', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8'])
+ to_create = ['w1', 'w2', 'w3', 'w', 'sub/w5', 'w7_symlink']
+ in_wallet_dir = [w.replace('/', os.path.sep) for w in to_create] # Wallets in the wallet dir
+ in_wallet_dir.append('w7') # w7 is not loaded or created, but will be listed by listwalletdir because w7_symlink
+ to_create.append(os.path.join(self.options.tmpdir, 'extern/w6')) # External, not in the wallet dir, so we need to avoid adding it to in_wallet_dir
+ to_load = [self.default_wallet_name]
+ if not self.options.descriptors:
+ to_load.append('w8')
+ wallet_names = to_create + to_load # Wallet names loaded in the wallet
+ in_wallet_dir += to_load # The loaded wallets are also in the wallet dir
+ self.start_node(0)
+ for wallet_name in to_create:
+ self.nodes[0].createwallet(wallet_name)
+ for wallet_name in to_load:
+ self.nodes[0].loadwallet(wallet_name)
+
+ os.mkdir(wallet_dir('no_access'))
+ os.chmod(wallet_dir('no_access'), 0)
+ try:
+ with self.nodes[0].assert_debug_log(expected_msgs=['Error scanning']):
+ walletlist = self.nodes[0].listwalletdir()['wallets']
+ finally:
+ # Need to ensure access is restored for cleanup
+ os.chmod(wallet_dir('no_access'), stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
+ assert_equal(sorted(map(lambda w: w['name'], walletlist)), sorted(in_wallet_dir))
assert_equal(set(node.listwallets()), set(wallet_names))
+ # should raise rpc error if wallet path can't be created
+ err_code = -4 if self.options.descriptors else -1
+ assert_raises_rpc_error(err_code, "boost::filesystem::create_directory:", self.nodes[0].createwallet, "w8/bad")
+
# check that all requested wallets were created
self.stop_node(0)
for wallet_name in wallet_names:
assert_equal(os.path.isfile(wallet_file(wallet_name)), True)
- # should not initialize if wallet path can't be created
- exp_stderr = "boost::filesystem::create_directory:"
- self.nodes[0].assert_start_raises_init_error(['-wallet=wallet.dat/bad'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
-
self.nodes[0].assert_start_raises_init_error(['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist')
self.nodes[0].assert_start_raises_init_error(['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir())
self.nodes[0].assert_start_raises_init_error(['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir())
- # should not initialize if there are duplicate wallets
- self.nodes[0].assert_start_raises_init_error(['-wallet=w1', '-wallet=w1'], 'Error: Error loading wallet w1. Duplicate -wallet filename specified.')
+ self.start_node(0, ['-wallet=w1', '-wallet=w1'])
+ self.stop_node(0, 'Warning: Ignoring duplicate -wallet w1.')
- # should not initialize if one wallet is a copy of another
- shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
- exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
- self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
+ if not self.options.descriptors:
+ # Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary
+ # should not initialize if one wallet is a copy of another
+ shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
+ in_wallet_dir.append('w8_copy')
+ exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
+ self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
# should not initialize if wallet file is a symlink
os.symlink('w8', wallet_dir('w8_symlink'))
@@ -133,17 +171,24 @@ class MultiWalletTest(BitcoinTestFramework):
open(not_a_dir, 'a', encoding="utf8").close()
self.nodes[0].assert_start_raises_init_error(['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory')
+ self.log.info("Do not allow -upgradewallet with multiwallet")
+ self.nodes[0].assert_start_raises_init_error(['-upgradewallet'], "Error: Error parsing command line arguments: Invalid parameter -upgradewallet")
+
# if wallets/ doesn't exist, datadir should be the default wallet dir
wallet_dir2 = data_dir('walletdir')
os.rename(wallet_dir(), wallet_dir2)
- self.start_node(0, ['-wallet=w4', '-wallet=w5'])
+ self.start_node(0)
+ self.nodes[0].createwallet("w4")
+ self.nodes[0].createwallet("w5")
assert_equal(set(node.listwallets()), {"w4", "w5"})
w5 = wallet("w5")
node.generatetoaddress(nblocks=1, address=w5.getnewaddress())
# now if wallets/ exists again, but the rootdir is specified as the walletdir, w4 and w5 should still be loaded
os.rename(wallet_dir2, wallet_dir())
- self.restart_node(0, ['-wallet=w4', '-wallet=w5', '-walletdir=' + data_dir()])
+ self.restart_node(0, ['-nowallet', '-walletdir=' + data_dir()])
+ self.nodes[0].loadwallet("w4")
+ self.nodes[0].loadwallet("w5")
assert_equal(set(node.listwallets()), {"w4", "w5"})
w5 = wallet("w5")
w5_info = w5.getwalletinfo()
@@ -151,13 +196,19 @@ class MultiWalletTest(BitcoinTestFramework):
competing_wallet_dir = os.path.join(self.options.tmpdir, 'competing_walletdir')
os.mkdir(competing_wallet_dir)
- self.restart_node(0, ['-walletdir=' + competing_wallet_dir, '-wallet='])
- exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\"!"
+ self.restart_node(0, ['-nowallet', '-walletdir=' + competing_wallet_dir])
+ self.nodes[0].createwallet(self.default_wallet_name)
+ if self.options.descriptors:
+ exp_stderr = r"Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?"
+ else:
+ exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\S*\"!"
self.nodes[1].assert_start_raises_init_error(['-walletdir=' + competing_wallet_dir], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
- self.restart_node(0, extra_args)
+ self.restart_node(0)
+ for wallet_name in wallet_names:
+ self.nodes[0].loadwallet(wallet_name)
- assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), ['', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy'])
+ assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir))
wallets = [wallet(w) for w in wallet_names]
wallet_bad = wallet("bad")
@@ -205,7 +256,7 @@ class MultiWalletTest(BitcoinTestFramework):
self.restart_node(0, ['-nowallet'])
assert_equal(node.listwallets(), [])
- assert_raises_rpc_error(-32601, "Method not found", node.getwalletinfo)
+ assert_raises_rpc_error(-18, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)", node.getwalletinfo)
self.log.info("Load first wallet")
loadwallet_name = node.loadwallet(wallet_names[0])
@@ -248,18 +299,22 @@ class MultiWalletTest(BitcoinTestFramework):
# Fail to load duplicate wallets
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "w1", "wallet.dat")
- assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0])
-
- # Fail to load duplicate wallets by different ways (directory and filepath)
- path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallet.dat")
- assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat')
+ if self.options.descriptors:
+ assert_raises_rpc_error(-4, "Wallet file verification failed. SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?", self.nodes[0].loadwallet, wallet_names[0])
+ else:
+ assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0])
- # Fail to load if one wallet is a copy of another
- assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
+ # This tests the default wallet that BDB makes, so SQLite wallet doesn't need to test this
+ # Fail to load duplicate wallets by different ways (directory and filepath)
+ path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallet.dat")
+ assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat')
- # Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
- assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
+ # Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary
+ # Fail to load if one wallet is a copy of another
+ assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
+ # Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
+ assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
# Fail to load if wallet file is a symlink
assert_raises_rpc_error(-4, "Wallet file verification failed. Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink')
@@ -277,6 +332,7 @@ class MultiWalletTest(BitcoinTestFramework):
# Successfully create a wallet with a new name
loadwallet_name = self.nodes[0].createwallet('w9')
+ in_wallet_dir.append('w9')
assert_equal(loadwallet_name['name'], 'w9')
w9 = node.get_wallet_rpc('w9')
assert_equal(w9.getwalletinfo()['walletname'], 'w9')
@@ -299,12 +355,18 @@ class MultiWalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].unloadwallet)
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy")
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet)
- assert_raises_rpc_error(-8, "Cannot unload the requested wallet", w1.unloadwallet, "w2"),
+ assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", w1.unloadwallet, "w2"),
# Successfully unload the specified wallet name
self.nodes[0].unloadwallet("w1")
assert 'w1' not in self.nodes[0].listwallets()
+ # Unload w1 again, this time providing the wallet name twice
+ self.nodes[0].loadwallet("w1")
+ assert 'w1' in self.nodes[0].listwallets()
+ w1.unloadwallet("w1")
+ assert 'w1' not in self.nodes[0].listwallets()
+
# Successfully unload the wallet referenced by the request endpoint
# Also ensure unload works during walletpassphrase timeout
w2.encryptwallet('test')
@@ -317,14 +379,14 @@ class MultiWalletTest(BitcoinTestFramework):
for wallet_name in self.nodes[0].listwallets():
self.nodes[0].unloadwallet(wallet_name)
assert_equal(self.nodes[0].listwallets(), [])
- assert_raises_rpc_error(-32601, "Method not found (wallet method is disabled because no wallet is loaded)", self.nodes[0].getwalletinfo)
+ assert_raises_rpc_error(-18, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)", self.nodes[0].getwalletinfo)
# Successfully load a previously unloaded wallet
self.nodes[0].loadwallet('w1')
assert_equal(self.nodes[0].listwallets(), ['w1'])
assert_equal(w1.getwalletinfo()['walletname'], 'w1')
- assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), ['', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy', 'w9'])
+ assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir))
# Test backing up and restoring wallets
self.log.info("Test wallet backup")
@@ -335,9 +397,11 @@ class MultiWalletTest(BitcoinTestFramework):
rpc = self.nodes[0].get_wallet_rpc(wallet_name)
addr = rpc.getnewaddress()
backup = os.path.join(self.options.tmpdir, 'backup.dat')
+ if os.path.exists(backup):
+ os.unlink(backup)
rpc.backupwallet(backup)
self.nodes[0].unloadwallet(wallet_name)
- shutil.copyfile(empty_wallet, wallet_file(wallet_name))
+ shutil.copyfile(empty_created_wallet if wallet_name == self.default_wallet_name else empty_wallet, wallet_file(wallet_name))
self.nodes[0].loadwallet(wallet_name)
assert_equal(rpc.getaddressinfo(addr)['ismine'], False)
self.nodes[0].unloadwallet(wallet_name)
@@ -349,9 +413,13 @@ class MultiWalletTest(BitcoinTestFramework):
self.start_node(1)
wallet = os.path.join(self.options.tmpdir, 'my_wallet')
self.nodes[0].createwallet(wallet)
- assert_raises_rpc_error(-4, "Error initializing wallet database environment", self.nodes[1].loadwallet, wallet)
+ if self.options.descriptors:
+ assert_raises_rpc_error(-4, "Unable to obtain an exclusive lock", self.nodes[1].loadwallet, wallet)
+ else:
+ assert_raises_rpc_error(-4, "Error initializing wallet database environment", self.nodes[1].loadwallet, wallet)
self.nodes[0].unloadwallet(wallet)
self.nodes[1].loadwallet(wallet)
+
if __name__ == '__main__':
MultiWalletTest().main()
diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py
index 455f1fc5e8..a1d6b098ad 100755
--- a/test/functional/wallet_reorgsrestore.py
+++ b/test/functional/wallet_reorgsrestore.py
@@ -20,8 +20,6 @@ import shutil
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
- disconnect_nodes,
)
class ReorgsRestoreTest(BitcoinTestFramework):
@@ -38,9 +36,9 @@ class ReorgsRestoreTest(BitcoinTestFramework):
self.sync_blocks()
# Disconnect node1 from others to reorg its chain later
- disconnect_nodes(self.nodes[0], 1)
- disconnect_nodes(self.nodes[1], 2)
- connect_nodes(self.nodes[0], 2)
+ self.disconnect_nodes(0, 1)
+ self.disconnect_nodes(1, 2)
+ self.connect_nodes(0, 2)
# Send a tx to be unconfirmed later
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
@@ -50,7 +48,7 @@ class ReorgsRestoreTest(BitcoinTestFramework):
assert_equal(tx_before_reorg["confirmations"], 4)
# Disconnect node0 from node2 to broadcast a conflict on their respective chains
- disconnect_nodes(self.nodes[0], 2)
+ self.disconnect_nodes(0, 2)
nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txid_conflict_from)["details"] if tx_out["amount"] == Decimal("10"))
inputs = []
inputs.append({"txid": txid_conflict_from, "vout": nA})
@@ -69,7 +67,7 @@ class ReorgsRestoreTest(BitcoinTestFramework):
self.nodes[2].generate(9)
# Reconnect node0 and node2 and check that conflicted_txid is effectively conflicted
- connect_nodes(self.nodes[0], 2)
+ self.connect_nodes(0, 2)
self.sync_blocks([self.nodes[0], self.nodes[2]])
conflicted = self.nodes[0].gettransaction(conflicted_txid)
conflicting = self.nodes[0].gettransaction(conflicting_txid)
@@ -89,7 +87,7 @@ class ReorgsRestoreTest(BitcoinTestFramework):
# Node0 wallet file is loaded on longest sync'ed node1
self.stop_node(1)
self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, 'wallet.bak'))
- shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, self.chain, 'wallet.dat'))
+ shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, self.chain, self.default_wallet_name, self.wallet_data_filename))
self.start_node(1)
tx_after_reorg = self.nodes[1].gettransaction(txid)
# Check that normal confirmed tx is confirmed again but with different blockhash
diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py
index d3c03c4764..78d88f8aa5 100755
--- a/test/functional/wallet_resendwallettransactions.py
+++ b/test/functional/wallet_resendwallettransactions.py
@@ -11,6 +11,7 @@ from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
+
class ResendWalletTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
@@ -27,10 +28,10 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
txid = node.sendtoaddress(node.getnewaddress(), 1)
# Wallet rebroadcast is first scheduled 1 sec after startup (see
- # nNextResend in ResendWalletTransactions()). Sleep for just over a
- # second to be certain that it has been called before the first
+ # nNextResend in ResendWalletTransactions()). Tell scheduler to call
+ # MaybeResendWalletTxn now to initialize nNextResend before the first
# setmocktime call below.
- time.sleep(1.1)
+ node.mockscheduler(1)
# Can take a few seconds due to transaction trickling
peer_first.wait_for_broadcast([txid])
@@ -49,6 +50,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
block.solve()
node.submitblock(ToHex(block))
+ # Set correct m_best_block_time, which is used in ResendWalletTransactions
node.syncwithvalidationinterfacequeue()
now = int(time.time())
@@ -57,13 +59,18 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
twelve_hrs = 12 * 60 * 60
two_min = 2 * 60
node.setmocktime(now + twelve_hrs - two_min)
- time.sleep(2) # ensure enough time has passed for rebroadcast attempt to occur
+ node.mockscheduler(1) # Tell scheduler to call MaybeResendWalletTxn now
assert_equal(int(txid, 16) in peer_second.get_invs(), False)
self.log.info("Bump time & check that transaction is rebroadcast")
# Transaction should be rebroadcast approximately 24 hours in the future,
# but can range from 12-36. So bump 36 hours to be sure.
- node.setmocktime(now + 36 * 60 * 60)
+ with node.assert_debug_log(['ResendWalletTransactions: resubmit 1 unconfirmed transactions']):
+ node.setmocktime(now + 36 * 60 * 60)
+ # Tell scheduler to call MaybeResendWalletTxn now.
+ node.mockscheduler(1)
+ # Give some time for trickle to occur
+ node.setmocktime(now + 36 * 60 * 60 + 600)
peer_second.wait_for_broadcast([txid])
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index b64d2030a4..192e9065e6 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -5,13 +5,15 @@
"""Test the send RPC command."""
from decimal import Decimal, getcontext
+from itertools import product
+
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_fee_amount,
assert_greater_than,
- assert_raises_rpc_error
+ assert_raises_rpc_error,
)
class WalletSendTest(BitcoinTestFramework):
@@ -28,10 +30,10 @@ class WalletSendTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
- arg_conf_target=None, arg_estimate_mode=None,
- conf_target=None, estimate_mode=None, add_to_wallet=None,psbt=None,
- inputs=None,add_inputs=None,change_address=None,change_position=None,change_type=None,
- include_watching=None,locktime=None,lock_unspents=None,replaceable=None,subtract_fee_from_outputs=None,
+ arg_conf_target=None, arg_estimate_mode=None, arg_fee_rate=None,
+ conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,
+ inputs=None, add_inputs=None, change_address=None, change_position=None, change_type=None,
+ include_watching=None, locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None,
expect_error=None):
assert (amount is None) != (data is None)
@@ -62,6 +64,8 @@ class WalletSendTest(BitcoinTestFramework):
options["conf_target"] = conf_target
if estimate_mode is not None:
options["estimate_mode"] = estimate_mode
+ if fee_rate is not None:
+ options["fee_rate"] = fee_rate
if inputs is not None:
options["inputs"] = inputs
if add_inputs is not None:
@@ -89,16 +93,19 @@ class WalletSendTest(BitcoinTestFramework):
options = None
if expect_error is None:
- res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
+ res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
else:
try:
- assert_raises_rpc_error(expect_error[0],expect_error[1],from_wallet.send,
- outputs=outputs,conf_target=arg_conf_target,estimate_mode=arg_estimate_mode,options=options)
+ assert_raises_rpc_error(expect_error[0], expect_error[1], from_wallet.send,
+ outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
except AssertionError:
# Provide debug info if the test fails
self.log.error("Unexpected successful result:")
+ self.log.error(arg_conf_target)
+ self.log.error(arg_estimate_mode)
+ self.log.error(arg_fee_rate)
self.log.error(options)
- res = from_wallet.send(outputs=outputs,conf_target=arg_conf_target,estimate_mode=arg_estimate_mode,options=options)
+ res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
self.log.error(res)
if "txid" in res and add_to_wallet:
self.log.error("Transaction details:")
@@ -131,7 +138,7 @@ class WalletSendTest(BitcoinTestFramework):
assert tx
assert_equal(tx["bip125-replaceable"], "yes" if replaceable else "no")
# Ensure transaction exists in the mempool:
- tx = from_wallet.getrawtransaction(res["txid"],True)
+ tx = from_wallet.getrawtransaction(res["txid"], True)
assert tx
if amount:
if subtract_fee_from_outputs:
@@ -156,7 +163,7 @@ class WalletSendTest(BitcoinTestFramework):
def run_test(self):
self.log.info("Setup wallets...")
# w0 is a wallet with coinbase rewards
- w0 = self.nodes[0].get_wallet_rpc("")
+ w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
# w1 is a regular wallet
self.nodes[1].createwallet(wallet_name="w1")
w1 = self.nodes[1].get_wallet_rpc("w1")
@@ -164,7 +171,7 @@ class WalletSendTest(BitcoinTestFramework):
self.nodes[1].createwallet(wallet_name="w2")
w2 = self.nodes[1].get_wallet_rpc("w2")
# w3 is a watch-only wallet, based on w2
- self.nodes[1].createwallet(wallet_name="w3",disable_private_keys=True)
+ self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True)
w3 = self.nodes[1].get_wallet_rpc("w3")
for _ in range(3):
a2_receive = w2.getnewaddress()
@@ -188,7 +195,7 @@ class WalletSendTest(BitcoinTestFramework):
self.sync_blocks()
# w4 has private keys enabled, but only contains watch-only keys (from w2)
- self.nodes[1].createwallet(wallet_name="w4",disable_private_keys=False)
+ self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False)
w4 = self.nodes[1].get_wallet_rpc("w4")
for _ in range(3):
a2_receive = w2.getnewaddress()
@@ -224,8 +231,10 @@ class WalletSendTest(BitcoinTestFramework):
assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"],
self.nodes[1].decodepsbt(res2["psbt"])["fee"])
# but not at the same time
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical",
- conf_target=1, estimate_mode="economical", add_to_wallet=False, expect_error=(-8,"Use either conf_target and estimate_mode or the options dictionary to control fee rate"))
+ for mode in ["unset", "economical", "conservative"]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical",
+ conf_target=1, estimate_mode=mode, add_to_wallet=False,
+ expect_error=(-8, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both"))
self.log.info("Create PSBT from watch-only wallet w3, sign with w2...")
res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1)
@@ -246,19 +255,63 @@ class WalletSendTest(BitcoinTestFramework):
res = w2.walletprocesspsbt(res["psbt"])
assert res["complete"]
- self.log.info("Set fee rate...")
- res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=2, estimate_mode="sat/b", add_to_wallet=False)
+ self.log.info("Test setting explicit fee rate")
+ res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, add_to_wallet=False)
+ res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=1, add_to_wallet=False)
+ assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"], self.nodes[1].decodepsbt(res2["psbt"])["fee"])
+
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=7, add_to_wallet=False)
+ fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
+ assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00007"))
+
+ # "unset" and None are treated the same for estimate_mode
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=2, estimate_mode="unset", add_to_wallet=False)
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00002"))
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=-1, estimate_mode="sat/b",
- expect_error=(-3, "Amount out of range"))
- # Fee rate of 0.1 satoshi per byte should throw an error
- # TODO: error should say 1.000 sat/b
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b",
- expect_error=(-4, "Fee rate (0.00000100 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)"))
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.000001, estimate_mode="BTC/KB",
- expect_error=(-4, "Fee rate (0.00000100 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)"))
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=4.531, add_to_wallet=False)
+ fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
+ assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00004531"))
+
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=3, add_to_wallet=False)
+ fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
+ assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00003"))
+
+ # Test that passing fee_rate as both an argument and an option raises.
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, fee_rate=1, add_to_wallet=False,
+ expect_error=(-8, "Pass the fee_rate either as an argument, or in the options object, but not both"))
+
+ assert_raises_rpc_error(-8, "Use fee_rate (sat/vB) instead of feeRate", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"feeRate": 0.01})
+
+ assert_raises_rpc_error(-3, "Unexpected key totalFee", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"totalFee": 0.01})
+
+ for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode,
+ expect_error=(-8, "Invalid conf_target, must be between 1 and 1008")) # max value of 1008 per src/policy/fees.h
+ msg = 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"'
+ for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode, expect_error=(-8, msg))
+ for mode in ["", "foo", Decimal("3.141592")]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode=mode, expect_error=(-8, msg))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.1, arg_estimate_mode=mode, expect_error=(-8, msg))
+ assert_raises_rpc_error(-8, msg, w0.send, {w1.getnewaddress(): 1}, 0.1, mode)
+
+ for mode in ["economical", "conservative", "btc/kb", "sat/b"]:
+ self.log.debug("{}".format(mode))
+ for k, v in {"string": "true", "object": {"foo": "bar"}}.items():
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
+ expect_error=(-3, "Expected type number for conf_target, got {}".format(k)))
+
+ # Test setting explicit fee rate just below the minimum and at zero.
+ self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed")
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0.99999999,
+ expect_error=(-4, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=0.99999999,
+ expect_error=(-4, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0,
+ expect_error=(-4, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=0,
+ expect_error=(-4, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
# TODO: Return hex if fee rate is below -maxmempool
# res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b", add_to_wallet=False)
@@ -316,6 +369,7 @@ class WalletSendTest(BitcoinTestFramework):
res = self.nodes[0].sendrawtransaction(hex)
self.nodes[0].generate(1)
assert_equal(self.nodes[0].gettransaction(txid)["confirmations"], 1)
+ self.sync_all()
self.log.info("Lock unspents...")
utxo1 = w0.listunspent()[0]
@@ -325,11 +379,16 @@ class WalletSendTest(BitcoinTestFramework):
locked_coins = w0.listlockunspent()
assert_equal(len(locked_coins), 1)
# Locked coins are automatically unlocked when manually selected
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1],add_to_wallet=False)
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1], add_to_wallet=False)
+ assert res["complete"]
self.log.info("Replaceable...")
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, replaceable=True)
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, replaceable=False)
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True, replaceable=True)
+ assert res["complete"]
+ assert_equal(self.nodes[0].gettransaction(res["txid"])["bip125-replaceable"], "yes")
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True, replaceable=False)
+ assert res["complete"]
+ assert_equal(self.nodes[0].gettransaction(res["txid"])["bip125-replaceable"], "no")
self.log.info("Subtract fee from output")
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, subtract_fee_from_outputs=[0])
diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py
index 33a2a9411e..bdbbb3e530 100755
--- a/test/functional/wallet_txn_clone.py
+++ b/test/functional/wallet_txn_clone.py
@@ -8,8 +8,6 @@ import io
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
- disconnect_nodes,
)
from test_framework.messages import CTransaction, COIN
@@ -30,7 +28,7 @@ class TxnMallTest(BitcoinTestFramework):
def setup_network(self):
# Start with split network:
super().setup_network()
- disconnect_nodes(self.nodes[1], 2)
+ self.disconnect_nodes(1, 2)
def run_test(self):
if self.options.segwit:
@@ -118,7 +116,7 @@ class TxnMallTest(BitcoinTestFramework):
self.nodes[2].generate(1)
# Reconnect the split network, and sync chain:
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(1, 2)
self.nodes[2].sendrawtransaction(node0_tx2["hex"])
self.nodes[2].sendrawtransaction(tx2["hex"])
self.nodes[2].generate(1) # Mine another block to make sure we sync
diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py
index cac58aeaf2..42de131354 100755
--- a/test/functional/wallet_txn_doublespend.py
+++ b/test/functional/wallet_txn_doublespend.py
@@ -8,8 +8,6 @@ from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- connect_nodes,
- disconnect_nodes,
find_output,
)
@@ -28,7 +26,7 @@ class TxnMallTest(BitcoinTestFramework):
def setup_network(self):
# Start with split network:
super().setup_network()
- disconnect_nodes(self.nodes[1], 2)
+ self.disconnect_nodes(1, 2)
def run_test(self):
# All nodes should start with 1,250 BTC:
@@ -116,7 +114,7 @@ class TxnMallTest(BitcoinTestFramework):
self.nodes[2].generate(1)
# Reconnect the split network, and sync chain:
- connect_nodes(self.nodes[1], 2)
+ self.connect_nodes(1, 2)
self.nodes[2].generate(1) # Mine another block to make sure we sync
self.sync_blocks()
assert_equal(self.nodes[0].gettransaction(doublespend_txid)["confirmations"], 2)
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index 031da8da81..d0bb6135a8 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -13,24 +13,47 @@ Only v0.15.2 and v0.16.3 are required by this test. The others are used in featu
import os
import shutil
+import struct
+from io import BytesIO
+
+from test_framework.bdb import dump_bdb_kv
+from test_framework.messages import deser_compact_size, deser_string
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- assert_greater_than,
assert_is_hex_string,
+ sha256sum_file,
)
+UPGRADED_KEYMETA_VERSION = 12
+
+def deser_keymeta(f):
+ ver, create_time = struct.unpack('<Iq', f.read(12))
+ kp_str = deser_string(f)
+ seed_id = f.read(20)
+ fpr = f.read(4)
+ path_len = 0
+ path = []
+ has_key_orig = False
+ if ver == UPGRADED_KEYMETA_VERSION:
+ path_len = deser_compact_size(f)
+ for i in range(0, path_len):
+ path.append(struct.unpack('<I', f.read(4))[0])
+ has_key_orig = bool(f.read(1))
+ return ver, create_time, kp_str, seed_id, fpr, path_len, path, has_key_orig
+
class UpgradeWalletTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.extra_args = [
- ["-addresstype=bech32", "-wallet="], # current wallet version
- ["-usehd=1"], # v0.16.3 wallet
- ["-usehd=0"] # v0.15.2 wallet
+ ["-addresstype=bech32", "-keypool=2"], # current wallet version
+ ["-usehd=1", "-keypool=2"], # v0.16.3 wallet
+ ["-usehd=0", "-keypool=2"] # v0.15.2 wallet
]
+ self.wallet_names = [self.default_wallet_name, None, None]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -46,6 +69,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
150200,
])
self.start_nodes()
+ self.import_deterministic_coinbase_privkeys()
def dumb_sync_blocks(self):
"""
@@ -66,6 +90,32 @@ class UpgradeWalletTest(BitcoinTestFramework):
v16_3_node.submitblock(b)
assert_equal(v16_3_node.getblockcount(), to_height)
+ def test_upgradewallet(self, wallet, previous_version, requested_version=None, expected_version=None):
+ unchanged = expected_version == previous_version
+ new_version = previous_version if unchanged else expected_version if expected_version else requested_version
+ assert_equal(wallet.getwalletinfo()["walletversion"], previous_version)
+ assert_equal(wallet.upgradewallet(requested_version),
+ {
+ "wallet_name": "",
+ "previous_version": previous_version,
+ "current_version": new_version,
+ "result": "Already at latest version. Wallet version unchanged." if unchanged else "Wallet upgraded successfully from version {} to version {}.".format(previous_version, new_version),
+ }
+ )
+ assert_equal(wallet.getwalletinfo()["walletversion"], new_version)
+
+ def test_upgradewallet_error(self, wallet, previous_version, requested_version, msg):
+ assert_equal(wallet.getwalletinfo()["walletversion"], previous_version)
+ assert_equal(wallet.upgradewallet(requested_version),
+ {
+ "wallet_name": "",
+ "previous_version": previous_version,
+ "current_version": previous_version,
+ "error": msg,
+ }
+ )
+ assert_equal(wallet.getwalletinfo()["walletversion"], previous_version)
+
def run_test(self):
self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress())
self.dumb_sync_blocks()
@@ -85,55 +135,222 @@ class UpgradeWalletTest(BitcoinTestFramework):
self.log.info("Test upgradewallet RPC...")
# Prepare for copying of the older wallet
- node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets")
+ node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets", self.default_wallet_name)
+ node_master_wallet = os.path.join(node_master_wallet_dir, self.default_wallet_name, self.wallet_data_filename)
v16_3_wallet = os.path.join(v16_3_node.datadir, "regtest/wallets/wallet.dat")
v15_2_wallet = os.path.join(v15_2_node.datadir, "regtest/wallet.dat")
+ split_hd_wallet = os.path.join(v15_2_node.datadir, "regtest/splithd")
self.stop_nodes()
- # Copy the 0.16.3 wallet to the last Bitcoin Core version and open it:
- shutil.rmtree(node_master_wallet_dir)
- os.mkdir(node_master_wallet_dir)
- shutil.copy(
- v16_3_wallet,
- node_master_wallet_dir
- )
- self.restart_node(0, ['-nowallet'])
- node_master.loadwallet('')
+ # Make split hd wallet
+ self.start_node(2, ['-usehd=1', '-keypool=2', '-wallet=splithd'])
+ self.stop_node(2)
+
+ def copy_v16():
+ node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
+ # Copy the 0.16.3 wallet to the last Bitcoin Core version and open it:
+ shutil.rmtree(node_master_wallet_dir)
+ os.mkdir(node_master_wallet_dir)
+ shutil.copy(
+ v16_3_wallet,
+ node_master_wallet_dir
+ )
+ node_master.loadwallet(self.default_wallet_name)
+
+ def copy_non_hd():
+ node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
+ # Copy the 0.15.2 non hd wallet to the last Bitcoin Core version and open it:
+ shutil.rmtree(node_master_wallet_dir)
+ os.mkdir(node_master_wallet_dir)
+ shutil.copy(
+ v15_2_wallet,
+ node_master_wallet_dir
+ )
+ node_master.loadwallet(self.default_wallet_name)
- wallet = node_master.get_wallet_rpc('')
- old_version = wallet.getwalletinfo()["walletversion"]
+ def copy_split_hd():
+ node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
+ # Copy the 0.15.2 split hd wallet to the last Bitcoin Core version and open it:
+ shutil.rmtree(node_master_wallet_dir)
+ os.mkdir(node_master_wallet_dir)
+ shutil.copy(
+ split_hd_wallet,
+ os.path.join(node_master_wallet_dir, 'wallet.dat')
+ )
+ node_master.loadwallet(self.default_wallet_name)
- # calling upgradewallet without version arguments
- # should return nothing if successful
- assert_equal(wallet.upgradewallet(), "")
- new_version = wallet.getwalletinfo()["walletversion"]
- # upgraded wallet version should be greater than older one
- assert_greater_than(new_version, old_version)
+ self.restart_node(0)
+ copy_v16()
+ wallet = node_master.get_wallet_rpc(self.default_wallet_name)
+ self.log.info("Test upgradewallet without a version argument")
+ self.test_upgradewallet(wallet, previous_version=159900, expected_version=169900)
# wallet should still contain the same balance
assert_equal(wallet.getbalance(), v16_3_balance)
- self.stop_node(0)
- # Copy the 0.15.2 wallet to the last Bitcoin Core version and open it:
- shutil.rmtree(node_master_wallet_dir)
- os.mkdir(node_master_wallet_dir)
- shutil.copy(
- v15_2_wallet,
- node_master_wallet_dir
- )
- self.restart_node(0, ['-nowallet'])
- node_master.loadwallet('')
-
- wallet = node_master.get_wallet_rpc('')
+ copy_non_hd()
+ wallet = node_master.get_wallet_rpc(self.default_wallet_name)
# should have no master key hash before conversion
assert_equal('hdseedid' in wallet.getwalletinfo(), False)
- # calling upgradewallet with explicit version number
- # should return nothing if successful
- assert_equal(wallet.upgradewallet(169900), "")
- new_version = wallet.getwalletinfo()["walletversion"]
- # upgraded wallet should have version 169900
- assert_equal(new_version, 169900)
+ self.log.info("Test upgradewallet with explicit version number")
+ self.test_upgradewallet(wallet, previous_version=60000, requested_version=169900)
# after conversion master key hash should be present
assert_is_hex_string(wallet.getwalletinfo()['hdseedid'])
+ self.log.info("Intermediary versions don't effect anything")
+ copy_non_hd()
+ # Wallet starts with 60000
+ assert_equal(60000, wallet.getwalletinfo()['walletversion'])
+ wallet.unloadwallet()
+ before_checksum = sha256sum_file(node_master_wallet)
+ node_master.loadwallet('')
+ # Test an "upgrade" from 60000 to 129999 has no effect, as the next version is 130000
+ self.test_upgradewallet(wallet, previous_version=60000, requested_version=129999, expected_version=60000)
+ wallet.unloadwallet()
+ assert_equal(before_checksum, sha256sum_file(node_master_wallet))
+ node_master.loadwallet('')
+
+ self.log.info('Wallets cannot be downgraded')
+ copy_non_hd()
+ self.test_upgradewallet_error(wallet, previous_version=60000, requested_version=40000,
+ msg="Cannot downgrade wallet from version 60000 to version 40000. Wallet version unchanged.")
+ wallet.unloadwallet()
+ assert_equal(before_checksum, sha256sum_file(node_master_wallet))
+ node_master.loadwallet('')
+
+ self.log.info('Can upgrade to HD')
+ # Inspect the old wallet and make sure there is no hdchain
+ orig_kvs = dump_bdb_kv(node_master_wallet)
+ assert b'\x07hdchain' not in orig_kvs
+ # Upgrade to HD, no split
+ self.test_upgradewallet(wallet, previous_version=60000, requested_version=130000)
+ # Check that there is now a hd chain and it is version 1, no internal chain counter
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ assert b'\x07hdchain' in new_kvs
+ hd_chain = new_kvs[b'\x07hdchain']
+ assert_equal(28, len(hd_chain))
+ hd_chain_version, external_counter, seed_id = struct.unpack('<iI20s', hd_chain)
+ assert_equal(1, hd_chain_version)
+ seed_id = bytearray(seed_id)
+ seed_id.reverse()
+ old_kvs = new_kvs
+ # First 2 keys should still be non-HD
+ for i in range(0, 2):
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ assert 'hdkeypath' not in info
+ assert 'hdseedid' not in info
+ # Next key should be HD
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ assert_equal(seed_id.hex(), info['hdseedid'])
+ assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
+ prev_seed_id = info['hdseedid']
+ # Change key should be the same keypool
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert_equal(prev_seed_id, info['hdseedid'])
+ assert_equal('m/0\'/0\'/1\'', info['hdkeypath'])
+
+ self.log.info('Cannot upgrade to HD Split, needs Pre Split Keypool')
+ for version in [139900, 159900, 169899]:
+ self.test_upgradewallet_error(wallet, previous_version=130000, requested_version=version,
+ msg="Cannot upgrade a non HD split wallet from version {} to version {} without upgrading to "
+ "support pre-split keypool. Please use version 169900 or no version specified.".format(130000, version))
+
+ self.log.info('Upgrade HD to HD chain split')
+ self.test_upgradewallet(wallet, previous_version=130000, requested_version=169900)
+ # Check that the hdchain updated correctly
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ hd_chain = new_kvs[b'\x07hdchain']
+ assert_equal(32, len(hd_chain))
+ hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
+ assert_equal(2, hd_chain_version)
+ assert_equal(0, internal_counter)
+ seed_id = bytearray(seed_id)
+ seed_id.reverse()
+ assert_equal(seed_id.hex(), prev_seed_id)
+ # Next change address is the same keypool
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert_equal(prev_seed_id, info['hdseedid'])
+ assert_equal('m/0\'/0\'/2\'', info['hdkeypath'])
+ # Next change address is the new keypool
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert_equal(prev_seed_id, info['hdseedid'])
+ assert_equal('m/0\'/1\'/0\'', info['hdkeypath'])
+ # External addresses use the same keypool
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ assert_equal(prev_seed_id, info['hdseedid'])
+ assert_equal('m/0\'/0\'/3\'', info['hdkeypath'])
+
+ self.log.info('Upgrade non-HD to HD chain split')
+ copy_non_hd()
+ self.test_upgradewallet(wallet, previous_version=60000, requested_version=169900)
+ # Check that the hdchain updated correctly
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ hd_chain = new_kvs[b'\x07hdchain']
+ assert_equal(32, len(hd_chain))
+ hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
+ assert_equal(2, hd_chain_version)
+ assert_equal(2, internal_counter)
+ # Drain the keypool by fetching one external key and one change key. Should still be the same keypool
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ assert 'hdseedid' not in info
+ assert 'hdkeypath' not in info
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert 'hdseedid' not in info
+ assert 'hdkeypath' not in info
+ # The next addresses are HD and should be on different HD chains
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ ext_id = info['hdseedid']
+ assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert_equal(ext_id, info['hdseedid'])
+ assert_equal('m/0\'/1\'/0\'', info['hdkeypath'])
+
+ self.log.info('KeyMetadata should upgrade when loading into master')
+ copy_v16()
+ old_kvs = dump_bdb_kv(v16_3_wallet)
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ for k, old_v in old_kvs.items():
+ if k.startswith(b'\x07keymeta'):
+ new_ver, new_create_time, new_kp_str, new_seed_id, new_fpr, new_path_len, new_path, new_has_key_orig = deser_keymeta(BytesIO(new_kvs[k]))
+ old_ver, old_create_time, old_kp_str, old_seed_id, old_fpr, old_path_len, old_path, old_has_key_orig = deser_keymeta(BytesIO(old_v))
+ assert_equal(10, old_ver)
+ if old_kp_str == b"": # imported things that don't have keymeta (i.e. imported coinbase privkeys) won't be upgraded
+ assert_equal(new_kvs[k], old_v)
+ continue
+ assert_equal(12, new_ver)
+ assert_equal(new_create_time, old_create_time)
+ assert_equal(new_kp_str, old_kp_str)
+ assert_equal(new_seed_id, old_seed_id)
+ assert_equal(0, old_path_len)
+ assert_equal(new_path_len, len(new_path))
+ assert_equal([], old_path)
+ assert_equal(False, old_has_key_orig)
+ assert_equal(True, new_has_key_orig)
+
+ # Check that the path is right
+ built_path = []
+ for s in new_kp_str.decode().split('/')[1:]:
+ h = 0
+ if s[-1] == '\'':
+ s = s[:-1]
+ h = 0x80000000
+ p = int(s) | h
+ built_path.append(p)
+ assert_equal(new_path, built_path)
+
+ self.log.info('Upgrading to NO_DEFAULT_KEY should not remove the defaultkey')
+ copy_split_hd()
+ # Check the wallet has a default key initially
+ old_kvs = dump_bdb_kv(node_master_wallet)
+ defaultkey = old_kvs[b'\x0adefaultkey']
+ self.log.info("Upgrade the wallet. Should still have the same default key.")
+ self.test_upgradewallet(wallet, previous_version=139900, requested_version=159900)
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ up_defaultkey = new_kvs[b'\x0adefaultkey']
+ assert_equal(defaultkey, up_defaultkey)
+ # 0.16.3 doesn't have a default key
+ v16_3_kvs = dump_bdb_kv(v16_3_wallet)
+ assert b'\x0adefaultkey' not in v16_3_kvs
+
+
if __name__ == '__main__':
UpgradeWalletTest().main()