aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/CMakeLists.txt48
-rw-r--r--test/README.md40
-rw-r--r--test/functional/.gitignore1
-rw-r--r--test/functional/README.md3
-rwxr-xr-xtest/functional/create_cache.py2
-rw-r--r--test/functional/data/invalid_txs.py4
-rw-r--r--test/functional/data/rpc_psbt.json4
-rwxr-xr-xtest/functional/example_test.py2
-rwxr-xr-xtest/functional/feature_abortnode.py2
-rwxr-xr-xtest/functional/feature_addrman.py2
-rwxr-xr-xtest/functional/feature_anchors.py2
-rwxr-xr-xtest/functional/feature_asmap.py7
-rwxr-xr-xtest/functional/feature_assumeutxo.py385
-rwxr-xr-xtest/functional/feature_assumevalid.py6
-rwxr-xr-xtest/functional/feature_bind_extra.py25
-rwxr-xr-xtest/functional/feature_bind_port_discover.py2
-rwxr-xr-xtest/functional/feature_bind_port_externalip.py2
-rwxr-xr-xtest/functional/feature_bip68_sequence.py2
-rwxr-xr-xtest/functional/feature_block.py20
-rwxr-xr-xtest/functional/feature_blocksdir.py2
-rwxr-xr-xtest/functional/feature_blocksxor.py68
-rwxr-xr-xtest/functional/feature_cltv.py2
-rwxr-xr-xtest/functional/feature_coinstatsindex.py5
-rwxr-xr-xtest/functional/feature_config_args.py45
-rwxr-xr-xtest/functional/feature_csv_activation.py2
-rwxr-xr-xtest/functional/feature_dbcrash.py2
-rwxr-xr-xtest/functional/feature_dersig.py2
-rwxr-xr-xtest/functional/feature_dirsymlinks.py2
-rwxr-xr-xtest/functional/feature_discover.py2
-rwxr-xr-xtest/functional/feature_fastprune.py2
-rwxr-xr-xtest/functional/feature_fee_estimation.py61
-rwxr-xr-xtest/functional/feature_filelock.py2
-rwxr-xr-xtest/functional/feature_framework_miniwallet.py36
-rwxr-xr-xtest/functional/feature_help.py2
-rwxr-xr-xtest/functional/feature_includeconf.py2
-rwxr-xr-xtest/functional/feature_index_prune.py2
-rwxr-xr-xtest/functional/feature_init.py2
-rwxr-xr-xtest/functional/feature_loadblock.py2
-rwxr-xr-xtest/functional/feature_logging.py2
-rwxr-xr-xtest/functional/feature_maxtipage.py2
-rwxr-xr-xtest/functional/feature_maxuploadtarget.py2
-rwxr-xr-xtest/functional/feature_minchainwork.py4
-rwxr-xr-xtest/functional/feature_notifications.py2
-rwxr-xr-xtest/functional/feature_nulldummy.py2
-rwxr-xr-xtest/functional/feature_posix_fs_permissions.py2
-rwxr-xr-xtest/functional/feature_presegwit_node_upgrade.py2
-rwxr-xr-xtest/functional/feature_proxy.py13
-rwxr-xr-xtest/functional/feature_pruning.py20
-rwxr-xr-xtest/functional/feature_rbf.py7
-rwxr-xr-xtest/functional/feature_reindex.py17
-rwxr-xr-xtest/functional/feature_reindex_readonly.py2
-rwxr-xr-xtest/functional/feature_remove_pruned_files_on_startup.py2
-rwxr-xr-xtest/functional/feature_segwit.py2
-rwxr-xr-xtest/functional/feature_settings.py25
-rwxr-xr-xtest/functional/feature_shutdown.py2
-rwxr-xr-xtest/functional/feature_signet.py2
-rwxr-xr-xtest/functional/feature_startupnotify.py2
-rwxr-xr-xtest/functional/feature_taproot.py4
-rwxr-xr-xtest/functional/feature_uacomment.py2
-rwxr-xr-xtest/functional/feature_unsupported_utxo_db.py2
-rwxr-xr-xtest/functional/feature_utxo_set_hash.py2
-rwxr-xr-xtest/functional/feature_versionbits_warning.py2
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py16
-rwxr-xr-xtest/functional/interface_http.py2
-rwxr-xr-xtest/functional/interface_rest.py9
-rwxr-xr-xtest/functional/interface_rpc.py2
-rwxr-xr-xtest/functional/interface_usdt_coinselection.py12
-rwxr-xr-xtest/functional/interface_usdt_mempool.py2
-rwxr-xr-xtest/functional/interface_usdt_net.py2
-rwxr-xr-xtest/functional/interface_usdt_utxocache.py2
-rwxr-xr-xtest/functional/interface_usdt_validation.py2
-rwxr-xr-xtest/functional/interface_zmq.py2
-rwxr-xr-xtest/functional/mempool_accept.py63
-rwxr-xr-xtest/functional/mempool_accept_wtxid.py2
-rwxr-xr-xtest/functional/mempool_compatibility.py2
-rwxr-xr-xtest/functional/mempool_datacarrier.py2
-rwxr-xr-xtest/functional/mempool_dust.py2
-rwxr-xr-xtest/functional/mempool_expiry.py2
-rwxr-xr-xtest/functional/mempool_limit.py33
-rwxr-xr-xtest/functional/mempool_package_limits.py19
-rwxr-xr-xtest/functional/mempool_package_onemore.py2
-rwxr-xr-xtest/functional/mempool_package_rbf.py50
-rwxr-xr-xtest/functional/mempool_packages.py2
-rwxr-xr-xtest/functional/mempool_persist.py2
-rwxr-xr-xtest/functional/mempool_reorg.py4
-rwxr-xr-xtest/functional/mempool_resurrect.py2
-rwxr-xr-xtest/functional/mempool_sigoplimit.py4
-rwxr-xr-xtest/functional/mempool_spend_coinbase.py2
-rwxr-xr-xtest/functional/mempool_truc.py (renamed from test/functional/mempool_accept_v3.py)185
-rwxr-xr-xtest/functional/mempool_unbroadcast.py2
-rwxr-xr-xtest/functional/mempool_updatefromblock.py2
-rwxr-xr-xtest/functional/mining_basic.py56
-rwxr-xr-xtest/functional/mining_getblocktemplate_longpoll.py2
-rwxr-xr-xtest/functional/mining_prioritisetransaction.py2
-rwxr-xr-xtest/functional/p2p_1p1c_network.py7
-rwxr-xr-xtest/functional/p2p_add_connections.py26
-rwxr-xr-xtest/functional/p2p_addr_relay.py2
-rwxr-xr-xtest/functional/p2p_addrfetch.py2
-rwxr-xr-xtest/functional/p2p_addrv2_relay.py2
-rwxr-xr-xtest/functional/p2p_block_sync.py2
-rwxr-xr-xtest/functional/p2p_blockfilters.py2
-rwxr-xr-xtest/functional/p2p_blocksonly.py2
-rwxr-xr-xtest/functional/p2p_compactblocks.py2
-rwxr-xr-xtest/functional/p2p_compactblocks_blocksonly.py2
-rwxr-xr-xtest/functional/p2p_compactblocks_hb.py2
-rwxr-xr-xtest/functional/p2p_disconnect_ban.py2
-rwxr-xr-xtest/functional/p2p_dns_seeds.py2
-rwxr-xr-xtest/functional/p2p_dos_header_tree.py2
-rwxr-xr-xtest/functional/p2p_eviction.py2
-rwxr-xr-xtest/functional/p2p_feefilter.py2
-rwxr-xr-xtest/functional/p2p_filter.py2
-rwxr-xr-xtest/functional/p2p_fingerprint.py2
-rwxr-xr-xtest/functional/p2p_getaddr_caching.py2
-rwxr-xr-xtest/functional/p2p_getdata.py2
-rwxr-xr-xtest/functional/p2p_handshake.py9
-rwxr-xr-xtest/functional/p2p_headers_sync_with_minchainwork.py12
-rwxr-xr-xtest/functional/p2p_i2p_ports.py2
-rwxr-xr-xtest/functional/p2p_i2p_sessions.py2
-rwxr-xr-xtest/functional/p2p_ibd_stalling.py5
-rwxr-xr-xtest/functional/p2p_ibd_txrelay.py2
-rwxr-xr-xtest/functional/p2p_initial_headers_sync.py2
-rwxr-xr-xtest/functional/p2p_invalid_block.py2
-rwxr-xr-xtest/functional/p2p_invalid_locator.py2
-rwxr-xr-xtest/functional/p2p_invalid_messages.py2
-rwxr-xr-xtest/functional/p2p_invalid_tx.py2
-rwxr-xr-xtest/functional/p2p_leak.py2
-rwxr-xr-xtest/functional/p2p_leak_tx.py2
-rwxr-xr-xtest/functional/p2p_message_capture.py2
-rwxr-xr-xtest/functional/p2p_mutated_blocks.py2
-rwxr-xr-xtest/functional/p2p_net_deadlock.py2
-rwxr-xr-xtest/functional/p2p_nobloomfilter_messages.py2
-rwxr-xr-xtest/functional/p2p_node_network_limited.py6
-rwxr-xr-xtest/functional/p2p_opportunistic_1p1c.py2
-rwxr-xr-xtest/functional/p2p_orphan_handling.py2
-rwxr-xr-xtest/functional/p2p_outbound_eviction.py2
-rwxr-xr-xtest/functional/p2p_permissions.py14
-rwxr-xr-xtest/functional/p2p_ping.py2
-rwxr-xr-xtest/functional/p2p_seednode.py55
-rwxr-xr-xtest/functional/p2p_segwit.py2
-rwxr-xr-xtest/functional/p2p_sendheaders.py2
-rwxr-xr-xtest/functional/p2p_sendtxrcncl.py2
-rwxr-xr-xtest/functional/p2p_timeouts.py2
-rwxr-xr-xtest/functional/p2p_tx_download.py8
-rwxr-xr-xtest/functional/p2p_tx_privacy.py2
-rwxr-xr-xtest/functional/p2p_unrequested_blocks.py10
-rwxr-xr-xtest/functional/p2p_v2_earlykeyresponse.py89
-rwxr-xr-xtest/functional/p2p_v2_encrypted.py2
-rwxr-xr-xtest/functional/p2p_v2_misbehaving.py189
-rwxr-xr-xtest/functional/p2p_v2_transport.py2
-rwxr-xr-xtest/functional/rpc_bind.py19
-rwxr-xr-xtest/functional/rpc_blockchain.py98
-rwxr-xr-xtest/functional/rpc_createmultisig.py6
-rwxr-xr-xtest/functional/rpc_decodescript.py12
-rwxr-xr-xtest/functional/rpc_deprecated.py2
-rwxr-xr-xtest/functional/rpc_deriveaddresses.py5
-rwxr-xr-xtest/functional/rpc_dumptxoutset.py35
-rwxr-xr-xtest/functional/rpc_estimatefee.py2
-rwxr-xr-xtest/functional/rpc_generate.py2
-rwxr-xr-xtest/functional/rpc_getblockfilter.py2
-rwxr-xr-xtest/functional/rpc_getblockfrompeer.py4
-rwxr-xr-xtest/functional/rpc_getblockstats.py15
-rwxr-xr-xtest/functional/rpc_getchaintips.py2
-rwxr-xr-xtest/functional/rpc_getdescriptorinfo.py16
-rwxr-xr-xtest/functional/rpc_getorphantxs.py130
-rwxr-xr-xtest/functional/rpc_help.py2
-rwxr-xr-xtest/functional/rpc_invalid_address_message.py7
-rwxr-xr-xtest/functional/rpc_invalidateblock.py2
-rwxr-xr-xtest/functional/rpc_mempool_info.py2
-rwxr-xr-xtest/functional/rpc_misc.py2
-rwxr-xr-xtest/functional/rpc_named_arguments.py2
-rwxr-xr-xtest/functional/rpc_net.py29
-rwxr-xr-xtest/functional/rpc_packages.py2
-rwxr-xr-xtest/functional/rpc_preciousblock.py2
-rwxr-xr-xtest/functional/rpc_psbt.py76
-rwxr-xr-xtest/functional/rpc_rawtransaction.py6
-rwxr-xr-xtest/functional/rpc_scanblocks.py2
-rwxr-xr-xtest/functional/rpc_scantxoutset.py13
-rwxr-xr-xtest/functional/rpc_setban.py2
-rwxr-xr-xtest/functional/rpc_signer.py2
-rwxr-xr-xtest/functional/rpc_signmessagewithprivkey.py2
-rwxr-xr-xtest/functional/rpc_signrawtransactionwithkey.py45
-rwxr-xr-xtest/functional/rpc_txoutproof.py6
-rwxr-xr-xtest/functional/rpc_uptime.py2
-rwxr-xr-xtest/functional/rpc_users.py62
-rwxr-xr-xtest/functional/rpc_validateaddress.py7
-rwxr-xr-xtest/functional/rpc_whitelist.py2
-rw-r--r--test/functional/test-shell.md2
-rw-r--r--test/functional/test_framework/address.py10
-rw-r--r--test/functional/test_framework/blocktools.py11
-rw-r--r--test/functional/test_framework/key.py6
-rw-r--r--test/functional/test_framework/mempool_util.py32
-rwxr-xr-xtest/functional/test_framework/messages.py7
-rwxr-xr-xtest/functional/test_framework/p2p.py2
-rw-r--r--test/functional/test_framework/script.py10
-rwxr-xr-xtest/functional/test_framework/script_util.py3
-rwxr-xr-xtest/functional/test_framework/test_framework.py10
-rwxr-xr-xtest/functional/test_framework/test_node.py33
-rw-r--r--test/functional/test_framework/test_shell.py10
-rw-r--r--test/functional/test_framework/util.py35
-rw-r--r--test/functional/test_framework/v2_p2p.py12
-rw-r--r--test/functional/test_framework/wallet.py44
-rwxr-xr-xtest/functional/test_runner.py50
-rwxr-xr-xtest/functional/tool_signet_miner.py3
-rwxr-xr-xtest/functional/tool_wallet.py2
-rwxr-xr-xtest/functional/wallet_abandonconflict.py2
-rwxr-xr-xtest/functional/wallet_address_types.py2
-rwxr-xr-xtest/functional/wallet_assumeutxo.py44
-rwxr-xr-xtest/functional/wallet_avoid_mixing_output_types.py2
-rwxr-xr-xtest/functional/wallet_avoidreuse.py2
-rwxr-xr-xtest/functional/wallet_backup.py23
-rwxr-xr-xtest/functional/wallet_backwards_compatibility.py29
-rwxr-xr-xtest/functional/wallet_balance.py2
-rwxr-xr-xtest/functional/wallet_basic.py2
-rwxr-xr-xtest/functional/wallet_blank.py2
-rwxr-xr-xtest/functional/wallet_bumpfee.py4
-rwxr-xr-xtest/functional/wallet_change_address.py2
-rwxr-xr-xtest/functional/wallet_coinbase_category.py2
-rwxr-xr-xtest/functional/wallet_conflicts.py4
-rwxr-xr-xtest/functional/wallet_create_tx.py10
-rwxr-xr-xtest/functional/wallet_createwallet.py2
-rwxr-xr-xtest/functional/wallet_createwalletdescriptor.py2
-rwxr-xr-xtest/functional/wallet_crosschain.py31
-rwxr-xr-xtest/functional/wallet_descriptor.py2
-rwxr-xr-xtest/functional/wallet_disable.py2
-rwxr-xr-xtest/functional/wallet_dump.py2
-rwxr-xr-xtest/functional/wallet_encryption.py2
-rwxr-xr-xtest/functional/wallet_fallbackfee.py2
-rwxr-xr-xtest/functional/wallet_fast_rescan.py2
-rwxr-xr-xtest/functional/wallet_fundrawtransaction.py8
-rwxr-xr-xtest/functional/wallet_gethdkeys.py2
-rwxr-xr-xtest/functional/wallet_groups.py2
-rwxr-xr-xtest/functional/wallet_hd.py2
-rwxr-xr-xtest/functional/wallet_implicitsegwit.py2
-rwxr-xr-xtest/functional/wallet_import_rescan.py2
-rwxr-xr-xtest/functional/wallet_import_with_label.py2
-rwxr-xr-xtest/functional/wallet_importdescriptors.py54
-rwxr-xr-xtest/functional/wallet_importmulti.py39
-rwxr-xr-xtest/functional/wallet_importprunedfunds.py2
-rwxr-xr-xtest/functional/wallet_inactive_hdchains.py2
-rwxr-xr-xtest/functional/wallet_keypool.py2
-rwxr-xr-xtest/functional/wallet_keypool_topup.py2
-rwxr-xr-xtest/functional/wallet_labels.py2
-rwxr-xr-xtest/functional/wallet_listdescriptors.py2
-rwxr-xr-xtest/functional/wallet_listreceivedby.py2
-rwxr-xr-xtest/functional/wallet_listsinceblock.py2
-rwxr-xr-xtest/functional/wallet_listtransactions.py2
-rwxr-xr-xtest/functional/wallet_migration.py21
-rwxr-xr-xtest/functional/wallet_miniscript.py2
-rwxr-xr-xtest/functional/wallet_multisig_descriptor_psbt.py27
-rwxr-xr-xtest/functional/wallet_multiwallet.py6
-rwxr-xr-xtest/functional/wallet_orphanedreward.py2
-rwxr-xr-xtest/functional/wallet_pruning.py2
-rwxr-xr-xtest/functional/wallet_reindex.py2
-rwxr-xr-xtest/functional/wallet_reorgsrestore.py2
-rwxr-xr-xtest/functional/wallet_rescan_unconfirmed.py2
-rwxr-xr-xtest/functional/wallet_resendwallettransactions.py2
-rwxr-xr-xtest/functional/wallet_send.py2
-rwxr-xr-xtest/functional/wallet_sendall.py2
-rwxr-xr-xtest/functional/wallet_sendmany.py2
-rwxr-xr-xtest/functional/wallet_signer.py2
-rwxr-xr-xtest/functional/wallet_signmessagewithaddress.py2
-rwxr-xr-xtest/functional/wallet_signrawtransactionwithwallet.py2
-rwxr-xr-xtest/functional/wallet_simulaterawtx.py2
-rwxr-xr-xtest/functional/wallet_spend_unconfirmed.py2
-rwxr-xr-xtest/functional/wallet_startup.py2
-rwxr-xr-xtest/functional/wallet_taproot.py2
-rwxr-xr-xtest/functional/wallet_timelock.py2
-rwxr-xr-xtest/functional/wallet_transactiontime_rescan.py2
-rwxr-xr-xtest/functional/wallet_txn_clone.py2
-rwxr-xr-xtest/functional/wallet_txn_doublespend.py2
-rwxr-xr-xtest/functional/wallet_upgradewallet.py13
-rwxr-xr-xtest/functional/wallet_watchonly.py2
-rwxr-xr-xtest/fuzz/test_runner.py8
-rwxr-xr-xtest/get_previous_releases.py17
-rw-r--r--test/lint/README.md2
-rwxr-xr-xtest/lint/lint-assertions.py10
-rwxr-xr-xtest/lint/lint-format-strings.py22
-rwxr-xr-xtest/lint/lint-python-mutable-default-parameters.py72
-rwxr-xr-xtest/lint/lint-python.py96
-rwxr-xr-xtest/lint/lint-spelling.py2
-rwxr-xr-xtest/lint/run-lint-format-strings.py9
-rw-r--r--test/lint/spelling.ignore-words.txt13
-rw-r--r--test/lint/test_runner/src/main.rs153
-rw-r--r--test/sanitizer_suppressions/ubsan9
-rwxr-xr-xtest/util/test_runner.py2
285 files changed, 2561 insertions, 1119 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000000..3a5998697d
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,48 @@
+# Copyright (c) 2023-present The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or https://opensource.org/license/mit/.
+
+function(create_test_config)
+ set(abs_top_srcdir ${PROJECT_SOURCE_DIR})
+ set(abs_top_builddir ${PROJECT_BINARY_DIR})
+ set(EXEEXT ${CMAKE_EXECUTABLE_SUFFIX})
+
+ macro(set_configure_variable var conf_var)
+ if(${var})
+ set(${conf_var}_TRUE "")
+ else()
+ set(${conf_var}_TRUE "#")
+ endif()
+ endmacro()
+
+ set_configure_variable(ENABLE_WALLET ENABLE_WALLET)
+ set_configure_variable(WITH_SQLITE USE_SQLITE)
+ set_configure_variable(WITH_BDB USE_BDB)
+ set_configure_variable(BUILD_CLI BUILD_BITCOIN_CLI)
+ set_configure_variable(BUILD_UTIL BUILD_BITCOIN_UTIL)
+ set_configure_variable(BUILD_WALLET_TOOL BUILD_BITCOIN_WALLET)
+ set_configure_variable(BUILD_DAEMON BUILD_BITCOIND)
+ set_configure_variable(BUILD_FUZZ_BINARY ENABLE_FUZZ_BINARY)
+ set_configure_variable(WITH_ZMQ ENABLE_ZMQ)
+ set_configure_variable(ENABLE_EXTERNAL_SIGNER ENABLE_EXTERNAL_SIGNER)
+ set_configure_variable(WITH_USDT ENABLE_USDT_TRACEPOINTS)
+
+ configure_file(config.ini.in config.ini USE_SOURCE_PERMISSIONS @ONLY)
+endfunction()
+
+create_test_config()
+
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/functional)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/fuzz)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/util)
+
+file(GLOB_RECURSE functional_tests RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} functional/*)
+foreach(script ${functional_tests} fuzz/test_runner.py util/rpcauth-test.py util/test_runner.py)
+ if(CMAKE_HOST_WIN32)
+ set(symlink)
+ else()
+ set(symlink SYMBOLIC)
+ endif()
+ file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/${script} ${CMAKE_CURRENT_BINARY_DIR}/${script} COPY_ON_ERROR ${symlink})
+endforeach()
+unset(functional_tests)
diff --git a/test/README.md b/test/README.md
index 0d1e06c0ad..74adc644b8 100644
--- a/test/README.md
+++ b/test/README.md
@@ -13,13 +13,15 @@ interfaces.
- [util](/test/util) which tests the utilities (bitcoin-util, bitcoin-tx, ...).
- [lint](/test/lint/) which perform various static analysis checks.
-The util tests are run as part of `make check` target. The fuzz tests, functional
+The util tests are run as part of `ctest` invocation. The fuzz tests, functional
tests and lint scripts can be run as explained in the sections below.
# Running tests locally
Before tests can be run locally, Bitcoin Core must be built. See the [building instructions](/doc#building) for help.
+The following examples assume that the build directory is named `build`.
+
## Fuzz tests
See [/doc/fuzzing.md](/doc/fuzzing.md)
@@ -45,19 +47,19 @@ set PYTHONUTF8=1
Individual tests can be run by directly calling the test script, e.g.:
```
-test/functional/feature_rbf.py
+build/test/functional/feature_rbf.py
```
or can be run through the test_runner harness, eg:
```
-test/functional/test_runner.py feature_rbf.py
+build/test/functional/test_runner.py feature_rbf.py
```
You can run any combination (incl. duplicates) of tests by calling:
```
-test/functional/test_runner.py <testname1> <testname2> <testname3> ...
+build/test/functional/test_runner.py <testname1> <testname2> <testname3> ...
```
Wildcard test names can be passed, if the paths are coherent and the test runner
@@ -65,34 +67,34 @@ is called from a `bash` shell or similar that does the globbing. For example,
to run all the wallet tests:
```
-test/functional/test_runner.py test/functional/wallet*
-functional/test_runner.py functional/wallet* (called from the test/ directory)
-test_runner.py wallet* (called from the test/functional/ directory)
+build/test/functional/test_runner.py test/functional/wallet*
+functional/test_runner.py functional/wallet* # (called from the build/test/ directory)
+test_runner.py wallet* # (called from the build/test/functional/ directory)
```
but not
```
-test/functional/test_runner.py wallet*
+build/test/functional/test_runner.py wallet*
```
Combinations of wildcards can be passed:
```
-test/functional/test_runner.py ./test/functional/tool* test/functional/mempool*
+build/test/functional/test_runner.py ./test/functional/tool* test/functional/mempool*
test_runner.py tool* mempool*
```
Run the regression test suite with:
```
-test/functional/test_runner.py
+build/test/functional/test_runner.py
```
Run all possible tests with
```
-test/functional/test_runner.py --extended
+build/test/functional/test_runner.py --extended
```
In order to run backwards compatibility tests, first run:
@@ -107,7 +109,7 @@ By default, up to 4 tests will be run in parallel by test_runner. To specify
how many jobs to run, append `--jobs=n`
The individual tests and the test_runner harness have many command-line
-options. Run `test/functional/test_runner.py -h` to see them all.
+options. Run `build/test/functional/test_runner.py -h` to see them all.
#### Speed up test runs with a RAM disk
@@ -130,7 +132,7 @@ For example running the test suite with `--jobs=100` might need a 4 GiB RAM disk
To use, run the test suite specifying the RAM disk as the `cachedir` and `tmpdir`:
```bash
-test/functional/test_runner.py --cachedir=/mnt/tmp/cache --tmpdir=/mnt/tmp
+build/test/functional/test_runner.py --cachedir=/mnt/tmp/cache --tmpdir=/mnt/tmp
```
Once finished with the tests and the disk, and to free the RAM, simply unmount the disk:
@@ -151,7 +153,7 @@ Configure the RAM disk size, expressed as the number of blocks, at the end of th
(`4096 MiB * 2048 blocks/MiB = 8388608 blocks` for 4 GiB). To run the tests using the RAM disk:
```bash
-test/functional/test_runner.py --cachedir=/Volumes/ramdisk/cache --tmpdir=/Volumes/ramdisk/tmp
+build/test/functional/test_runner.py --cachedir=/Volumes/ramdisk/cache --tmpdir=/Volumes/ramdisk/tmp
```
To unmount:
@@ -193,14 +195,14 @@ pkill -9 bitcoind
##### Data directory cache
A pre-mined blockchain with 200 blocks is generated the first time a
-functional test is run and is stored in test/cache. This speeds up
+functional test is run and is stored in build/test/cache. This speeds up
test startup times since new blockchains don't need to be generated for
each test. However, the cache may get into a bad state, in which case
tests will fail. If this happens, remove the cache directory (and make
sure bitcoind processes are stopped as above):
```bash
-rm -rf test/cache
+rm -rf build/test/cache
killall bitcoind
```
@@ -236,7 +238,7 @@ aggregate log by running the `combine_logs.py` script. The output can be plain
text, colorized text or html. For example:
```
-test/functional/combine_logs.py -c <test data directory> | less -r
+build/test/functional/combine_logs.py -c <test data directory> | less -r
```
will pipe the colorized logs from the test into less.
@@ -297,7 +299,7 @@ See this link for considerations: https://www.kernel.org/doc/Documentation/secur
Often while debugging RPC calls in functional tests, the test might time out before the
process can return a response. Use `--timeout-factor 0` to disable all RPC timeouts for that particular
-functional test. Ex: `test/functional/wallet_hd.py --timeout-factor 0`.
+functional test. Ex: `build/test/functional/wallet_hd.py --timeout-factor 0`.
##### Profiling
@@ -321,7 +323,7 @@ For ways to generate more granular profiles, see the README in
### Util tests
-Util tests can be run locally by running `test/util/test_runner.py`.
+Util tests can be run locally by running `build/test/util/test_runner.py`.
Use the `-v` option for verbose output.
### Lint tests
diff --git a/test/functional/.gitignore b/test/functional/.gitignore
index cb41d94423..0d20b6487c 100644
--- a/test/functional/.gitignore
+++ b/test/functional/.gitignore
@@ -1,2 +1 @@
*.pyc
-cache
diff --git a/test/functional/README.md b/test/functional/README.md
index a4994f2e7c..a34bf1827c 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -10,7 +10,8 @@ that file and modify to fit your needs.
#### Coverage
-Running `test/functional/test_runner.py` with the `--coverage` argument tracks which RPCs are
+Assuming the build directory is `build`,
+running `build/test/functional/test_runner.py` with the `--coverage` argument tracks which RPCs are
called by the tests and prints a report of uncovered RPCs in the summary. This
can be used (along with the `--extended` argument) to find out which RPCs we
don't have test cases for.
diff --git a/test/functional/create_cache.py b/test/functional/create_cache.py
index 1108a8e354..0702ea7666 100755
--- a/test/functional/create_cache.py
+++ b/test/functional/create_cache.py
@@ -24,4 +24,4 @@ class CreateCache(BitcoinTestFramework):
pass
if __name__ == '__main__':
- CreateCache().main()
+ CreateCache(__file__).main()
diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py
index 33054fd517..2e4ca83bf0 100644
--- a/test/functional/data/invalid_txs.py
+++ b/test/functional/data/invalid_txs.py
@@ -192,7 +192,7 @@ class SpendTooMuch(BadTxTemplate):
def get_tx(self):
return create_tx_with_script(
- self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1))
+ self.spend_tx, 0, output_script=basic_p2sh, amount=(self.spend_avail + 1))
class CreateNegative(BadTxTemplate):
@@ -242,7 +242,7 @@ class TooManySigops(BadTxTemplate):
lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS))
return create_tx_with_script(
self.spend_tx, 0,
- script_pub_key=lotsa_checksigs,
+ output_script=lotsa_checksigs,
amount=1)
def getDisabledOpcodeTemplate(opcode):
diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json
index 3127350872..1ccc5e0ba0 100644
--- a/test/functional/data/rpc_psbt.json
+++ b/test/functional/data/rpc_psbt.json
@@ -38,7 +38,9 @@
"cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwlCiXVqo3OczGiewPzzo2C+MswLWbFuk6Hou0YFcmssp6P/cGxBdmSWMrLMaOH5ErileONxnOdxCIXHqWb0m81DywEBAAA=",
"cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwk5iXVqo3OczGiewPzzo2C+MswLWbFuk6Hou0YFcmssp6P/cGxBdmSWMrLMaOH5ErileONxnOdxCIXHqWb0m81DywAA",
"cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJjFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgAIyAssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20qzAAAA=",
- "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJhFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4SMgLLE6xoJI3oBqpqNlnPPAPraCHQnIEUpOho/r3oZbttKswAAA"
+ "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJhFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4SMgLLE6xoJI3oBqpqNlnPPAPraCHQnIEUpOho/r3oZbttKswAAA",
+ "cHNidP8BAHUCAAAAAQCBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA",
+ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAgD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA"
],
"invalid_with_msg": [
[
diff --git a/test/functional/example_test.py b/test/functional/example_test.py
index 7f7aa065ad..39cea2962f 100755
--- a/test/functional/example_test.py
+++ b/test/functional/example_test.py
@@ -225,4 +225,4 @@ class ExampleTest(BitcoinTestFramework):
if __name__ == '__main__':
- ExampleTest().main()
+ ExampleTest(__file__).main()
diff --git a/test/functional/feature_abortnode.py b/test/functional/feature_abortnode.py
index 01ba2834c4..a5c8aa163a 100755
--- a/test/functional/feature_abortnode.py
+++ b/test/functional/feature_abortnode.py
@@ -42,4 +42,4 @@ class AbortNodeTest(BitcoinTestFramework):
if __name__ == '__main__':
- AbortNodeTest().main()
+ AbortNodeTest(__file__).main()
diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py
index 2efad70900..c4e60fba12 100755
--- a/test/functional/feature_addrman.py
+++ b/test/functional/feature_addrman.py
@@ -160,4 +160,4 @@ class AddrmanTest(BitcoinTestFramework):
if __name__ == "__main__":
- AddrmanTest().main()
+ AddrmanTest(__file__).main()
diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py
index 5d68f50f58..5ca46c1366 100755
--- a/test/functional/feature_anchors.py
+++ b/test/functional/feature_anchors.py
@@ -141,4 +141,4 @@ class AnchorsTest(BitcoinTestFramework):
if __name__ == "__main__":
- AnchorsTest().main()
+ AnchorsTest(__file__).main()
diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py
index e469deef49..53e94bf4df 100755
--- a/test/functional/feature_asmap.py
+++ b/test/functional/feature_asmap.py
@@ -30,7 +30,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
DEFAULT_ASMAP_FILENAME = 'ip_asn.map' # defined in src/init.cpp
-ASMAP = '../../src/test/data/asmap.raw' # path to unit test skeleton asmap
+ASMAP = 'src/test/data/asmap.raw' # path to unit test skeleton asmap
VERSION = 'fec61fa21a9f46f3b17bdcd660d7f4cd90b966aad3aec593c99b35f0aca15853'
def expected_messages(filename):
@@ -133,7 +133,8 @@ class AsmapTest(BitcoinTestFramework):
self.node = self.nodes[0]
self.datadir = self.node.chain_path
self.default_asmap = os.path.join(self.datadir, DEFAULT_ASMAP_FILENAME)
- self.asmap_raw = os.path.join(os.path.dirname(os.path.realpath(__file__)), ASMAP)
+ base_dir = self.config["environment"]["SRCDIR"]
+ self.asmap_raw = os.path.join(base_dir, ASMAP)
self.test_without_asmap_arg()
self.test_asmap_with_absolute_path()
@@ -146,4 +147,4 @@ class AsmapTest(BitcoinTestFramework):
if __name__ == '__main__':
- AsmapTest().main()
+ AsmapTest(__file__).main()
diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py
index 658eea0a0e..2995ece42f 100755
--- a/test/functional/feature_assumeutxo.py
+++ b/test/functional/feature_assumeutxo.py
@@ -8,33 +8,31 @@ to a hash that has been compiled into bitcoind.
The assumeutxo value generated and used here is committed to in
`CRegTestParams::m_assumeutxo_data` in `src/kernel/chainparams.cpp`.
-
-## Possible test improvements
-
-Interesting test cases could be loading an assumeutxo snapshot file with:
-
-- TODO: Valid snapshot file, but referencing a snapshot block that turns out to be
- invalid, or has an invalid parent
-- TODO: Valid snapshot file and snapshot block, but the block is not on the
- most-work chain
-
-Interesting starting states could be loading a snapshot when the current chain tip is:
-
-- TODO: An ancestor of snapshot block
-- TODO: Not an ancestor of the snapshot block but has less work
-- TODO: The snapshot block
-- TODO: A descendant of the snapshot block
-- TODO: Not an ancestor or a descendant of the snapshot block and has more work
-
"""
+import time
from shutil import rmtree
from dataclasses import dataclass
-from test_framework.messages import tx_from_hex
+from test_framework.blocktools import (
+ create_block,
+ create_coinbase
+)
+from test_framework.messages import (
+ CBlockHeader,
+ from_hex,
+ msg_headers,
+ tx_from_hex
+)
+from test_framework.p2p import (
+ P2PInterface,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
+ assert_approx,
assert_equal,
assert_raises_rpc_error,
+ sha256sum_file,
+ try_rpc,
)
from test_framework.wallet import (
getnewdestination,
@@ -51,18 +49,19 @@ class AssumeutxoTest(BitcoinTestFramework):
def set_test_params(self):
"""Use the pregenerated, deterministic chain up to height 199."""
- self.num_nodes = 3
+ self.num_nodes = 4
self.rpc_timeout = 120
self.extra_args = [
[],
["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"],
["-persistmempool=0","-txindex=1", "-blockfilterindex=1", "-coinstatsindex=1"],
+ []
]
def setup_network(self):
"""Start with the nodes disconnected so that one can generate a snapshot
including blocks the other hasn't yet seen."""
- self.add_nodes(3)
+ self.add_nodes(4)
self.start_nodes(extra_args=self.extra_args)
def test_invalid_snapshot_scenarios(self, valid_snapshot_path):
@@ -70,23 +69,23 @@ class AssumeutxoTest(BitcoinTestFramework):
with open(valid_snapshot_path, 'rb') as f:
valid_snapshot_contents = f.read()
bad_snapshot_path = valid_snapshot_path + '.mod'
+ node = self.nodes[1]
- def expected_error(log_msg="", rpc_details=""):
- with self.nodes[1].assert_debug_log([log_msg]):
- assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot{rpc_details}", self.nodes[1].loadtxoutset, bad_snapshot_path)
+ def expected_error(msg):
+ assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot: Population failed: {msg}", node.loadtxoutset, bad_snapshot_path)
self.log.info(" - snapshot file with invalid file magic")
parsing_error_code = -22
bad_magic = 0xf00f00f000
with open(bad_snapshot_path, 'wb') as f:
f.write(bad_magic.to_bytes(5, "big") + valid_snapshot_contents[5:])
- assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: Invalid UTXO set snapshot magic bytes. Please check if this is indeed a snapshot file or if you are using an outdated snapshot format.", self.nodes[1].loadtxoutset, bad_snapshot_path)
+ assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: Invalid UTXO set snapshot magic bytes. Please check if this is indeed a snapshot file or if you are using an outdated snapshot format.", node.loadtxoutset, bad_snapshot_path)
self.log.info(" - snapshot file with unsupported version")
- for version in [0, 2]:
+ for version in [0, 1, 3]:
with open(bad_snapshot_path, 'wb') as f:
f.write(valid_snapshot_contents[:5] + version.to_bytes(2, "little") + valid_snapshot_contents[7:])
- assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: Version of snapshot {version} does not match any of the supported versions.", self.nodes[1].loadtxoutset, bad_snapshot_path)
+ assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: Version of snapshot {version} does not match any of the supported versions.", node.loadtxoutset, bad_snapshot_path)
self.log.info(" - snapshot file with mismatching network magic")
invalid_magics = [
@@ -101,59 +100,56 @@ class AssumeutxoTest(BitcoinTestFramework):
with open(bad_snapshot_path, 'wb') as f:
f.write(valid_snapshot_contents[:7] + magic.to_bytes(4, 'big') + valid_snapshot_contents[11:])
if real:
- assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: The network of the snapshot ({name}) does not match the network of this node (regtest).", self.nodes[1].loadtxoutset, bad_snapshot_path)
+ assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: The network of the snapshot ({name}) does not match the network of this node (regtest).", node.loadtxoutset, bad_snapshot_path)
else:
- assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: This snapshot has been created for an unrecognized network. This could be a custom signet, a new testnet or possibly caused by data corruption.", self.nodes[1].loadtxoutset, bad_snapshot_path)
+ assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: This snapshot has been created for an unrecognized network. This could be a custom signet, a new testnet or possibly caused by data corruption.", node.loadtxoutset, bad_snapshot_path)
self.log.info(" - snapshot file referring to a block that is not in the assumeutxo parameters")
prev_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1)
bogus_block_hash = "0" * 64 # Represents any unknown block hash
- # The height is not used for anything critical currently, so we just
- # confirm the manipulation in the error message
- bogus_height = 1337
for bad_block_hash in [bogus_block_hash, prev_block_hash]:
with open(bad_snapshot_path, 'wb') as f:
- f.write(valid_snapshot_contents[:11] + bogus_height.to_bytes(4, "little") + bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[47:])
- error_details = f", assumeutxo block hash in snapshot metadata not recognized (hash: {bad_block_hash}, height: {bogus_height}). The following snapshot heights are available: 110, 299."
- expected_error(rpc_details=error_details)
+ f.write(valid_snapshot_contents[:11] + bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[43:])
+
+ msg = f"Unable to load UTXO snapshot: assumeutxo block hash in snapshot metadata not recognized (hash: {bad_block_hash}). The following snapshot heights are available: 110, 200, 299."
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, bad_snapshot_path)
self.log.info(" - snapshot file with wrong number of coins")
- valid_num_coins = int.from_bytes(valid_snapshot_contents[47:47 + 8], "little")
+ valid_num_coins = int.from_bytes(valid_snapshot_contents[43:43 + 8], "little")
for off in [-1, +1]:
with open(bad_snapshot_path, 'wb') as f:
- f.write(valid_snapshot_contents[:47])
+ f.write(valid_snapshot_contents[:43])
f.write((valid_num_coins + off).to_bytes(8, "little"))
- f.write(valid_snapshot_contents[47 + 8:])
- expected_error(log_msg=f"bad snapshot - coins left over after deserializing 298 coins" if off == -1 else f"bad snapshot format or truncated snapshot after deserializing 299 coins")
+ f.write(valid_snapshot_contents[43 + 8:])
+ expected_error(msg="Bad snapshot - coins left over after deserializing 298 coins." if off == -1 else "Bad snapshot format or truncated snapshot after deserializing 299 coins.")
self.log.info(" - snapshot file with alternated but parsable UTXO data results in different hash")
cases = [
# (content, offset, wrong_hash, custom_message)
[b"\xff" * 32, 0, "7d52155c9a9fdc4525b637ef6170568e5dad6fabd0b1fdbb9432010b8453095b", None], # wrong outpoint hash
- [(2).to_bytes(1, "little"), 32, None, "[snapshot] bad snapshot data after deserializing 1 coins"], # wrong txid coins count
- [b"\xfd\xff\xff", 32, None, "[snapshot] mismatch in coins count in snapshot metadata and actual snapshot data"], # txid coins count exceeds coins left
+ [(2).to_bytes(1, "little"), 32, None, "Bad snapshot data after deserializing 1 coins."], # wrong txid coins count
+ [b"\xfd\xff\xff", 32, None, "Mismatch in coins count in snapshot metadata and actual snapshot data"], # txid coins count exceeds coins left
[b"\x01", 33, "9f4d897031ab8547665b4153317ae2fdbf0130c7840b66427ebc48b881cb80ad", None], # wrong outpoint index
[b"\x81", 34, "3da966ba9826fb6d2604260e01607b55ba44e1a5de298606b08704bc62570ea8", None], # wrong coin code VARINT
[b"\x80", 34, "091e893b3ccb4334378709578025356c8bcb0a623f37c7c4e493133c988648e5", None], # another wrong coin code
- [b"\x84\x58", 34, None, "[snapshot] bad snapshot data after deserializing 0 coins"], # wrong coin case with height 364 and coinbase 0
- [b"\xCA\xD2\x8F\x5A", 39, None, "[snapshot] bad snapshot data after deserializing 0 coins - bad tx out value"], # Amount exceeds MAX_MONEY
+ [b"\x84\x58", 34, None, "Bad snapshot data after deserializing 0 coins"], # wrong coin case with height 364 and coinbase 0
+ [b"\xCA\xD2\x8F\x5A", 39, None, "Bad snapshot data after deserializing 0 coins - bad tx out value"], # Amount exceeds MAX_MONEY
]
for content, offset, wrong_hash, custom_message in cases:
with open(bad_snapshot_path, "wb") as f:
- # Prior to offset: Snapshot magic, snapshot version, network magic, height, hash, coins count
- f.write(valid_snapshot_contents[:(5 + 2 + 4 + 4 + 32 + 8 + offset)])
+ # Prior to offset: Snapshot magic, snapshot version, network magic, hash, coins count
+ f.write(valid_snapshot_contents[:(5 + 2 + 4 + 32 + 8 + offset)])
f.write(content)
- f.write(valid_snapshot_contents[(5 + 2 + 4 + 4 + 32 + 8 + offset + len(content)):])
+ f.write(valid_snapshot_contents[(5 + 2 + 4 + 32 + 8 + offset + len(content)):])
- log_msg = custom_message if custom_message is not None else f"[snapshot] bad snapshot content hash: expected a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27, got {wrong_hash}"
- expected_error(log_msg=log_msg)
+ msg = custom_message if custom_message is not None else f"Bad snapshot content hash: expected a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27, got {wrong_hash}."
+ expected_error(msg)
def test_headers_not_synced(self, valid_snapshot_path):
for node in self.nodes[1:]:
- assert_raises_rpc_error(-32603, "The base block header (3bb7ce5eba0be48939b7a521ac1ba9316afee2c7bada3a0cca24188e6d7d96c0) must appear in the headers chain. Make sure all headers are syncing, and call this RPC again.",
- node.loadtxoutset,
- valid_snapshot_path)
+ msg = "Unable to load UTXO snapshot: The base block header (3bb7ce5eba0be48939b7a521ac1ba9316afee2c7bada3a0cca24188e6d7d96c0) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again."
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, valid_snapshot_path)
def test_invalid_chainstate_scenarios(self):
self.log.info("Test different scenarios of invalid snapshot chainstate in datadir")
@@ -185,8 +181,8 @@ class AssumeutxoTest(BitcoinTestFramework):
assert tx['txid'] in node.getrawmempool()
# Attempt to load the snapshot on Node 2 and expect it to fail
- with node.assert_debug_log(expected_msgs=["[snapshot] can't activate a snapshot when mempool not empty"]):
- assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", node.loadtxoutset, dump_output_path)
+ msg = "Unable to load UTXO snapshot: Can't activate a snapshot when mempool not empty"
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)
self.restart_node(2, extra_args=self.extra_args[2])
@@ -199,10 +195,136 @@ class AssumeutxoTest(BitcoinTestFramework):
def test_snapshot_with_less_work(self, dump_output_path):
self.log.info("Test bitcoind should fail when snapshot has less accumulated work than this node.")
node = self.nodes[0]
- assert_equal(node.getblockcount(), FINAL_HEIGHT)
- with node.assert_debug_log(expected_msgs=["[snapshot] activation failed - work does not exceed active chainstate"]):
- assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", node.loadtxoutset, dump_output_path)
- self.restart_node(0, extra_args=self.extra_args[0])
+ msg = "Unable to load UTXO snapshot: Population failed: Work does not exceed active chainstate."
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)
+
+ def test_snapshot_block_invalidated(self, dump_output_path):
+ self.log.info("Test snapshot is not loaded when base block is invalid.")
+ node = self.nodes[0]
+ # We are testing the case where the base block is invalidated itself
+ # and also the case where one of its parents is invalidated.
+ for height in [SNAPSHOT_BASE_HEIGHT, SNAPSHOT_BASE_HEIGHT - 1]:
+ block_hash = node.getblockhash(height)
+ node.invalidateblock(block_hash)
+ assert_equal(node.getblockcount(), height - 1)
+ msg = "Unable to load UTXO snapshot: The base block header (3bb7ce5eba0be48939b7a521ac1ba9316afee2c7bada3a0cca24188e6d7d96c0) is part of an invalid chain."
+ assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)
+ node.reconsiderblock(block_hash)
+
+ def test_snapshot_in_a_divergent_chain(self, dump_output_path):
+ n0 = self.nodes[0]
+ n3 = self.nodes[3]
+ assert_equal(n0.getblockcount(), FINAL_HEIGHT)
+ assert_equal(n3.getblockcount(), START_HEIGHT)
+
+ self.log.info("Check importing a snapshot where current chain-tip is not an ancestor of the snapshot block but has less work")
+ # Generate a divergent chain in n3 up to 298
+ self.generate(n3, nblocks=99, sync_fun=self.no_op)
+ assert_equal(n3.getblockcount(), SNAPSHOT_BASE_HEIGHT - 1)
+
+ # Try importing the snapshot and assert its success
+ loaded = n3.loadtxoutset(dump_output_path)
+ assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
+ normal, snapshot = n3.getchainstates()["chainstates"]
+ assert_equal(normal['blocks'], START_HEIGHT + 99)
+ assert_equal(snapshot['blocks'], SNAPSHOT_BASE_HEIGHT)
+
+ # Now lets sync the nodes and wait for the background validation to finish
+ self.connect_nodes(0, 3)
+ self.sync_blocks(nodes=(n0, n3))
+ self.wait_until(lambda: len(n3.getchainstates()['chainstates']) == 1)
+
+ def test_snapshot_not_on_most_work_chain(self, dump_output_path):
+ self.log.info("Test snapshot is not loaded when the node knows the headers of another chain with more work.")
+ node0 = self.nodes[0]
+ node1 = self.nodes[1]
+ # Create an alternative chain of 2 new blocks, forking off the main chain at the block before the snapshot block.
+ # This simulates a longer chain than the main chain when submitting these two block headers to node 1 because it is only aware of
+ # the main chain headers up to the snapshot height.
+ parent_block_hash = node0.getblockhash(SNAPSHOT_BASE_HEIGHT - 1)
+ block_time = node0.getblock(node0.getbestblockhash())['time'] + 1
+ fork_block1 = create_block(int(parent_block_hash, 16), create_coinbase(SNAPSHOT_BASE_HEIGHT), block_time)
+ fork_block1.solve()
+ fork_block2 = create_block(fork_block1.sha256, create_coinbase(SNAPSHOT_BASE_HEIGHT + 1), block_time + 1)
+ fork_block2.solve()
+ node1.submitheader(fork_block1.serialize().hex())
+ node1.submitheader(fork_block2.serialize().hex())
+ msg = "A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo."
+ assert_raises_rpc_error(-32603, msg, node1.loadtxoutset, dump_output_path)
+ # Cleanup: submit two more headers of the snapshot chain to node 1, so that it is the most-work chain again and loading
+ # the snapshot in future subtests succeeds
+ main_block1 = node0.getblock(node0.getblockhash(SNAPSHOT_BASE_HEIGHT + 1), 0)
+ main_block2 = node0.getblock(node0.getblockhash(SNAPSHOT_BASE_HEIGHT + 2), 0)
+ node1.submitheader(main_block1)
+ node1.submitheader(main_block2)
+
+ def test_sync_from_assumeutxo_node(self, snapshot):
+ """
+ This test verifies that:
+ 1. An IBD node can sync headers from an AssumeUTXO node at any time.
+ 2. IBD nodes do not request historical blocks from AssumeUTXO nodes while they are syncing the background-chain.
+ 3. The assumeUTXO node dynamically adjusts the network services it offers according to its state.
+ 4. IBD nodes can fully sync from AssumeUTXO nodes after they finish the background-chain sync.
+ """
+ self.log.info("Testing IBD-sync from assumeUTXO node")
+ # Node2 starts clean and loads the snapshot.
+ # Node3 starts clean and seeks to sync-up from snapshot_node.
+ miner = self.nodes[0]
+ snapshot_node = self.nodes[2]
+ ibd_node = self.nodes[3]
+
+ # Start test fresh by cleaning up node directories
+ for node in (snapshot_node, ibd_node):
+ self.stop_node(node.index)
+ rmtree(node.chain_path)
+ self.start_node(node.index, extra_args=self.extra_args[node.index])
+
+ # Sync-up headers chain on snapshot_node to load snapshot
+ headers_provider_conn = snapshot_node.add_p2p_connection(P2PInterface())
+ headers_provider_conn.wait_for_getheaders()
+ msg = msg_headers()
+ for block_num in range(1, miner.getblockcount()+1):
+ msg.headers.append(from_hex(CBlockHeader(), miner.getblockheader(miner.getblockhash(block_num), verbose=False)))
+ headers_provider_conn.send_message(msg)
+
+ # Ensure headers arrived
+ default_value = {'status': ''} # No status
+ headers_tip_hash = miner.getbestblockhash()
+ self.wait_until(lambda: next(filter(lambda x: x['hash'] == headers_tip_hash, snapshot_node.getchaintips()), default_value)['status'] == "headers-only")
+ snapshot_node.disconnect_p2ps()
+
+ # Load snapshot
+ snapshot_node.loadtxoutset(snapshot['path'])
+
+ # Connect nodes and verify the ibd_node can sync-up the headers-chain from the snapshot_node
+ self.connect_nodes(ibd_node.index, snapshot_node.index)
+ snapshot_block_hash = snapshot['base_hash']
+ self.wait_until(lambda: next(filter(lambda x: x['hash'] == snapshot_block_hash, ibd_node.getchaintips()), default_value)['status'] == "headers-only")
+
+ # Once the headers-chain is synced, the ibd_node must avoid requesting historical blocks from the snapshot_node.
+ # If it does request such blocks, the snapshot_node will ignore requests it cannot fulfill, causing the ibd_node
+ # to stall. This stall could last for up to 10 min, ultimately resulting in an abrupt disconnection due to the
+ # ibd_node's perceived unresponsiveness.
+ time.sleep(3) # Sleep here because we can't detect when a node avoids requesting blocks from other peer.
+ assert_equal(len(ibd_node.getpeerinfo()[0]['inflight']), 0)
+
+ # Now disconnect nodes and finish background chain sync
+ self.disconnect_nodes(ibd_node.index, snapshot_node.index)
+ self.connect_nodes(snapshot_node.index, miner.index)
+ self.sync_blocks(nodes=(miner, snapshot_node))
+ # Check the base snapshot block was stored and ensure node signals full-node service support
+ self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", snapshot_node.getblock, snapshot_block_hash))
+ self.wait_until(lambda: 'NETWORK' in snapshot_node.getnetworkinfo()['localservicesnames'])
+
+ # Now that the snapshot_node is synced, verify the ibd_node can sync from it
+ self.connect_nodes(snapshot_node.index, ibd_node.index)
+ assert 'NETWORK' in ibd_node.getpeerinfo()[0]['servicesnames']
+ self.sync_blocks(nodes=(ibd_node, snapshot_node))
+
+ def assert_only_network_limited_service(self, node):
+ node_services = node.getnetworkinfo()['localservicesnames']
+ assert 'NETWORK' not in node_services
+ assert 'NETWORK_LIMITED' in node_services
def run_test(self):
"""
@@ -215,6 +337,7 @@ class AssumeutxoTest(BitcoinTestFramework):
n0 = self.nodes[0]
n1 = self.nodes[1]
n2 = self.nodes[2]
+ n3 = self.nodes[3]
self.mini_wallet = MiniWallet(n0)
@@ -251,7 +374,12 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(n1.getblockcount(), START_HEIGHT)
self.log.info(f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
- dump_output = n0.dumptxoutset('utxos.dat')
+ dump_output = n0.dumptxoutset('utxos.dat', "latest")
+
+ self.log.info("Test loading snapshot when the node tip is on the same block as the snapshot")
+ assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT)
+ assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
+ self.test_snapshot_with_less_work(dump_output['path'])
self.log.info("Test loading snapshot when headers are not synced")
self.test_headers_not_synced(dump_output['path'])
@@ -265,17 +393,22 @@ class AssumeutxoTest(BitcoinTestFramework):
# block.
n1.submitheader(block)
n2.submitheader(block)
+ n3.submitheader(block)
# Ensure everyone is seeing the same headers.
for n in self.nodes:
assert_equal(n.getblockchaininfo()["headers"], SNAPSHOT_BASE_HEIGHT)
- assert_equal(
- dump_output['txoutset_hash'],
- "a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27")
- assert_equal(dump_output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)
assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
+ def check_dump_output(output):
+ assert_equal(
+ output['txoutset_hash'],
+ "a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27")
+ assert_equal(output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)
+
+ check_dump_output(dump_output)
+
# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
# will allow us to test n1's sync-to-tip on top of a snapshot.
self.generate(n0, nblocks=100, sync_fun=self.no_op)
@@ -285,37 +418,120 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)
+ self.log.info(f"Check that dumptxoutset works for past block heights")
+ # rollback defaults to the snapshot base height
+ dump_output2 = n0.dumptxoutset('utxos2.dat', "rollback")
+ check_dump_output(dump_output2)
+ assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output2['path']))
+
+ # Rollback with specific height
+ dump_output3 = n0.dumptxoutset('utxos3.dat', rollback=SNAPSHOT_BASE_HEIGHT)
+ check_dump_output(dump_output3)
+ assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output3['path']))
+
+ # Specified height that is not a snapshot height
+ prev_snap_height = SNAPSHOT_BASE_HEIGHT - 1
+ dump_output4 = n0.dumptxoutset(path='utxos4.dat', rollback=prev_snap_height)
+ assert_equal(
+ dump_output4['txoutset_hash'],
+ "8a1db0d6e958ce0d7c963bc6fc91ead596c027129bacec68acc40351037b09d7")
+ assert sha256sum_file(dump_output['path']) != sha256sum_file(dump_output4['path'])
+
+ # Use a hash instead of a height
+ prev_snap_hash = n0.getblockhash(prev_snap_height)
+ dump_output5 = n0.dumptxoutset('utxos5.dat', rollback=prev_snap_hash)
+ assert_equal(sha256sum_file(dump_output4['path']), sha256sum_file(dump_output5['path']))
+
+ # TODO: This is a hack to set m_best_header to the correct value after
+ # dumptxoutset/reconsiderblock. Otherwise the wrong error messages are
+ # returned in following tests. It can be removed once this bug is
+ # fixed. See also https://github.com/bitcoin/bitcoin/issues/26245
+ self.restart_node(0, ["-reindex"])
+
+ # Ensure n0 is back at the tip
+ assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)
+
self.test_snapshot_with_less_work(dump_output['path'])
self.test_invalid_mempool_state(dump_output['path'])
self.test_invalid_snapshot_scenarios(dump_output['path'])
self.test_invalid_chainstate_scenarios()
self.test_invalid_file_path()
+ self.test_snapshot_block_invalidated(dump_output['path'])
+ self.test_snapshot_not_on_most_work_chain(dump_output['path'])
+
+ # Prune-node sanity check
+ assert 'NETWORK' not in n1.getnetworkinfo()['localservicesnames']
self.log.info(f"Loading snapshot into second node from {dump_output['path']}")
+ # This node's tip is on an ancestor block of the snapshot, which should
+ # be the normal case
loaded = n1.loadtxoutset(dump_output['path'])
assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
+ self.log.info("Confirm that local services remain unchanged")
+ # Since n1 is a pruned node, the 'NETWORK' service flag must always be unset.
+ self.assert_only_network_limited_service(n1)
+
+ self.log.info("Check that UTXO-querying RPCs operate on snapshot chainstate")
+ snapshot_hash = loaded['tip_hash']
+ snapshot_num_coins = loaded['coins_loaded']
+ # coinstatsindex might be not caught up yet and is not relevant for this test, so don't use it
+ utxo_info = n1.gettxoutsetinfo(use_index=False)
+ assert_equal(utxo_info['txouts'], snapshot_num_coins)
+ assert_equal(utxo_info['height'], SNAPSHOT_BASE_HEIGHT)
+ assert_equal(utxo_info['bestblock'], snapshot_hash)
+
+ # find coinbase output at snapshot height on node0 and scan for it on node1,
+ # where the block is not available, but the snapshot was loaded successfully
+ coinbase_tx = n0.getblock(snapshot_hash, verbosity=2)['tx'][0]
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, snapshot_hash)
+ coinbase_output_descriptor = coinbase_tx['vout'][0]['scriptPubKey']['desc']
+ scan_result = n1.scantxoutset('start', [coinbase_output_descriptor])
+ assert_equal(scan_result['success'], True)
+ assert_equal(scan_result['txouts'], snapshot_num_coins)
+ assert_equal(scan_result['height'], SNAPSHOT_BASE_HEIGHT)
+ assert_equal(scan_result['bestblock'], snapshot_hash)
+ scan_utxos = [(coin['txid'], coin['vout']) for coin in scan_result['unspents']]
+ assert (coinbase_tx['txid'], 0) in scan_utxos
+
+ txout_result = n1.gettxout(coinbase_tx['txid'], 0)
+ assert_equal(txout_result['scriptPubKey']['desc'], coinbase_output_descriptor)
+
def check_tx_counts(final: bool) -> None:
"""Check nTx and nChainTx intermediate values right after loading
the snapshot, and final values after the snapshot is validated."""
for height, block in blocks.items():
tx = n1.getblockheader(block.hash)["nTx"]
- chain_tx = n1.getchaintxstats(nblocks=1, blockhash=block.hash)["txcount"]
+ stats = n1.getchaintxstats(nblocks=1, blockhash=block.hash)
+ chain_tx = stats.get("txcount", None)
+ window_tx_count = stats.get("window_tx_count", None)
+ tx_rate = stats.get("txrate", None)
+ window_interval = stats.get("window_interval")
# Intermediate nTx of the starting block should be set, but nTx of
# later blocks should be 0 before they are downloaded.
+ # The window_tx_count of one block is equal to the blocks tx count.
+ # If the window tx count is unknown, the value is missing.
+ # The tx_rate is calculated from window_tx_count and window_interval
+ # when possible.
if final or height == START_HEIGHT:
assert_equal(tx, block.tx)
+ assert_equal(window_tx_count, tx)
+ if window_interval > 0:
+ assert_approx(tx_rate, window_tx_count / window_interval, vspan=0.1)
+ else:
+ assert_equal(tx_rate, None)
else:
assert_equal(tx, 0)
+ assert_equal(window_tx_count, None)
# Intermediate nChainTx of the starting block and snapshot block
- # should be set, but others should be 0 until they are downloaded.
+ # should be set, but others should be None until they are downloaded.
if final or height in (START_HEIGHT, SNAPSHOT_BASE_HEIGHT):
assert_equal(chain_tx, block.chain_tx)
else:
- assert_equal(chain_tx, 0)
+ assert_equal(chain_tx, None)
check_tx_counts(final=False)
@@ -341,7 +557,7 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("Submit a spending transaction for a snapshot chainstate coin to the mempool")
# spend the coinbase output of the first block that is not available on node1
spend_coin_blockhash = n1.getblockhash(START_HEIGHT + 1)
- assert_raises_rpc_error(-1, "Block not found on disk", n1.getblock, spend_coin_blockhash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, spend_coin_blockhash)
prev_tx = n0.getblock(spend_coin_blockhash, 3)['tx'][0]
prevout = {"txid": prev_tx['txid'], "vout": 0, "scriptPubKey": prev_tx['vout'][0]['scriptPubKey']['hex']}
privkey = n0.get_deterministic_priv_key().key
@@ -360,6 +576,9 @@ class AssumeutxoTest(BitcoinTestFramework):
self.restart_node(1, extra_args=[
f"-stopatheight={PAUSE_HEIGHT}", *self.extra_args[1]])
+ # Upon restart during snapshot tip sync, the node must remain in 'limited' mode.
+ self.assert_only_network_limited_service(n1)
+
# Finally connect the nodes and let them sync.
#
# Set `wait_for_connect=False` to avoid a race between performing connection
@@ -376,6 +595,9 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("Restarted node before snapshot validation completed, reloading...")
self.restart_node(1, extra_args=self.extra_args[1])
+ # Upon restart, the node must remain in 'limited' mode
+ self.assert_only_network_limited_service(n1)
+
# Send snapshot block to n1 out of order. This makes the test less
# realistic because normally the snapshot block is one of the last
# blocks downloaded, but its useful to test because it triggers more
@@ -394,6 +616,10 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("Ensuring background validation completes")
self.wait_until(lambda: len(n1.getchainstates()['chainstates']) == 1)
+ # Since n1 is a pruned node, it will not signal NODE_NETWORK after
+ # completing the background sync.
+ self.assert_only_network_limited_service(n1)
+
# Ensure indexes have synced.
completed_idx_state = {
'basic block filter index': COMPLETE_IDX,
@@ -424,12 +650,18 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("-- Testing all indexes + reindex")
assert_equal(n2.getblockcount(), START_HEIGHT)
+ assert 'NETWORK' in n2.getnetworkinfo()['localservicesnames'] # sanity check
self.log.info(f"Loading snapshot into third node from {dump_output['path']}")
loaded = n2.loadtxoutset(dump_output['path'])
assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
+ # Even though n2 is a full node, it will unset the 'NETWORK' service flag during snapshot loading.
+ # This indicates other peers that the node will temporarily not provide historical blocks.
+ self.log.info("Check node2 updated the local services during snapshot load")
+ self.assert_only_network_limited_service(n2)
+
for reindex_arg in ['-reindex=1', '-reindex-chainstate=1']:
self.log.info(f"Check that restarting with {reindex_arg} will delete the snapshot chainstate")
self.restart_node(2, extra_args=[reindex_arg, *self.extra_args[2]])
@@ -450,16 +682,24 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(snapshot['validated'], False)
self.log.info("Check that loading the snapshot again will fail because there is already an active snapshot.")
- with n2.assert_debug_log(expected_msgs=["[snapshot] can't activate a snapshot-based chainstate more than once"]):
- assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", n2.loadtxoutset, dump_output['path'])
+ msg = "Unable to load UTXO snapshot: Can't activate a snapshot-based chainstate more than once"
+ assert_raises_rpc_error(-32603, msg, n2.loadtxoutset, dump_output['path'])
+
+ # Upon restart, the node must stay in 'limited' mode until the background
+ # chain sync completes.
+ self.restart_node(2, extra_args=self.extra_args[2])
+ self.assert_only_network_limited_service(n2)
self.connect_nodes(0, 2)
self.wait_until(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT)
- self.sync_blocks()
+ self.sync_blocks(nodes=(n0, n2))
self.log.info("Ensuring background validation completes")
self.wait_until(lambda: len(n2.getchainstates()['chainstates']) == 1)
+ # Once background chain sync completes, the full node must start offering historical blocks again.
+ self.wait_until(lambda: {'NETWORK', 'NETWORK_LIMITED'}.issubset(n2.getnetworkinfo()['localservicesnames']))
+
completed_idx_state = {
'basic block filter index': COMPLETE_IDX,
'coinstatsindex': COMPLETE_IDX,
@@ -492,6 +732,11 @@ class AssumeutxoTest(BitcoinTestFramework):
self.connect_nodes(0, 2)
self.wait_until(lambda: n2.getblockcount() == FINAL_HEIGHT)
+ self.test_snapshot_in_a_divergent_chain(dump_output['path'])
+
+ # The following test cleans node2 and node3 chain directories.
+ self.test_sync_from_assumeutxo_node(snapshot=dump_output)
+
@dataclass
class Block:
hash: str
@@ -499,4 +744,4 @@ class Block:
chain_tx: int
if __name__ == '__main__':
- AssumeutxoTest().main()
+ AssumeutxoTest(__file__).main()
diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py
index 982fa79915..32ad3ad271 100755
--- a/test/functional/feature_assumevalid.py
+++ b/test/functional/feature_assumevalid.py
@@ -139,8 +139,8 @@ class AssumeValidTest(BitcoinTestFramework):
height += 1
# Start node1 and node2 with assumevalid so they accept a block with a bad signature.
- self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)])
- self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)])
+ self.start_node(1, extra_args=["-assumevalid=" + block102.hash])
+ self.start_node(2, extra_args=["-assumevalid=" + block102.hash])
p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
p2p0.send_header_for_blocks(self.blocks[0:2000])
@@ -172,4 +172,4 @@ class AssumeValidTest(BitcoinTestFramework):
if __name__ == '__main__':
- AssumeValidTest().main()
+ AssumeValidTest(__file__).main()
diff --git a/test/functional/feature_bind_extra.py b/test/functional/feature_bind_extra.py
index 5cd031f852..6b53de188f 100755
--- a/test/functional/feature_bind_extra.py
+++ b/test/functional/feature_bind_extra.py
@@ -27,7 +27,7 @@ class BindExtraTest(BitcoinTestFramework):
# Avoid any -bind= on the command line. Force the framework to avoid
# adding -bind=127.0.0.1.
self.bind_to_localhost_only = False
- self.num_nodes = 2
+ self.num_nodes = 3
def skip_test_if_missing_module(self):
# Due to OS-specific network stats queries, we only run on Linux.
@@ -60,14 +60,21 @@ class BindExtraTest(BitcoinTestFramework):
)
port += 2
+ # Node2, no -bind=...=onion, thus no extra port for Tor target.
+ self.expected.append(
+ [
+ [f"-bind=127.0.0.1:{port}"],
+ [(loopback_ipv4, port)]
+ ],
+ )
+ port += 1
+
self.extra_args = list(map(lambda e: e[0], self.expected))
- self.add_nodes(self.num_nodes, self.extra_args)
- # Don't start the nodes, as some of them would collide trying to bind on the same port.
+ self.setup_nodes()
def run_test(self):
- for i in range(len(self.expected)):
- self.log.info(f"Starting node {i} with {self.expected[i][0]}")
- self.start_node(i)
+ for i, (args, expected_services) in enumerate(self.expected):
+ self.log.info(f"Checking listening ports of node {i} with {args}")
pid = self.nodes[i].process.pid
binds = set(get_bind_addrs(pid))
# Remove IPv6 addresses because on some CI environments "::1" is not configured
@@ -78,9 +85,7 @@ class BindExtraTest(BitcoinTestFramework):
binds = set(filter(lambda e: len(e[0]) != ipv6_addr_len_bytes, binds))
# Remove RPC ports. They are not relevant for this test.
binds = set(filter(lambda e: e[1] != rpc_port(i), binds))
- assert_equal(binds, set(self.expected[i][1]))
- self.stop_node(i)
- self.log.info(f"Stopped node {i}")
+ assert_equal(binds, set(expected_services))
if __name__ == '__main__':
- BindExtraTest().main()
+ BindExtraTest(__file__).main()
diff --git a/test/functional/feature_bind_port_discover.py b/test/functional/feature_bind_port_discover.py
index 6e07f2f16c..568c88bcbe 100755
--- a/test/functional/feature_bind_port_discover.py
+++ b/test/functional/feature_bind_port_discover.py
@@ -75,4 +75,4 @@ class BindPortDiscoverTest(BitcoinTestFramework):
assert found_addr1
if __name__ == '__main__':
- BindPortDiscoverTest().main()
+ BindPortDiscoverTest(__file__).main()
diff --git a/test/functional/feature_bind_port_externalip.py b/test/functional/feature_bind_port_externalip.py
index 6a74ce5738..8e2ac02470 100755
--- a/test/functional/feature_bind_port_externalip.py
+++ b/test/functional/feature_bind_port_externalip.py
@@ -72,4 +72,4 @@ class BindPortExternalIPTest(BitcoinTestFramework):
assert found
if __name__ == '__main__':
- BindPortExternalIPTest().main()
+ BindPortExternalIPTest(__file__).main()
diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py
index 14b92d6733..2d61987e94 100755
--- a/test/functional/feature_bip68_sequence.py
+++ b/test/functional/feature_bip68_sequence.py
@@ -412,4 +412,4 @@ class BIP68Test(BitcoinTestFramework):
if __name__ == '__main__':
- BIP68Test().main()
+ BIP68Test(__file__).main()
diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py
index 932f37a083..384ca311c7 100755
--- a/test/functional/feature_block.py
+++ b/test/functional/feature_block.py
@@ -1326,8 +1326,10 @@ class FullBlockTest(BitcoinTestFramework):
block.vtx.extend(tx_list)
# this is a little handier to use than the version in blocktools.py
- def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])):
- return create_tx_with_script(spend_tx, n, amount=value, script_pub_key=script)
+ def create_tx(self, spend_tx, n, value, output_script=None):
+ if output_script is None:
+ output_script = CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])
+ return create_tx_with_script(spend_tx, n, amount=value, output_script=output_script)
# sign a transaction, using the key we know about
# this signs input 0 in tx, which is assumed to be spending output 0 in spend_tx
@@ -1338,13 +1340,17 @@ class FullBlockTest(BitcoinTestFramework):
return
sign_input_legacy(tx, 0, spend_tx.vout[0].scriptPubKey, self.coinbase_key)
- def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])):
- tx = self.create_tx(spend_tx, 0, value, script)
+ def create_and_sign_transaction(self, spend_tx, value, output_script=None):
+ if output_script is None:
+ output_script = CScript([OP_TRUE])
+ tx = self.create_tx(spend_tx, 0, value, output_script=output_script)
self.sign_tx(tx, spend_tx)
tx.rehash()
return tx
- def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), *, version=4):
+ def next_block(self, number, spend=None, additional_coinbase_value=0, *, script=None, version=4):
+ if script is None:
+ script = CScript([OP_TRUE])
if self.tip is None:
base_block_hash = self.genesis_hash
block_time = int(time.time()) + 1
@@ -1361,7 +1367,7 @@ class FullBlockTest(BitcoinTestFramework):
else:
coinbase.vout[0].nValue += spend.vout[0].nValue - 1 # all but one satoshi to fees
coinbase.rehash()
- tx = self.create_tx(spend, 0, 1, script) # spend 1 satoshi
+ tx = self.create_tx(spend, 0, 1, output_script=script) # spend 1 satoshi
self.sign_tx(tx, spend)
tx.rehash()
block = create_block(base_block_hash, coinbase, block_time, version=version, txlist=[tx])
@@ -1434,4 +1440,4 @@ class FullBlockTest(BitcoinTestFramework):
if __name__ == '__main__':
- FullBlockTest().main()
+ FullBlockTest(__file__).main()
diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py
index 1a60c13c2c..4a43632855 100755
--- a/test/functional/feature_blocksdir.py
+++ b/test/functional/feature_blocksdir.py
@@ -35,4 +35,4 @@ class BlocksdirTest(BitcoinTestFramework):
if __name__ == '__main__':
- BlocksdirTest().main()
+ BlocksdirTest(__file__).main()
diff --git a/test/functional/feature_blocksxor.py b/test/functional/feature_blocksxor.py
new file mode 100755
index 0000000000..9824bf9715
--- /dev/null
+++ b/test/functional/feature_blocksxor.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+# Copyright (c) 2024 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test support for XORed block data and undo files (`-blocksxor` option)."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.test_node import (
+ ErrorMatch,
+ NULL_BLK_XOR_KEY,
+)
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ util_xor,
+)
+from test_framework.wallet import MiniWallet
+
+
+class BlocksXORTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.extra_args = [[
+ '-blocksxor=1',
+ '-fastprune=1', # use smaller block files
+ '-datacarriersize=100000', # needed to pad transaction with MiniWallet
+ ]]
+
+ def run_test(self):
+ self.log.info("Mine some blocks, to create multiple blk*.dat/rev*.dat files")
+ node = self.nodes[0]
+ wallet = MiniWallet(node)
+ for _ in range(5):
+ wallet.send_self_transfer(from_node=node, target_vsize=20000)
+ self.generate(wallet, 1)
+
+ block_files = list(node.blocks_path.glob('blk[0-9][0-9][0-9][0-9][0-9].dat'))
+ undo_files = list(node.blocks_path.glob('rev[0-9][0-9][0-9][0-9][0-9].dat'))
+ assert_equal(len(block_files), len(undo_files))
+ assert_greater_than(len(block_files), 1) # we want at least one full block file
+
+ self.log.info("Shut down node and un-XOR block/undo files manually")
+ self.stop_node(0)
+ xor_key = node.read_xor_key()
+ for data_file in sorted(block_files + undo_files):
+ self.log.debug(f"Rewriting file {data_file}...")
+ with open(data_file, 'rb+') as f:
+ xored_data = f.read()
+ f.seek(0)
+ f.write(util_xor(xored_data, xor_key, offset=0))
+
+ self.log.info("Check that restarting with 'blocksxor=0' fails if XOR key is present")
+ node.assert_start_raises_init_error(['-blocksxor=0'],
+ 'The blocksdir XOR-key can not be disabled when a random key was already stored!',
+ match=ErrorMatch.PARTIAL_REGEX)
+
+ self.log.info("Delete XOR key, restart node with '-blocksxor=0', check blk*.dat/rev*.dat file integrity")
+ node.blocks_key_path.unlink()
+ self.start_node(0, extra_args=['-blocksxor=0'])
+ # checklevel=2 -> verify block validity + undo data
+ # nblocks=0 -> verify all blocks
+ node.verifychain(checklevel=2, nblocks=0)
+ self.log.info("Check that blocks XOR key is recreated")
+ assert_equal(node.read_xor_key(), NULL_BLK_XOR_KEY)
+
+
+if __name__ == '__main__':
+ BlocksXORTest(__file__).main()
diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py
index fb3f662271..dc01f8fa8f 100755
--- a/test/functional/feature_cltv.py
+++ b/test/functional/feature_cltv.py
@@ -195,4 +195,4 @@ class BIP65Test(BitcoinTestFramework):
if __name__ == '__main__':
- BIP65Test().main()
+ BIP65Test(__file__).main()
diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py
index d6c1567e64..b4392e6002 100755
--- a/test/functional/feature_coinstatsindex.py
+++ b/test/functional/feature_coinstatsindex.py
@@ -242,6 +242,9 @@ class CoinStatsIndexTest(BitcoinTestFramework):
res12 = index_node.gettxoutsetinfo('muhash')
assert_equal(res12, res10)
+ self.log.info("Test obtaining info for a non-existent block hash")
+ assert_raises_rpc_error(-5, "Block not found", index_node.gettxoutsetinfo, hash_type="none", hash_or_height="ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", use_index=True)
+
def _test_use_index_option(self):
self.log.info("Test use_index option for nodes running the index")
@@ -321,4 +324,4 @@ class CoinStatsIndexTest(BitcoinTestFramework):
if __name__ == '__main__':
- CoinStatsIndexTest().main()
+ CoinStatsIndexTest(__file__).main()
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index 9e13a3deef..44c7edf962 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -153,6 +153,13 @@ class ConfArgsTest(BitcoinTestFramework):
expected_msg='Error: Error parsing command line arguments: Can not set -proxy with no value. Please specify value with -proxy=value.',
extra_args=['-proxy'],
)
+ # Provide a value different from 1 to the -wallet negated option
+ if self.is_wallet_compiled():
+ for value in [0, 'not_a_boolean']:
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg="Error: Invalid value detected for '-wallet' or '-nowallet'. '-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets",
+ extra_args=[f'-nowallet={value}'],
+ )
def test_log_buffer(self):
self.stop_node(0)
@@ -371,11 +378,44 @@ class ConfArgsTest(BitcoinTestFramework):
def test_acceptstalefeeestimates_arg_support(self):
self.log.info("Test -acceptstalefeeestimates option support")
conf_file = self.nodes[0].datadir_path / "bitcoin.conf"
- for chain, chain_name in {("main", ""), ("test", "testnet3"), ("signet", "signet")}:
+ for chain, chain_name in {("main", ""), ("test", "testnet3"), ("signet", "signet"), ("testnet4", "testnet4")}:
util.write_config(conf_file, n=0, chain=chain_name, extra_config='acceptstalefeeestimates=1\n')
self.nodes[0].assert_start_raises_init_error(expected_msg=f'Error: acceptstalefeeestimates is not supported on {chain} chain.')
util.write_config(conf_file, n=0, chain="regtest") # Reset to regtest
+ def test_testnet3_deprecation_msg(self):
+ self.log.info("Test testnet3 deprecation warning")
+ t3_warning_log = "Warning: Support for testnet3 is deprecated and will be removed in an upcoming release. Consider switching to testnet4."
+
+ def warning_msg(node, approx_size):
+ return f'Warning: Disk space for "{node.datadir_path / node.chain / "blocks" }" may not accommodate the block files. Approximately {approx_size} GB of data will be stored in this directory.'
+
+ # Testnet3 node will log the warning
+ self.nodes[0].chain = 'testnet3'
+ self.nodes[0].replace_in_config([('regtest=', 'testnet='), ('[regtest]', '[test]')])
+ with self.nodes[0].assert_debug_log([t3_warning_log]):
+ self.start_node(0)
+ # Some CI environments will have limited space and some others won't
+ # so we need to handle both cases as a valid result.
+ self.nodes[0].stderr.seek(0)
+ err = self.nodes[0].stdout.read()
+ self.nodes[0].stderr.seek(0)
+ self.nodes[0].stderr.truncate()
+ if err != b'' and err != warning_msg(self.nodes[0], 42):
+ raise AssertionError("Unexpected stderr after shutdown of Testnet3 node")
+ self.stop_node(0)
+
+ # Testnet4 node will not log the warning
+ self.nodes[0].chain = 'testnet4'
+ self.nodes[0].replace_in_config([('testnet=', 'testnet4='), ('[test]', '[testnet4]')])
+ with self.nodes[0].assert_debug_log([], unexpected_msgs=[t3_warning_log]):
+ self.start_node(0)
+ self.stop_node(0)
+
+ # Reset to regtest
+ self.nodes[0].chain = 'regtest'
+ self.nodes[0].replace_in_config([('testnet4=', 'regtest='), ('[testnet4]', '[regtest]')])
+
def run_test(self):
self.test_log_buffer()
self.test_args_log()
@@ -389,6 +429,7 @@ class ConfArgsTest(BitcoinTestFramework):
self.test_ignored_conf()
self.test_ignored_default_conf()
self.test_acceptstalefeeestimates_arg_support()
+ self.test_testnet3_deprecation_msg()
# 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")]
@@ -431,4 +472,4 @@ class ConfArgsTest(BitcoinTestFramework):
if __name__ == '__main__':
- ConfArgsTest().main()
+ ConfArgsTest(__file__).main()
diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py
index 2db9682931..df02bcc6ad 100755
--- a/test/functional/feature_csv_activation.py
+++ b/test/functional/feature_csv_activation.py
@@ -482,4 +482,4 @@ class BIP68_112_113Test(BitcoinTestFramework):
if __name__ == '__main__':
- BIP68_112_113Test().main()
+ BIP68_112_113Test(__file__).main()
diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py
index afd0246209..f1aa3961e3 100755
--- a/test/functional/feature_dbcrash.py
+++ b/test/functional/feature_dbcrash.py
@@ -286,4 +286,4 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
if __name__ == "__main__":
- ChainstateWriteCrashTest().main()
+ ChainstateWriteCrashTest(__file__).main()
diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py
index 035e7151ca..48b0b745c6 100755
--- a/test/functional/feature_dersig.py
+++ b/test/functional/feature_dersig.py
@@ -148,4 +148,4 @@ class BIP66Test(BitcoinTestFramework):
if __name__ == '__main__':
- BIP66Test().main()
+ BIP66Test(__file__).main()
diff --git a/test/functional/feature_dirsymlinks.py b/test/functional/feature_dirsymlinks.py
index 96f4aed08a..9fed99cbe5 100755
--- a/test/functional/feature_dirsymlinks.py
+++ b/test/functional/feature_dirsymlinks.py
@@ -38,4 +38,4 @@ class SymlinkTest(BitcoinTestFramework):
if __name__ == "__main__":
- SymlinkTest().main()
+ SymlinkTest(__file__).main()
diff --git a/test/functional/feature_discover.py b/test/functional/feature_discover.py
index 7f4b81114e..9eaaea3652 100755
--- a/test/functional/feature_discover.py
+++ b/test/functional/feature_discover.py
@@ -72,4 +72,4 @@ class DiscoverTest(BitcoinTestFramework):
if __name__ == '__main__':
- DiscoverTest().main()
+ DiscoverTest(__file__).main()
diff --git a/test/functional/feature_fastprune.py b/test/functional/feature_fastprune.py
index c913c4f93a..ca6877b901 100755
--- a/test/functional/feature_fastprune.py
+++ b/test/functional/feature_fastprune.py
@@ -26,4 +26,4 @@ class FeatureFastpruneTest(BitcoinTestFramework):
if __name__ == '__main__':
- FeatureFastpruneTest().main()
+ FeatureFastpruneTest(__file__).main()
diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py
index ffc87f8b8b..974d8268a2 100755
--- a/test/functional/feature_fee_estimation.py
+++ b/test/functional/feature_fee_estimation.py
@@ -4,7 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test fee estimation code."""
from copy import deepcopy
-from decimal import Decimal
+from decimal import Decimal, ROUND_DOWN
import os
import random
import time
@@ -40,7 +40,7 @@ def small_txpuzzle_randfee(
# Exponentially distributed from 1-128 * fee_increment
rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28))
# Total fee ranges from min_fee to min_fee + 127*fee_increment
- fee = min_fee - fee_increment + satoshi_round(rand_fee)
+ fee = min_fee - fee_increment + satoshi_round(rand_fee, rounding=ROUND_DOWN)
utxos_to_spend = []
total_in = Decimal("0.00000000")
while total_in <= (amount + fee) and len(conflist) > 0:
@@ -128,6 +128,14 @@ def make_tx(wallet, utxo, feerate):
fee_rate=Decimal(feerate * 1000) / COIN,
)
+def check_fee_estimates_btw_modes(node, expected_conservative, expected_economical):
+ fee_est_conservative = node.estimatesmartfee(1, estimate_mode="conservative")['feerate']
+ fee_est_economical = node.estimatesmartfee(1, estimate_mode="economical")['feerate']
+ fee_est_default = node.estimatesmartfee(1)['feerate']
+ assert_equal(fee_est_conservative, expected_conservative)
+ assert_equal(fee_est_economical, expected_economical)
+ assert_equal(fee_est_default, expected_economical)
+
class EstimateFeeTest(BitcoinTestFramework):
def set_test_params(self):
@@ -382,6 +390,41 @@ class EstimateFeeTest(BitcoinTestFramework):
self.start_node(0,extra_args=["-acceptstalefeeestimates"])
assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)
+ def clear_estimates(self):
+ self.log.info("Restarting node with fresh estimation")
+ self.stop_node(0)
+ fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
+ os.remove(fee_dat)
+ self.start_node(0)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(0, 2)
+ self.sync_blocks()
+ assert_equal(self.nodes[0].estimatesmartfee(1)["errors"], ["Insufficient data or no feerate found"])
+
+ def broadcast_and_mine(self, broadcaster, miner, feerate, count):
+ """Broadcast and mine some number of transactions with a specified fee rate."""
+ for _ in range(count):
+ self.wallet.send_self_transfer(from_node=broadcaster, fee_rate=feerate)
+ self.sync_mempools()
+ self.generate(miner, 1)
+
+ def test_estimation_modes(self):
+ low_feerate = Decimal("0.001")
+ high_feerate = Decimal("0.005")
+ tx_count = 24
+ # Broadcast and mine high fee transactions for the first 12 blocks.
+ for _ in range(12):
+ self.broadcast_and_mine(self.nodes[1], self.nodes[2], high_feerate, tx_count)
+ check_fee_estimates_btw_modes(self.nodes[0], high_feerate, high_feerate)
+
+ # We now track 12 blocks; short horizon stats will start decaying.
+ # Broadcast and mine low fee transactions for the next 4 blocks.
+ for _ in range(4):
+ self.broadcast_and_mine(self.nodes[1], self.nodes[2], low_feerate, tx_count)
+ # conservative mode will consider longer time horizons while economical mode does not
+ # Check the fee estimates for both modes after mining low fee transactions.
+ check_fee_estimates_btw_modes(self.nodes[0], high_feerate, low_feerate)
+
def run_test(self):
self.log.info("This test is time consuming, please be patient")
@@ -420,17 +463,15 @@ class EstimateFeeTest(BitcoinTestFramework):
self.log.info("Test reading old fee_estimates.dat")
self.test_old_fee_estimate_file()
- self.log.info("Restarting node with fresh estimation")
- self.stop_node(0)
- fee_dat = os.path.join(self.nodes[0].chain_path, "fee_estimates.dat")
- os.remove(fee_dat)
- self.start_node(0)
- self.connect_nodes(0, 1)
- self.connect_nodes(0, 2)
+ self.clear_estimates()
self.log.info("Testing estimates with RBF.")
self.sanity_check_rbf_estimates(self.confutxo + self.memutxo)
+ self.clear_estimates()
+ self.log.info("Test estimatesmartfee modes")
+ self.test_estimation_modes()
+
self.log.info("Testing that fee estimation is disabled in blocksonly.")
self.restart_node(0, ["-blocksonly"])
assert_raises_rpc_error(
@@ -439,4 +480,4 @@ class EstimateFeeTest(BitcoinTestFramework):
if __name__ == "__main__":
- EstimateFeeTest().main()
+ EstimateFeeTest(__file__).main()
diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py
index 567207915e..e71871114d 100755
--- a/test/functional/feature_filelock.py
+++ b/test/functional/feature_filelock.py
@@ -54,4 +54,4 @@ class FilelockTest(BitcoinTestFramework):
check_wallet_filelock(True)
if __name__ == '__main__':
- FilelockTest().main()
+ FilelockTest(__file__).main()
diff --git a/test/functional/feature_framework_miniwallet.py b/test/functional/feature_framework_miniwallet.py
index f108289018..f723f7f31e 100755
--- a/test/functional/feature_framework_miniwallet.py
+++ b/test/functional/feature_framework_miniwallet.py
@@ -3,10 +3,13 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test MiniWallet."""
+import random
+import string
+
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
- assert_greater_than_or_equal,
+ assert_equal,
)
from test_framework.wallet import (
MiniWallet,
@@ -19,17 +22,29 @@ class FeatureFrameworkMiniWalletTest(BitcoinTestFramework):
self.num_nodes = 1
def test_tx_padding(self):
- """Verify that MiniWallet's transaction padding (`target_weight` parameter)
- works accurately enough (i.e. at most 3 WUs higher) with all modes."""
+ """Verify that MiniWallet's transaction padding (`target_vsize` parameter)
+ works accurately with all modes."""
for mode_name, wallet in self.wallets:
self.log.info(f"Test tx padding with MiniWallet mode {mode_name}...")
utxo = wallet.get_utxo(mark_as_spent=False)
- for target_weight in [1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 4000000,
- 989, 2001, 4337, 13371, 23219, 49153, 102035, 223419, 3999989]:
- tx = wallet.create_self_transfer(utxo_to_spend=utxo, target_weight=target_weight)["tx"]
- self.log.debug(f"-> target weight: {target_weight}, actual weight: {tx.get_weight()}")
- assert_greater_than_or_equal(tx.get_weight(), target_weight)
- assert_greater_than_or_equal(target_weight + 3, tx.get_weight())
+ for target_vsize in [250, 500, 1250, 2500, 5000, 12500, 25000, 50000, 1000000,
+ 248, 501, 1085, 3343, 5805, 12289, 25509, 55855, 999998]:
+ tx = wallet.create_self_transfer(utxo_to_spend=utxo, target_vsize=target_vsize)["tx"]
+ assert_equal(tx.get_vsize(), target_vsize)
+
+ def test_wallet_tagging(self):
+ """Verify that tagged wallet instances are able to send funds."""
+ self.log.info(f"Test tagged wallet instances...")
+ node = self.nodes[0]
+ untagged_wallet = self.wallets[0][1]
+ for i in range(10):
+ tag = ''.join(random.choice(string.ascii_letters) for _ in range(20))
+ self.log.debug(f"-> ({i}) tag name: {tag}")
+ tagged_wallet = MiniWallet(node, tag_name=tag)
+ untagged_wallet.send_to(from_node=node, scriptPubKey=tagged_wallet.get_scriptPubKey(), amount=100000)
+ tagged_wallet.rescan_utxos()
+ tagged_wallet.send_self_transfer(from_node=node)
+ self.generate(node, 1) # clear mempool
def run_test(self):
node = self.nodes[0]
@@ -43,7 +58,8 @@ class FeatureFrameworkMiniWalletTest(BitcoinTestFramework):
self.generate(wallet, COINBASE_MATURITY)
self.test_tx_padding()
+ self.test_wallet_tagging()
if __name__ == '__main__':
- FeatureFrameworkMiniWalletTest().main()
+ FeatureFrameworkMiniWalletTest(__file__).main()
diff --git a/test/functional/feature_help.py b/test/functional/feature_help.py
index 4b66030b47..6d8a742d89 100755
--- a/test/functional/feature_help.py
+++ b/test/functional/feature_help.py
@@ -59,4 +59,4 @@ class HelpTest(BitcoinTestFramework):
if __name__ == '__main__':
- HelpTest().main()
+ HelpTest(__file__).main()
diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py
index 58ab063e71..ee484e7ec5 100755
--- a/test/functional/feature_includeconf.py
+++ b/test/functional/feature_includeconf.py
@@ -83,4 +83,4 @@ class IncludeConfTest(BitcoinTestFramework):
assert subversion.endswith("main; relative; relative2)/")
if __name__ == '__main__':
- IncludeConfTest().main()
+ IncludeConfTest(__file__).main()
diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py
index 66c0a4f615..030bf51ed8 100755
--- a/test/functional/feature_index_prune.py
+++ b/test/functional/feature_index_prune.py
@@ -155,4 +155,4 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
if __name__ == '__main__':
- FeatureIndexPruneTest().main()
+ FeatureIndexPruneTest(__file__).main()
diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py
index 22ae0c307b..659d33684e 100755
--- a/test/functional/feature_init.py
+++ b/test/functional/feature_init.py
@@ -149,4 +149,4 @@ class InitStressTest(BitcoinTestFramework):
if __name__ == '__main__':
- InitStressTest().main()
+ InitStressTest(__file__).main()
diff --git a/test/functional/feature_loadblock.py b/test/functional/feature_loadblock.py
index 5129e0d328..1519c132b9 100755
--- a/test/functional/feature_loadblock.py
+++ b/test/functional/feature_loadblock.py
@@ -80,4 +80,4 @@ class LoadblockTest(BitcoinTestFramework):
if __name__ == '__main__':
- LoadblockTest().main()
+ LoadblockTest(__file__).main()
diff --git a/test/functional/feature_logging.py b/test/functional/feature_logging.py
index 0e9aca358d..ab817fd12d 100755
--- a/test/functional/feature_logging.py
+++ b/test/functional/feature_logging.py
@@ -101,4 +101,4 @@ class LoggingTest(BitcoinTestFramework):
if __name__ == '__main__':
- LoggingTest().main()
+ LoggingTest(__file__).main()
diff --git a/test/functional/feature_maxtipage.py b/test/functional/feature_maxtipage.py
index a1774a5395..4ce9bb949c 100755
--- a/test/functional/feature_maxtipage.py
+++ b/test/functional/feature_maxtipage.py
@@ -62,4 +62,4 @@ class MaxTipAgeTest(BitcoinTestFramework):
if __name__ == '__main__':
- MaxTipAgeTest().main()
+ MaxTipAgeTest(__file__).main()
diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py
index 39cff7b738..136cdd024d 100755
--- a/test/functional/feature_maxuploadtarget.py
+++ b/test/functional/feature_maxuploadtarget.py
@@ -206,4 +206,4 @@ class MaxUploadTest(BitcoinTestFramework):
self.nodes[0].assert_start_raises_init_error(extra_args=["-maxuploadtarget=abc"], expected_msg="Error: Unable to parse -maxuploadtarget: 'abc'")
if __name__ == '__main__':
- MaxUploadTest().main()
+ MaxUploadTest(__file__).main()
diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py
index 078d2ef63c..34228f6f38 100755
--- a/test/functional/feature_minchainwork.py
+++ b/test/functional/feature_minchainwork.py
@@ -110,9 +110,9 @@ class MinimumChainWorkTest(BitcoinTestFramework):
self.stop_node(0)
self.nodes[0].assert_start_raises_init_error(
["-minimumchainwork=test"],
- expected_msg='Error: Invalid non-hex (test) minimum chain work value specified',
+ expected_msg='Error: Invalid minimum work specified (test), must be up to 64 hex digits',
)
if __name__ == '__main__':
- MinimumChainWorkTest().main()
+ MinimumChainWorkTest(__file__).main()
diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py
index d2b5315d31..79e8df4b5e 100755
--- a/test/functional/feature_notifications.py
+++ b/test/functional/feature_notifications.py
@@ -194,4 +194,4 @@ class NotificationsTest(BitcoinTestFramework):
if __name__ == '__main__':
- NotificationsTest().main()
+ NotificationsTest(__file__).main()
diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py
index f896cb6f43..a53f78c13d 100755
--- a/test/functional/feature_nulldummy.py
+++ b/test/functional/feature_nulldummy.py
@@ -154,4 +154,4 @@ class NULLDUMMYTest(BitcoinTestFramework):
if __name__ == '__main__':
- NULLDUMMYTest().main()
+ NULLDUMMYTest(__file__).main()
diff --git a/test/functional/feature_posix_fs_permissions.py b/test/functional/feature_posix_fs_permissions.py
index 40528779e6..28b1803891 100755
--- a/test/functional/feature_posix_fs_permissions.py
+++ b/test/functional/feature_posix_fs_permissions.py
@@ -40,4 +40,4 @@ class PosixFsPermissionsTest(BitcoinTestFramework):
if __name__ == '__main__':
- PosixFsPermissionsTest().main()
+ PosixFsPermissionsTest(__file__).main()
diff --git a/test/functional/feature_presegwit_node_upgrade.py b/test/functional/feature_presegwit_node_upgrade.py
index 3d762c8197..8c1bd90083 100755
--- a/test/functional/feature_presegwit_node_upgrade.py
+++ b/test/functional/feature_presegwit_node_upgrade.py
@@ -54,4 +54,4 @@ class SegwitUpgradeTest(BitcoinTestFramework):
if __name__ == '__main__':
- SegwitUpgradeTest().main()
+ SegwitUpgradeTest(__file__).main()
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index 7a6f639021..644ee5cc7f 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -148,7 +148,8 @@ class ProxyTest(BitcoinTestFramework):
rv = []
addr = "15.61.23.23:1234"
self.log.debug(f"Test: outgoing IPv4 connection through node {node.index} for address {addr}")
- node.addnode(addr, "onetry")
+ # v2transport=False is used to avoid reconnections with v1 being scheduled. These could interfere with later checks.
+ node.addnode(addr, "onetry", v2transport=False)
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
@@ -164,7 +165,7 @@ class ProxyTest(BitcoinTestFramework):
if self.have_ipv6:
addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443"
self.log.debug(f"Test: outgoing IPv6 connection through node {node.index} for address {addr}")
- node.addnode(addr, "onetry")
+ node.addnode(addr, "onetry", v2transport=False)
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
@@ -180,7 +181,7 @@ class ProxyTest(BitcoinTestFramework):
if test_onion:
addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333"
self.log.debug(f"Test: outgoing onion connection through node {node.index} for address {addr}")
- node.addnode(addr, "onetry")
+ node.addnode(addr, "onetry", v2transport=False)
cmd = proxies[2].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@@ -195,7 +196,7 @@ class ProxyTest(BitcoinTestFramework):
if test_cjdns:
addr = "[fc00:1:2:3:4:5:6:7]:8888"
self.log.debug(f"Test: outgoing CJDNS connection through node {node.index} for address {addr}")
- node.addnode(addr, "onetry")
+ node.addnode(addr, "onetry", v2transport=False)
cmd = proxies[1].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@@ -209,7 +210,7 @@ class ProxyTest(BitcoinTestFramework):
addr = "node.noumenon:8333"
self.log.debug(f"Test: outgoing DNS name connection through node {node.index} for address {addr}")
- node.addnode(addr, "onetry")
+ node.addnode(addr, "onetry", v2transport=False)
cmd = proxies[3].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
@@ -457,4 +458,4 @@ class ProxyTest(BitcoinTestFramework):
os.unlink(socket_path)
if __name__ == '__main__':
- ProxyTest().main()
+ ProxyTest(__file__).main()
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index 4b548ef0f3..8d924282cf 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -25,6 +25,7 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
+ try_rpc,
)
# Rescans start at the earliest block up to 2 hours before a key timestamp, so
@@ -479,8 +480,12 @@ class PruneTest(BitcoinTestFramework):
self.log.info("Test invalid pruning command line options")
self.test_invalid_command_line_options()
+ self.log.info("Test scanblocks can not return pruned data")
self.test_scanblocks_pruned()
+ self.log.info("Test pruneheight reflects the presence of block and undo data")
+ self.test_pruneheight_undo_presence()
+
self.log.info("Done")
def test_scanblocks_pruned(self):
@@ -494,5 +499,18 @@ class PruneTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "Block not available (pruned data)", node.scanblocks,
"start", [{"desc": f"raw({false_positive_spk.hex()})"}], 0, 0, "basic", {"filter_false_positives": True})
+ def test_pruneheight_undo_presence(self):
+ node = self.nodes[2]
+ pruneheight = node.getblockchaininfo()["pruneheight"]
+ fetch_block = node.getblockhash(pruneheight - 1)
+
+ self.connect_nodes(1, 2)
+ peers = node.getpeerinfo()
+ node.getblockfrompeer(fetch_block, peers[0]["id"])
+ self.wait_until(lambda: not try_rpc(-1, "Block not available (pruned data)", node.getblock, fetch_block), timeout=5)
+
+ new_pruneheight = node.getblockchaininfo()["pruneheight"]
+ assert_equal(pruneheight, new_pruneheight)
+
if __name__ == '__main__':
- PruneTest().main()
+ PruneTest(__file__).main()
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index 739b9b9bb9..b660b96935 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -26,15 +26,18 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
+ # both nodes disable full-rbf to test BIP125 signaling
self.extra_args = [
[
+ "-mempoolfullrbf=0",
"-limitancestorcount=50",
"-limitancestorsize=101",
"-limitdescendantcount=200",
"-limitdescendantsize=101",
],
- # second node has default mempool parameters
+ # second node has default mempool parameters, besides mempoolfullrbf being disabled
[
+ "-mempoolfullrbf=0",
],
]
self.supports_cli = False
@@ -727,4 +730,4 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert conflicting_tx['txid'] in self.nodes[0].getrawmempool()
if __name__ == '__main__':
- ReplaceByFeeTest().main()
+ ReplaceByFeeTest(__file__).main()
diff --git a/test/functional/feature_reindex.py b/test/functional/feature_reindex.py
index 835cd0c5cf..1ebfe82da5 100755
--- a/test/functional/feature_reindex.py
+++ b/test/functional/feature_reindex.py
@@ -12,7 +12,10 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import MAGIC_BYTES
-from test_framework.util import assert_equal
+from test_framework.util import (
+ assert_equal,
+ util_xor,
+)
class ReindexTest(BitcoinTestFramework):
@@ -39,9 +42,11 @@ class ReindexTest(BitcoinTestFramework):
# we're generating them rather than getting them from peers), so to
# test out-of-order handling, swap blocks 1 and 2 on disk.
blk0 = self.nodes[0].blocks_path / "blk00000.dat"
+ xor_dat = self.nodes[0].read_xor_key()
+
with open(blk0, 'r+b') as bf:
# Read at least the first few blocks (including genesis)
- b = bf.read(2000)
+ b = util_xor(bf.read(2000), xor_dat, offset=0)
# Find the offsets of blocks 2, 3, and 4 (the first 3 blocks beyond genesis)
# by searching for the regtest marker bytes (see pchMessageStart).
@@ -55,12 +60,12 @@ class ReindexTest(BitcoinTestFramework):
b4_start = find_block(b, b3_start)
# Blocks 2 and 3 should be the same size.
- assert_equal(b3_start-b2_start, b4_start-b3_start)
+ assert_equal(b3_start - b2_start, b4_start - b3_start)
# Swap the second and third blocks (don't disturb the genesis block).
bf.seek(b2_start)
- bf.write(b[b3_start:b4_start])
- bf.write(b[b2_start:b3_start])
+ bf.write(util_xor(b[b3_start:b4_start], xor_dat, offset=b2_start))
+ bf.write(util_xor(b[b2_start:b3_start], xor_dat, offset=b3_start))
# The reindexing code should detect and accommodate out of order blocks.
with self.nodes[0].assert_debug_log([
@@ -103,4 +108,4 @@ class ReindexTest(BitcoinTestFramework):
if __name__ == '__main__':
- ReindexTest().main()
+ ReindexTest(__file__).main()
diff --git a/test/functional/feature_reindex_readonly.py b/test/functional/feature_reindex_readonly.py
index 52c0bb26a6..858a67566f 100755
--- a/test/functional/feature_reindex_readonly.py
+++ b/test/functional/feature_reindex_readonly.py
@@ -87,4 +87,4 @@ class BlockstoreReindexTest(BitcoinTestFramework):
if __name__ == '__main__':
- BlockstoreReindexTest().main()
+ BlockstoreReindexTest(__file__).main()
diff --git a/test/functional/feature_remove_pruned_files_on_startup.py b/test/functional/feature_remove_pruned_files_on_startup.py
index 4ee653142a..2e689f2920 100755
--- a/test/functional/feature_remove_pruned_files_on_startup.py
+++ b/test/functional/feature_remove_pruned_files_on_startup.py
@@ -52,4 +52,4 @@ class FeatureRemovePrunedFilesOnStartupTest(BitcoinTestFramework):
assert not os.path.exists(rev1)
if __name__ == '__main__':
- FeatureRemovePrunedFilesOnStartupTest().main()
+ FeatureRemovePrunedFilesOnStartupTest(__file__).main()
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 4dc19222c4..f98f326e8f 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -657,4 +657,4 @@ class SegWitTest(BitcoinTestFramework):
if __name__ == '__main__':
- SegWitTest().main()
+ SegWitTest(__file__).main()
diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py
index 1cd0aeabd3..a7294944bf 100755
--- a/test/functional/feature_settings.py
+++ b/test/functional/feature_settings.py
@@ -13,11 +13,32 @@ from test_framework.util import assert_equal
class SettingsTest(BitcoinTestFramework):
+ def add_options(self, parser):
+ self.add_wallet_options(parser)
+
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.wallet_names = []
+ def test_wallet_settings(self, settings_path):
+ if not self.is_wallet_compiled():
+ return
+
+ self.log.info("Testing wallet settings..")
+ node = self.nodes[0]
+ # Create wallet to use it during tests
+ self.start_node(0)
+ node.createwallet(wallet_name='w1')
+ self.stop_node(0)
+
+ # Verify wallet settings can only be strings. Either names or paths. Not booleans, nums nor anything else.
+ for wallets_data in [[10], [True], [[]], [{}], ["w1", 10], ["w1", False]]:
+ with settings_path.open("w") as fp:
+ json.dump({"wallet": wallets_data}, fp)
+ node.assert_start_raises_init_error(expected_msg="Error: Invalid value detected for '-wallet' or '-nowallet'. '-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets",
+ extra_args=[f'-settings={settings_path}'])
+
def run_test(self):
node, = self.nodes
settings = node.chain_path / "settings.json"
@@ -86,6 +107,8 @@ class SettingsTest(BitcoinTestFramework):
self.start_node(0, extra_args=[f"-settings={altsettings}"])
self.stop_node(0)
+ self.test_wallet_settings(settings)
+
if __name__ == '__main__':
- SettingsTest().main()
+ SettingsTest(__file__).main()
diff --git a/test/functional/feature_shutdown.py b/test/functional/feature_shutdown.py
index 291df4c518..a7277be39d 100755
--- a/test/functional/feature_shutdown.py
+++ b/test/functional/feature_shutdown.py
@@ -32,4 +32,4 @@ class ShutdownTest(BitcoinTestFramework):
self.stop_node(0, wait=1000)
if __name__ == '__main__':
- ShutdownTest().main()
+ ShutdownTest(__file__).main()
diff --git a/test/functional/feature_signet.py b/test/functional/feature_signet.py
index a90a2a8e5e..b648266cae 100755
--- a/test/functional/feature_signet.py
+++ b/test/functional/feature_signet.py
@@ -82,4 +82,4 @@ class SignetBasicTest(BitcoinTestFramework):
if __name__ == '__main__':
- SignetBasicTest().main()
+ SignetBasicTest(__file__).main()
diff --git a/test/functional/feature_startupnotify.py b/test/functional/feature_startupnotify.py
index a8e62c6244..1e07103725 100755
--- a/test/functional/feature_startupnotify.py
+++ b/test/functional/feature_startupnotify.py
@@ -39,4 +39,4 @@ class StartupNotifyTest(BitcoinTestFramework):
if __name__ == '__main__':
- StartupNotifyTest().main()
+ StartupNotifyTest(__file__).main()
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index 1a0844d240..443c1c33da 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -231,7 +231,7 @@ def default_sigmsg(ctx):
codeseppos = get(ctx, "codeseppos")
leaf_ver = get(ctx, "leafversion")
script = get(ctx, "script_taproot")
- return TaprootSignatureMsg(tx, utxos, hashtype, idx, scriptpath=True, script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex)
+ return TaprootSignatureMsg(tx, utxos, hashtype, idx, scriptpath=True, leaf_script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex)
else:
return TaprootSignatureMsg(tx, utxos, hashtype, idx, scriptpath=False, annex=annex)
elif mode == "witv0":
@@ -1766,4 +1766,4 @@ class TaprootTest(BitcoinTestFramework):
if __name__ == '__main__':
- TaprootTest().main()
+ TaprootTest(__file__).main()
diff --git a/test/functional/feature_uacomment.py b/test/functional/feature_uacomment.py
index 4720f6dea3..fc372f543d 100755
--- a/test/functional/feature_uacomment.py
+++ b/test/functional/feature_uacomment.py
@@ -37,4 +37,4 @@ class UacommentTest(BitcoinTestFramework):
if __name__ == '__main__':
- UacommentTest().main()
+ UacommentTest(__file__).main()
diff --git a/test/functional/feature_unsupported_utxo_db.py b/test/functional/feature_unsupported_utxo_db.py
index 6acf551216..694a8e0623 100755
--- a/test/functional/feature_unsupported_utxo_db.py
+++ b/test/functional/feature_unsupported_utxo_db.py
@@ -58,4 +58,4 @@ class UnsupportedUtxoDbTest(BitcoinTestFramework):
if __name__ == "__main__":
- UnsupportedUtxoDbTest().main()
+ UnsupportedUtxoDbTest(__file__).main()
diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py
index 0bdcc6d83d..3ab779b87d 100755
--- a/test/functional/feature_utxo_set_hash.py
+++ b/test/functional/feature_utxo_set_hash.py
@@ -75,4 +75,4 @@ class UTXOSetHashTest(BitcoinTestFramework):
if __name__ == '__main__':
- UTXOSetHashTest().main()
+ UTXOSetHashTest(__file__).main()
diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py
index 2c330eb681..dc25ce6c83 100755
--- a/test/functional/feature_versionbits_warning.py
+++ b/test/functional/feature_versionbits_warning.py
@@ -100,4 +100,4 @@ class VersionBitsWarningTest(BitcoinTestFramework):
self.wait_until(lambda: self.versionbits_in_alert_file())
if __name__ == '__main__':
- VersionBitsWarningTest().main()
+ VersionBitsWarningTest(__file__).main()
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index a6628dcbf3..7194c8ece4 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -30,7 +30,12 @@ JSON_PARSING_ERROR = 'error: Error parsing JSON: foo'
BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero'
TOO_MANY_ARGS = 'error: too many arguments (maximum 2 for nblocks and maxtries)'
WALLET_NOT_LOADED = 'Requested wallet does not exist or is not loaded'
-WALLET_NOT_SPECIFIED = 'Wallet file not specified'
+WALLET_NOT_SPECIFIED = (
+ "Multiple wallets are loaded. Please select which wallet to use by requesting the RPC "
+ "through the /wallet/<walletname> URI path. Or for the CLI, specify the \"-rpcwallet=<walletname>\" "
+ "option before the command (run \"bitcoin-cli -h\" for help or \"bitcoin-cli listwallets\" to see "
+ "which wallets are currently loaded)."
+)
def cli_get_info_string_to_dict(cli_get_info_string):
@@ -331,6 +336,10 @@ class TestBitcoinCli(BitcoinTestFramework):
n4 = 10
blocks = self.nodes[0].getblockcount()
+ self.log.info('Test -generate -rpcwallet=<filename> raise RPC error')
+ wallet2_path = f'-rpcwallet={self.nodes[0].wallets_path / wallets[2] / self.wallet_data_filename}'
+ assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(wallet2_path, '-generate').echo)
+
self.log.info('Test -generate -rpcwallet with no args')
generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
@@ -381,6 +390,9 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcwait', '-rpcwaittimeout=5').echo)
assert_greater_than_or_equal(time.time(), start_time + 5)
+ self.log.info("Test that only one of -addrinfo, -generate, -getinfo, -netinfo may be specified at a time")
+ assert_raises_process_error(1, "Only one of -getinfo, -netinfo may be specified", self.nodes[0].cli('-getinfo', '-netinfo').send_cli)
+
if __name__ == '__main__':
- TestBitcoinCli().main()
+ TestBitcoinCli(__file__).main()
diff --git a/test/functional/interface_http.py b/test/functional/interface_http.py
index 6e32009e05..dbdceb52d1 100755
--- a/test/functional/interface_http.py
+++ b/test/functional/interface_http.py
@@ -106,4 +106,4 @@ class HTTPBasicsTest (BitcoinTestFramework):
if __name__ == '__main__':
- HTTPBasicsTest ().main ()
+ HTTPBasicsTest(__file__).main()
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index ae8d6b226d..ba6e960476 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -201,10 +201,15 @@ class RESTTest (BitcoinTestFramework):
json_obj = self.test_rest_request(f"/getutxos/checkmempool/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 1)
- # Do some invalid requests
+ self.log.info("Check some invalid requests")
self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.JSON, body='{"checkmempool', status=400, ret_type=RetType.OBJ)
self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.BIN, body='{"checkmempool', status=400, ret_type=RetType.OBJ)
self.test_rest_request("/getutxos/checkmempool", http_method='POST', req_type=ReqType.JSON, status=400, ret_type=RetType.OBJ)
+ self.test_rest_request(f"/getutxos/{spending[0]}_+1", ret_type=RetType.OBJ, status=400)
+ self.test_rest_request(f"/getutxos/{spending[0]}-+1", ret_type=RetType.OBJ, status=400)
+ self.test_rest_request(f"/getutxos/{spending[0]}--1", ret_type=RetType.OBJ, status=400)
+ self.test_rest_request(f"/getutxos/{spending[0]}aa-1234", ret_type=RetType.OBJ, status=400)
+ self.test_rest_request(f"/getutxos/aa-1234", ret_type=RetType.OBJ, status=400)
# Test limits
long_uri = '/'.join([f"{txid}-{n_}" for n_ in range(20)])
@@ -436,4 +441,4 @@ class RESTTest (BitcoinTestFramework):
assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid hash: {INVALID_PARAM}")
if __name__ == '__main__':
- RESTTest().main()
+ RESTTest(__file__).main()
diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py
index 6c1855c400..9074f0a2d9 100755
--- a/test/functional/interface_rpc.py
+++ b/test/functional/interface_rpc.py
@@ -246,4 +246,4 @@ class RPCInterfaceTest(BitcoinTestFramework):
if __name__ == '__main__':
- RPCInterfaceTest().main()
+ RPCInterfaceTest(__file__).main()
diff --git a/test/functional/interface_usdt_coinselection.py b/test/functional/interface_usdt_coinselection.py
index 30931a41cd..f684848aed 100755
--- a/test/functional/interface_usdt_coinselection.py
+++ b/test/functional/interface_usdt_coinselection.py
@@ -181,7 +181,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
# 5. aps_create_tx_internal (type 4)
wallet.sendtoaddress(wallet.getnewaddress(), 10)
events = self.get_tracepoints([1, 2, 3, 1, 4])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, True)
assert_greater_than(change_pos, -1)
@@ -190,7 +190,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
# 1. normal_create_tx_internal (type 2)
assert_raises_rpc_error(-6, "Insufficient funds", wallet.sendtoaddress, wallet.getnewaddress(), 102 * 50)
events = self.get_tracepoints([2])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, False)
self.log.info("Explicitly enabling APS results in 2 tracepoints")
@@ -200,7 +200,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
wallet.setwalletflag("avoid_reuse")
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=10, avoid_reuse=True)
events = self.get_tracepoints([1, 2])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, True)
assert_equal(use_aps, None)
@@ -213,7 +213,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
# 5. aps_create_tx_internal (type 4)
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=wallet.getbalance(), subtractfeefromamount=True, avoid_reuse=False)
events = self.get_tracepoints([1, 2, 3, 1, 4])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, True)
assert_equal(change_pos, -1)
@@ -223,7 +223,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
# 2. normal_create_tx_internal (type 2)
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=wallet.getbalance(), subtractfeefromamount=True)
events = self.get_tracepoints([1, 2])
- success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events)
+ success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
assert_equal(success, True)
assert_equal(change_pos, -1)
@@ -231,4 +231,4 @@ class CoinSelectionTracepointTest(BitcoinTestFramework):
if __name__ == '__main__':
- CoinSelectionTracepointTest().main()
+ CoinSelectionTracepointTest(__file__).main()
diff --git a/test/functional/interface_usdt_mempool.py b/test/functional/interface_usdt_mempool.py
index 0168d9f916..a088278665 100755
--- a/test/functional/interface_usdt_mempool.py
+++ b/test/functional/interface_usdt_mempool.py
@@ -322,4 +322,4 @@ class MempoolTracepointTest(BitcoinTestFramework):
if __name__ == "__main__":
- MempoolTracepointTest().main()
+ MempoolTracepointTest(__file__).main()
diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py
index 5d7c8c2304..7b55259b63 100755
--- a/test/functional/interface_usdt_net.py
+++ b/test/functional/interface_usdt_net.py
@@ -168,4 +168,4 @@ class NetTracepointTest(BitcoinTestFramework):
if __name__ == '__main__':
- NetTracepointTest().main()
+ NetTracepointTest(__file__).main()
diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py
index 06cdcd10a0..ad98a3a162 100755
--- a/test/functional/interface_usdt_utxocache.py
+++ b/test/functional/interface_usdt_utxocache.py
@@ -407,4 +407,4 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
if __name__ == '__main__':
- UTXOCacheTracepointTest().main()
+ UTXOCacheTracepointTest(__file__).main()
diff --git a/test/functional/interface_usdt_validation.py b/test/functional/interface_usdt_validation.py
index 30982393d8..9a37b96ada 100755
--- a/test/functional/interface_usdt_validation.py
+++ b/test/functional/interface_usdt_validation.py
@@ -131,4 +131,4 @@ class ValidationTracepointTest(BitcoinTestFramework):
if __name__ == '__main__':
- ValidationTracepointTest().main()
+ ValidationTracepointTest(__file__).main()
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 9f6f8919de..b960f40ccc 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -597,4 +597,4 @@ class ZMQTest (BitcoinTestFramework):
if __name__ == '__main__':
- ZMQTest().main()
+ ZMQTest(__file__).main()
diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py
index e1cee46839..4d08575255 100755
--- a/test/functional/mempool_accept.py
+++ b/test/functional/mempool_accept.py
@@ -37,6 +37,7 @@ from test_framework.script_util import (
keys_to_multisig_script,
MIN_PADDING,
MIN_STANDARD_TX_NONWITNESS_SIZE,
+ PAY_TO_ANCHOR,
script_to_p2sh_script,
script_to_p2wsh_script,
)
@@ -54,6 +55,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [[
'-txindex','-permitbaremultisig=0',
+ '-mempoolfullrbf=0',
]] * self.num_nodes
self.supports_cli = False
@@ -389,6 +391,65 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
maxfeerate=0,
)
+ self.log.info('OP_1 <0x4e73> is able to be created and spent')
+ anchor_value = 10000
+ create_anchor_tx = self.wallet.send_to(from_node=node, scriptPubKey=PAY_TO_ANCHOR, amount=anchor_value)
+ self.generate(node, 1)
+
+ # First spend has non-empty witness, will be rejected to prevent third party wtxid malleability
+ anchor_nonempty_wit_spend = CTransaction()
+ anchor_nonempty_wit_spend.vin.append(CTxIn(COutPoint(int(create_anchor_tx["txid"], 16), create_anchor_tx["sent_vout"]), b""))
+ anchor_nonempty_wit_spend.vout.append(CTxOut(anchor_value - int(fee*COIN), script_to_p2wsh_script(CScript([OP_TRUE]))))
+ anchor_nonempty_wit_spend.wit.vtxinwit.append(CTxInWitness())
+ anchor_nonempty_wit_spend.wit.vtxinwit[0].scriptWitness.stack.append(b"f")
+ anchor_nonempty_wit_spend.rehash()
+
+ self.check_mempool_result(
+ result_expected=[{'txid': anchor_nonempty_wit_spend.rehash(), 'allowed': False, 'reject-reason': 'bad-witness-nonstandard'}],
+ rawtxs=[anchor_nonempty_wit_spend.serialize().hex()],
+ maxfeerate=0,
+ )
+
+ # but is consensus-legal
+ self.generateblock(node, self.wallet.get_address(), [anchor_nonempty_wit_spend.serialize().hex()])
+
+ # Without witness elements it is standard
+ create_anchor_tx = self.wallet.send_to(from_node=node, scriptPubKey=PAY_TO_ANCHOR, amount=anchor_value)
+ self.generate(node, 1)
+
+ anchor_spend = CTransaction()
+ anchor_spend.vin.append(CTxIn(COutPoint(int(create_anchor_tx["txid"], 16), create_anchor_tx["sent_vout"]), b""))
+ anchor_spend.vout.append(CTxOut(anchor_value - int(fee*COIN), script_to_p2wsh_script(CScript([OP_TRUE]))))
+ anchor_spend.wit.vtxinwit.append(CTxInWitness())
+ # It's "segwit" but txid == wtxid since there is no witness data
+ assert_equal(anchor_spend.rehash(), anchor_spend.getwtxid())
+
+ self.check_mempool_result(
+ result_expected=[{'txid': anchor_spend.rehash(), 'allowed': True, 'vsize': anchor_spend.get_vsize(), 'fees': { 'base': Decimal('0.00000700')}}],
+ rawtxs=[anchor_spend.serialize().hex()],
+ maxfeerate=0,
+ )
+
+ self.log.info('But cannot be spent if nested sh()')
+ nested_anchor_tx = self.wallet.create_self_transfer(sequence=SEQUENCE_FINAL)['tx']
+ nested_anchor_tx.vout[0].scriptPubKey = script_to_p2sh_script(PAY_TO_ANCHOR)
+ nested_anchor_tx.rehash()
+ self.generateblock(node, self.wallet.get_address(), [nested_anchor_tx.serialize().hex()])
+
+ nested_anchor_spend = CTransaction()
+ nested_anchor_spend.vin.append(CTxIn(COutPoint(nested_anchor_tx.sha256, 0), b""))
+ nested_anchor_spend.vin[0].scriptSig = CScript([bytes(PAY_TO_ANCHOR)])
+ nested_anchor_spend.vout.append(CTxOut(nested_anchor_tx.vout[0].nValue - int(fee*COIN), script_to_p2wsh_script(CScript([OP_TRUE]))))
+ nested_anchor_spend.rehash()
+
+ self.check_mempool_result(
+ result_expected=[{'txid': nested_anchor_spend.rehash(), 'allowed': False, 'reject-reason': 'non-mandatory-script-verify-flag (Witness version reserved for soft-fork upgrades)'}],
+ rawtxs=[nested_anchor_spend.serialize().hex()],
+ maxfeerate=0,
+ )
+ # but is consensus-legal
+ self.generateblock(node, self.wallet.get_address(), [nested_anchor_spend.serialize().hex()])
+
self.log.info('Spending a confirmed bare multisig is okay')
address = self.wallet.get_address()
tx = tx_from_hex(raw_tx_reference)
@@ -409,4 +470,4 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
)
if __name__ == '__main__':
- MempoolAcceptanceTest().main()
+ MempoolAcceptanceTest(__file__).main()
diff --git a/test/functional/mempool_accept_wtxid.py b/test/functional/mempool_accept_wtxid.py
index 4767d6db22..e00e7b1ae4 100755
--- a/test/functional/mempool_accept_wtxid.py
+++ b/test/functional/mempool_accept_wtxid.py
@@ -125,4 +125,4 @@ class MempoolWtxidTest(BitcoinTestFramework):
assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0)
if __name__ == '__main__':
- MempoolWtxidTest().main()
+ MempoolWtxidTest(__file__).main()
diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py
index a126f164aa..7c72aaae77 100755
--- a/test/functional/mempool_compatibility.py
+++ b/test/functional/mempool_compatibility.py
@@ -78,4 +78,4 @@ class MempoolCompatibilityTest(BitcoinTestFramework):
if __name__ == "__main__":
- MempoolCompatibilityTest().main()
+ MempoolCompatibilityTest(__file__).main()
diff --git a/test/functional/mempool_datacarrier.py b/test/functional/mempool_datacarrier.py
index 2e27aa988e..ed6ad8461a 100755
--- a/test/functional/mempool_datacarrier.py
+++ b/test/functional/mempool_datacarrier.py
@@ -88,4 +88,4 @@ class DataCarrierTest(BitcoinTestFramework):
if __name__ == '__main__':
- DataCarrierTest().main()
+ DataCarrierTest(__file__).main()
diff --git a/test/functional/mempool_dust.py b/test/functional/mempool_dust.py
index e0c026207a..1ea03a8a9e 100755
--- a/test/functional/mempool_dust.py
+++ b/test/functional/mempool_dust.py
@@ -110,4 +110,4 @@ class DustRelayFeeTest(BitcoinTestFramework):
if __name__ == '__main__':
- DustRelayFeeTest().main()
+ DustRelayFeeTest(__file__).main()
diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py
index 5eebe43488..2bd88f9825 100755
--- a/test/functional/mempool_expiry.py
+++ b/test/functional/mempool_expiry.py
@@ -114,4 +114,4 @@ class MempoolExpiryTest(BitcoinTestFramework):
if __name__ == '__main__':
- MempoolExpiryTest().main()
+ MempoolExpiryTest(__file__).main()
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
index 49a0a32c45..a29c103c3f 100755
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -55,12 +55,12 @@ class MempoolLimitTest(BitcoinTestFramework):
self.generate(node, 1)
# tx_A needs to be RBF'd, set minfee at set size
- A_weight = 1000
+ A_vsize = 250
mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"]
tx_A = self.wallet.send_self_transfer(
from_node=node,
fee_rate=mempoolmin_feerate,
- target_weight=A_weight,
+ target_vsize=A_vsize,
utxo_to_spend=rbf_utxo,
confirmed_only=True
)
@@ -68,15 +68,15 @@ class MempoolLimitTest(BitcoinTestFramework):
# RBF's tx_A, is not yet submitted
tx_B = self.wallet.create_self_transfer(
fee=tx_A["fee"] * 4,
- target_weight=A_weight,
+ target_vsize=A_vsize,
utxo_to_spend=rbf_utxo,
confirmed_only=True
)
# Spends tx_B's output, too big for cpfp carveout (because that would also increase the descendant limit by 1)
- non_cpfp_carveout_weight = 40001 # EXTRA_DESCENDANT_TX_SIZE_LIMIT + 1
+ non_cpfp_carveout_vsize = 10001 # EXTRA_DESCENDANT_TX_SIZE_LIMIT + 1
tx_C = self.wallet.create_self_transfer(
- target_weight=non_cpfp_carveout_weight,
+ target_vsize=non_cpfp_carveout_vsize,
fee_rate=mempoolmin_feerate,
utxo_to_spend=tx_B["new_utxo"],
confirmed_only=True
@@ -103,14 +103,14 @@ class MempoolLimitTest(BitcoinTestFramework):
# UTXOs to be spent by the ultimate child transaction
parent_utxos = []
- evicted_weight = 8000
+ evicted_vsize = 2000
# Mempool transaction which is evicted due to being at the "bottom" of the mempool when the
# mempool overflows and evicts by descendant score. It's important that the eviction doesn't
# happen in the middle of package evaluation, as it can invalidate the coins cache.
mempool_evicted_tx = self.wallet.send_self_transfer(
from_node=node,
fee_rate=mempoolmin_feerate,
- target_weight=evicted_weight,
+ target_vsize=evicted_vsize,
confirmed_only=True
)
# Already in mempool when package is submitted.
@@ -132,14 +132,16 @@ class MempoolLimitTest(BitcoinTestFramework):
# Series of parents that don't need CPFP and are submitted individually. Each one is large and
# high feerate, which means they should trigger eviction but not be evicted.
- parent_weight = 100000
+ parent_vsize = 25000
num_big_parents = 3
- assert_greater_than(parent_weight * num_big_parents, current_info["maxmempool"] - current_info["bytes"])
+ # Need to be large enough to trigger eviction
+ # (note that the mempool usage of a tx is about three times its vsize)
+ assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["bytes"])
parent_feerate = 100 * mempoolmin_feerate
big_parent_txids = []
for i in range(num_big_parents):
- parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_weight=parent_weight, confirmed_only=True)
+ parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True)
parent_utxos.append(parent["new_utxo"])
package_hex.append(parent["hex"])
big_parent_txids.append(parent["txid"])
@@ -311,8 +313,9 @@ class MempoolLimitTest(BitcoinTestFramework):
entry = node.getmempoolentry(txid)
worst_feerate_btcvb = min(worst_feerate_btcvb, entry["fees"]["descendant"] / entry["descendantsize"])
# Needs to be large enough to trigger eviction
- target_weight_each = 200000
- assert_greater_than(target_weight_each * 2, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"])
+ # (note that the mempool usage of a tx is about three times its vsize)
+ target_vsize_each = 50000
+ assert_greater_than(target_vsize_each * 2 * 3, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"])
# Should be a true CPFP: parent's feerate is just below mempool min feerate
parent_feerate = mempoolmin_feerate - Decimal("0.000001") # 0.1 sats/vbyte below min feerate
# Parent + child is above mempool minimum feerate
@@ -320,8 +323,8 @@ class MempoolLimitTest(BitcoinTestFramework):
# However, when eviction is triggered, these transactions should be at the bottom.
# This assertion assumes parent and child are the same size.
miniwallet.rescan_utxos()
- tx_parent_just_below = miniwallet.create_self_transfer(fee_rate=parent_feerate, target_weight=target_weight_each)
- tx_child_just_above = miniwallet.create_self_transfer(utxo_to_spend=tx_parent_just_below["new_utxo"], fee_rate=child_feerate, target_weight=target_weight_each)
+ tx_parent_just_below = miniwallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=target_vsize_each)
+ tx_child_just_above = miniwallet.create_self_transfer(utxo_to_spend=tx_parent_just_below["new_utxo"], fee_rate=child_feerate, target_vsize=target_vsize_each)
# This package ranks below the lowest descendant package in the mempool
package_fee = tx_parent_just_below["fee"] + tx_child_just_above["fee"]
package_vsize = tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()
@@ -342,4 +345,4 @@ class MempoolLimitTest(BitcoinTestFramework):
if __name__ == '__main__':
- MempoolLimitTest().main()
+ MempoolLimitTest(__file__).main()
diff --git a/test/functional/mempool_package_limits.py b/test/functional/mempool_package_limits.py
index 2a64597511..3290ff43c4 100755
--- a/test/functional/mempool_package_limits.py
+++ b/test/functional/mempool_package_limits.py
@@ -4,9 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test logic for limiting mempool and package ancestors/descendants."""
from test_framework.blocktools import COINBASE_MATURITY
-from test_framework.messages import (
- WITNESS_SCALE_FACTOR,
-)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -290,19 +287,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
parent_utxos = []
target_vsize = 30_000
high_fee = 10 * target_vsize # 10 sats/vB
- target_weight = target_vsize * WITNESS_SCALE_FACTOR
self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages")
# Mempool transactions A and B
for _ in range(2):
- bulked_tx = self.wallet.create_self_transfer(target_weight=target_weight)
+ bulked_tx = self.wallet.create_self_transfer(target_vsize=target_vsize)
self.wallet.sendrawtransaction(from_node=node, tx_hex=bulked_tx["hex"])
parent_utxos.append(bulked_tx["new_utxo"])
# Package transaction C
- pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=high_fee, target_weight=target_weight)
+ pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=high_fee, target_vsize=target_vsize)
# Package transaction D
- pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0], target_weight=target_weight)
+ pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0], target_vsize=target_vsize)
assert_equal(2, node.getmempoolinfo()["size"])
return [pc_tx["hex"], pd_tx["hex"]]
@@ -321,20 +317,19 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
node = self.nodes[0]
target_vsize = 21_000
high_fee = 10 * target_vsize # 10 sats/vB
- target_weight = target_vsize * WITNESS_SCALE_FACTOR
self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages")
# Top parent in mempool, Ma
- ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=high_fee // 2, target_weight=target_weight)
+ ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=high_fee // 2, target_vsize=target_vsize)
self.wallet.sendrawtransaction(from_node=node, tx_hex=ma_tx["hex"])
package_hex = []
for j in range(2): # Two legs (left and right)
# Mempool transaction (Mb and Mc)
- mempool_tx = self.wallet.create_self_transfer(utxo_to_spend=ma_tx["new_utxos"][j], target_weight=target_weight)
+ mempool_tx = self.wallet.create_self_transfer(utxo_to_spend=ma_tx["new_utxos"][j], target_vsize=target_vsize)
self.wallet.sendrawtransaction(from_node=node, tx_hex=mempool_tx["hex"])
# Package transaction (Pd and Pe)
- package_tx = self.wallet.create_self_transfer(utxo_to_spend=mempool_tx["new_utxo"], target_weight=target_weight)
+ package_tx = self.wallet.create_self_transfer(utxo_to_spend=mempool_tx["new_utxo"], target_vsize=target_vsize)
package_hex.append(package_tx["hex"])
assert_equal(3, node.getmempoolinfo()["size"])
@@ -343,4 +338,4 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
if __name__ == "__main__":
- MempoolPackageLimitsTest().main()
+ MempoolPackageLimitsTest(__file__).main()
diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py
index 632425814a..fd54a8c3e1 100755
--- a/test/functional/mempool_package_onemore.py
+++ b/test/functional/mempool_package_onemore.py
@@ -77,4 +77,4 @@ class MempoolPackagesTest(BitcoinTestFramework):
if __name__ == '__main__':
- MempoolPackagesTest().main()
+ MempoolPackagesTest(__file__).main()
diff --git a/test/functional/mempool_package_rbf.py b/test/functional/mempool_package_rbf.py
index ceb9530394..a5b8fa5f87 100755
--- a/test/functional/mempool_package_rbf.py
+++ b/test/functional/mempool_package_rbf.py
@@ -168,11 +168,20 @@ class PackageRBFTest(BitcoinTestFramework):
self.assert_mempool_contents(expected=package_txns1)
self.log.info("Check replacement pays for incremental bandwidth")
- package_hex3, package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
- pkg_results3 = node.submitpackage(package_hex3)
- assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {package_txns3[1].rehash()}, not enough additional fees to relay; 0.00 < 0.00000{sum([tx.get_vsize() for tx in package_txns3])}", pkg_results3["package_msg"])
-
+ _, placeholder_txns3 = self.create_simple_package(coin)
+ package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
+ incremental_sats_required = Decimal(package_3_size) / COIN
+ incremental_sats_short = incremental_sats_required - Decimal("0.00000001")
+ # Recreate the package with slightly higher fee once we know the size of the new package, but still short of required fee
+ failure_package_hex3, failure_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_short)
+ assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3]))
+ pkg_results3 = node.submitpackage(failure_package_hex3)
+ assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].rehash()}, not enough additional fees to relay; {incremental_sats_short} < {incremental_sats_required}", pkg_results3["package_msg"])
self.assert_mempool_contents(expected=package_txns1)
+
+ success_package_hex3, success_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_required)
+ node.submitpackage(success_package_hex3)
+ self.assert_mempool_contents(expected=success_package_txns3)
self.generate(node, 1)
self.log.info("Check Package RBF must have strict cpfp structure")
@@ -180,11 +189,14 @@ class PackageRBFTest(BitcoinTestFramework):
package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE)
node.submitpackage(package_hex4)
self.assert_mempool_contents(expected=package_txns4)
- package_hex5, package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"))
+ package_hex5, _package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE)
pkg_results5 = node.submitpackage(package_hex5)
- assert 'package RBF failed: package feerate is less than parent feerate' in pkg_results5["package_msg"]
-
+ assert 'package RBF failed: package feerate is less than or equal to parent feerate' in pkg_results5["package_msg"]
self.assert_mempool_contents(expected=package_txns4)
+
+ package_hex5_1, package_txns5_1 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE + Decimal("0.00000001"))
+ node.submitpackage(package_hex5_1)
+ self.assert_mempool_contents(expected=package_txns5_1)
self.generate(node, 1)
def test_package_rbf_max_conflicts(self):
@@ -324,16 +336,16 @@ class PackageRBFTest(BitcoinTestFramework):
self.assert_mempool_contents(expected=expected_txns)
# Now make conflicting packages for each coin
- package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex1)
assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"])
- package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex2)
assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has both ancestor and descendant, exceeding cluster limit of 2", package_result["package_msg"])
- package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex3)
assert_equal(f"package RBF failed: {grandchild_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
@@ -377,15 +389,15 @@ class PackageRBFTest(BitcoinTestFramework):
self.assert_mempool_contents(expected=expected_txns)
# Now make conflicting packages for each coin
- package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex1)
assert_equal(f"package RBF failed: {parent1_result['tx'].rehash()} is not the only parent of child {child_result['tx'].rehash()}", package_result["package_msg"])
- package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex2)
assert_equal(f"package RBF failed: {parent2_result['tx'].rehash()} is not the only parent of child {child_result['tx'].rehash()}", package_result["package_msg"])
- package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex3)
assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
@@ -431,15 +443,15 @@ class PackageRBFTest(BitcoinTestFramework):
self.assert_mempool_contents(expected=expected_txns)
# Now make conflicting packages for each coin
- package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex1)
assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"])
- package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex2)
assert_equal(f"package RBF failed: {child1_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
- package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
+ package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
package_result = node.submitpackage(package_hex3)
assert_equal(f"package RBF failed: {child2_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
@@ -507,7 +519,7 @@ class PackageRBFTest(BitcoinTestFramework):
# Package 2 feerate is below the feerate of directly conflicted parent, so it fails even though
# total fees are higher than the original package
- package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE)
+ package_hex2, _package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE)
pkg_results2 = node.submitpackage(package_hex2)
assert_equal(pkg_results2["package_msg"], 'package RBF failed: insufficient feerate: does not improve feerate diagram')
self.assert_mempool_contents(expected=package_txns1)
@@ -542,7 +554,7 @@ class PackageRBFTest(BitcoinTestFramework):
self.generate(node, 1)
def test_child_conflicts_parent_mempool_ancestor(self):
- fill_mempool(self, self.nodes[0])
+ fill_mempool(self, self.nodes[0], tx_sync_fun=self.no_op)
# Reset coins since we filled the mempool with current coins
self.coins = self.wallet.get_utxos(mark_as_spent=False, confirmed_only=True)
@@ -584,4 +596,4 @@ class PackageRBFTest(BitcoinTestFramework):
assert child_result["txid"] not in mempool_info
if __name__ == "__main__":
- PackageRBFTest().main()
+ PackageRBFTest(__file__).main()
diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py
index 4be6594de6..a844a2a1d8 100755
--- a/test/functional/mempool_packages.py
+++ b/test/functional/mempool_packages.py
@@ -298,4 +298,4 @@ class MempoolPackagesTest(BitcoinTestFramework):
self.sync_blocks()
if __name__ == '__main__':
- MempoolPackagesTest().main()
+ MempoolPackagesTest(__file__).main()
diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py
index 32a927084a..c64c203e50 100755
--- a/test/functional/mempool_persist.py
+++ b/test/functional/mempool_persist.py
@@ -263,4 +263,4 @@ class MempoolPersistTest(BitcoinTestFramework):
if __name__ == "__main__":
- MempoolPersistTest().main()
+ MempoolPersistTest(__file__).main()
diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py
index 691518ea09..74a353ab01 100755
--- a/test/functional/mempool_reorg.py
+++ b/test/functional/mempool_reorg.py
@@ -162,7 +162,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
self.log.info("Generate a block")
last_block = self.generate(self.nodes[0], 1)
# generate() implicitly syncs blocks, so that peer 1 gets the block before timelock_tx
- # Otherwise, peer 1 would put the timelock_tx in m_recent_rejects
+ # Otherwise, peer 1 would put the timelock_tx in m_lazy_recent_rejects
self.log.info("The time-locked transaction can now be spent")
timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx)
@@ -194,4 +194,4 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
if __name__ == '__main__':
- MempoolCoinbaseTest().main()
+ MempoolCoinbaseTest(__file__).main()
diff --git a/test/functional/mempool_resurrect.py b/test/functional/mempool_resurrect.py
index c10052372d..720255b9e3 100755
--- a/test/functional/mempool_resurrect.py
+++ b/test/functional/mempool_resurrect.py
@@ -55,4 +55,4 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
if __name__ == '__main__':
- MempoolCoinbaseTest().main()
+ MempoolCoinbaseTest(__file__).main()
diff --git a/test/functional/mempool_sigoplimit.py b/test/functional/mempool_sigoplimit.py
index d3fb5f9119..47df0c614a 100755
--- a/test/functional/mempool_sigoplimit.py
+++ b/test/functional/mempool_sigoplimit.py
@@ -154,7 +154,7 @@ class BytesPerSigOpTest(BitcoinTestFramework):
return (tx_utxo, tx)
tx_parent_utxo, tx_parent = create_bare_multisig_tx()
- tx_child_utxo, tx_child = create_bare_multisig_tx(tx_parent_utxo)
+ _tx_child_utxo, tx_child = create_bare_multisig_tx(tx_parent_utxo)
# Separately, the parent tx is ok
parent_individual_testres = self.nodes[0].testmempoolaccept([tx_parent.serialize().hex()])[0]
@@ -196,4 +196,4 @@ class BytesPerSigOpTest(BitcoinTestFramework):
if __name__ == '__main__':
- BytesPerSigOpTest().main()
+ BytesPerSigOpTest(__file__).main()
diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py
index a7cb2ba602..64ab33d3ff 100755
--- a/test/functional/mempool_spend_coinbase.py
+++ b/test/functional/mempool_spend_coinbase.py
@@ -57,4 +57,4 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework):
if __name__ == '__main__':
- MempoolSpendCoinbaseTest().main()
+ MempoolSpendCoinbaseTest(__file__).main()
diff --git a/test/functional/mempool_accept_v3.py b/test/functional/mempool_truc.py
index d4a33c232e..54a258215d 100755
--- a/test/functional/mempool_accept_v3.py
+++ b/test/functional/mempool_truc.py
@@ -6,7 +6,6 @@ from decimal import Decimal
from test_framework.messages import (
MAX_BIP125_RBF_SEQUENCE,
- WITNESS_SCALE_FACTOR,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -22,7 +21,8 @@ from test_framework.wallet import (
)
MAX_REPLACEMENT_CANDIDATES = 100
-V3_MAX_VSIZE = 10000
+TRUC_MAX_VSIZE = 10000
+TRUC_CHILD_MAX_VSIZE = 1000
def cleanup(extra_args=None):
def decorator(func):
@@ -39,7 +39,7 @@ def cleanup(extra_args=None):
return wrapper
return decorator
-class MempoolAcceptV3(BitcoinTestFramework):
+class MempoolTRUC(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [[]]
@@ -52,63 +52,64 @@ class MempoolAcceptV3(BitcoinTestFramework):
assert all([txid in txids for txid in mempool_contents])
@cleanup(extra_args=["-datacarriersize=20000"])
- def test_v3_max_vsize(self):
+ def test_truc_max_vsize(self):
node = self.nodes[0]
- self.log.info("Test v3-specific maximum transaction vsize")
- tx_v3_heavy = self.wallet.create_self_transfer(target_weight=(V3_MAX_VSIZE + 1) * WITNESS_SCALE_FACTOR, version=3)
- assert_greater_than_or_equal(tx_v3_heavy["tx"].get_vsize(), V3_MAX_VSIZE)
- expected_error_heavy = f"v3-rule-violation, v3 tx {tx_v3_heavy['txid']} (wtxid={tx_v3_heavy['wtxid']}) is too big"
+ self.log.info("Test TRUC-specific maximum transaction vsize")
+ tx_v3_heavy = self.wallet.create_self_transfer(target_vsize=TRUC_MAX_VSIZE + 1, version=3)
+ assert_greater_than_or_equal(tx_v3_heavy["tx"].get_vsize(), TRUC_MAX_VSIZE)
+ expected_error_heavy = f"TRUC-violation, version=3 tx {tx_v3_heavy['txid']} (wtxid={tx_v3_heavy['wtxid']}) is too big"
assert_raises_rpc_error(-26, expected_error_heavy, node.sendrawtransaction, tx_v3_heavy["hex"])
self.check_mempool([])
- # Ensure we are hitting the v3-specific limit and not something else
- tx_v2_heavy = self.wallet.send_self_transfer(from_node=node, target_weight=(V3_MAX_VSIZE + 1) * WITNESS_SCALE_FACTOR, version=2)
+ # Ensure we are hitting the TRUC-specific limit and not something else
+ tx_v2_heavy = self.wallet.send_self_transfer(from_node=node, target_vsize=TRUC_MAX_VSIZE + 1, version=2)
self.check_mempool([tx_v2_heavy["txid"]])
@cleanup(extra_args=["-datacarriersize=1000"])
- def test_v3_acceptance(self):
+ def test_truc_acceptance(self):
node = self.nodes[0]
- self.log.info("Test a child of a v3 transaction cannot be more than 1000vB")
+ self.log.info("Test a child of a TRUC transaction cannot be more than 1000vB")
tx_v3_parent_normal = self.wallet.send_self_transfer(from_node=node, version=3)
self.check_mempool([tx_v3_parent_normal["txid"]])
tx_v3_child_heavy = self.wallet.create_self_transfer(
utxo_to_spend=tx_v3_parent_normal["new_utxo"],
- target_weight=4004,
+ target_vsize=TRUC_CHILD_MAX_VSIZE + 1,
version=3
)
- assert_greater_than_or_equal(tx_v3_child_heavy["tx"].get_vsize(), 1000)
- expected_error_child_heavy = f"v3-rule-violation, v3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big"
+ assert_greater_than_or_equal(tx_v3_child_heavy["tx"].get_vsize(), TRUC_CHILD_MAX_VSIZE)
+ expected_error_child_heavy = f"TRUC-violation, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big"
assert_raises_rpc_error(-26, expected_error_child_heavy, node.sendrawtransaction, tx_v3_child_heavy["hex"])
self.check_mempool([tx_v3_parent_normal["txid"]])
# tx has no descendants
assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 1)
- self.log.info("Test that, during replacements, only the new transaction counts for v3 descendant limit")
+ self.log.info("Test that, during replacements, only the new transaction counts for TRUC descendant limit")
tx_v3_child_almost_heavy = self.wallet.send_self_transfer(
from_node=node,
fee_rate=DEFAULT_FEE,
utxo_to_spend=tx_v3_parent_normal["new_utxo"],
- target_weight=3987,
+ target_vsize=TRUC_CHILD_MAX_VSIZE - 3,
version=3
)
- assert_greater_than_or_equal(1000, tx_v3_child_almost_heavy["tx"].get_vsize())
+ assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_almost_heavy["tx"].get_vsize())
self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy["txid"]])
assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)
tx_v3_child_almost_heavy_rbf = self.wallet.send_self_transfer(
from_node=node,
fee_rate=DEFAULT_FEE * 2,
utxo_to_spend=tx_v3_parent_normal["new_utxo"],
- target_weight=3500,
+ target_vsize=875,
version=3
)
- assert_greater_than_or_equal(tx_v3_child_almost_heavy["tx"].get_vsize() + tx_v3_child_almost_heavy_rbf["tx"].get_vsize(), 1000)
+ assert_greater_than_or_equal(tx_v3_child_almost_heavy["tx"].get_vsize() + tx_v3_child_almost_heavy_rbf["tx"].get_vsize(),
+ TRUC_CHILD_MAX_VSIZE)
self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy_rbf["txid"]])
assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)
@cleanup(extra_args=None)
- def test_v3_replacement(self):
+ def test_truc_replacement(self):
node = self.nodes[0]
- self.log.info("Test v3 transactions may be replaced by v3 transactions")
+ self.log.info("Test TRUC transactions may be replaced by TRUC transactions")
utxo_v3_bip125 = self.wallet.get_utxo()
tx_v3_bip125 = self.wallet.send_self_transfer(
from_node=node,
@@ -127,7 +128,7 @@ class MempoolAcceptV3(BitcoinTestFramework):
)
self.check_mempool([tx_v3_bip125_rbf["txid"]])
- self.log.info("Test v3 transactions may be replaced by V2 transactions")
+ self.log.info("Test TRUC transactions may be replaced by non-TRUC (BIP125) transactions")
tx_v3_bip125_rbf_v2 = self.wallet.send_self_transfer(
from_node=node,
fee_rate=DEFAULT_FEE * 3,
@@ -136,7 +137,7 @@ class MempoolAcceptV3(BitcoinTestFramework):
)
self.check_mempool([tx_v3_bip125_rbf_v2["txid"]])
- self.log.info("Test that replacements cannot cause violation of inherited v3")
+ self.log.info("Test that replacements cannot cause violation of inherited TRUC")
utxo_v3_parent = self.wallet.get_utxo()
tx_v3_parent = self.wallet.send_self_transfer(
from_node=node,
@@ -157,15 +158,15 @@ class MempoolAcceptV3(BitcoinTestFramework):
utxo_to_spend=tx_v3_parent["new_utxo"],
version=2
)
- expected_error_v2_v3 = f"v3-rule-violation, non-v3 tx {tx_v3_child_rbf_v2['txid']} (wtxid={tx_v3_child_rbf_v2['wtxid']}) cannot spend from v3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})"
+ expected_error_v2_v3 = f"TRUC-violation, non-version=3 tx {tx_v3_child_rbf_v2['txid']} (wtxid={tx_v3_child_rbf_v2['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})"
assert_raises_rpc_error(-26, expected_error_v2_v3, node.sendrawtransaction, tx_v3_child_rbf_v2["hex"])
self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
- @cleanup(extra_args=None)
- def test_v3_bip125(self):
+ @cleanup(extra_args=["-mempoolfullrbf=0"])
+ def test_truc_bip125(self):
node = self.nodes[0]
- self.log.info("Test v3 transactions that don't signal BIP125 are replaceable")
+ self.log.info("Test TRUC transactions that don't signal BIP125 are replaceable")
assert_equal(node.getmempoolinfo()["fullrbf"], False)
utxo_v3_no_bip125 = self.wallet.get_utxo()
tx_v3_no_bip125 = self.wallet.send_self_transfer(
@@ -187,9 +188,9 @@ class MempoolAcceptV3(BitcoinTestFramework):
self.check_mempool([tx_v3_no_bip125_rbf["txid"]])
@cleanup(extra_args=["-datacarriersize=40000"])
- def test_v3_reorg(self):
+ def test_truc_reorg(self):
node = self.nodes[0]
- self.log.info("Test that, during a reorg, v3 rules are not enforced")
+ self.log.info("Test that, during a reorg, TRUC rules are not enforced")
tx_v2_block = self.wallet.send_self_transfer(from_node=node, version=2)
tx_v3_block = self.wallet.send_self_transfer(from_node=node, version=3)
tx_v3_block2 = self.wallet.send_self_transfer(from_node=node, version=3)
@@ -199,8 +200,8 @@ class MempoolAcceptV3(BitcoinTestFramework):
self.check_mempool([])
tx_v2_from_v3 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block["new_utxo"], version=2)
tx_v3_from_v2 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v2_block["new_utxo"], version=3)
- tx_v3_child_large = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block2["new_utxo"], target_weight=5000, version=3)
- assert_greater_than(node.getmempoolentry(tx_v3_child_large["txid"])["vsize"], 1000)
+ tx_v3_child_large = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block2["new_utxo"], target_vsize=1250, version=3)
+ assert_greater_than(node.getmempoolentry(tx_v3_child_large["txid"])["vsize"], TRUC_CHILD_MAX_VSIZE)
self.check_mempool([tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"]])
node.invalidateblock(block[0])
self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"], tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"]])
@@ -211,28 +212,28 @@ class MempoolAcceptV3(BitcoinTestFramework):
@cleanup(extra_args=["-limitdescendantsize=10", "-datacarriersize=40000"])
def test_nondefault_package_limits(self):
"""
- Max standard tx size + v3 rules imply the ancestor/descendant rules (at their default
+ Max standard tx size + TRUC rules imply the ancestor/descendant rules (at their default
values), but those checks must not be skipped. Ensure both sets of checks are done by
changing the ancestor/descendant limit configurations.
"""
node = self.nodes[0]
- self.log.info("Test that a decreased limitdescendantsize also applies to v3 child")
- parent_target_weight = 9990 * WITNESS_SCALE_FACTOR
- child_target_weight = 500 * WITNESS_SCALE_FACTOR
+ self.log.info("Test that a decreased limitdescendantsize also applies to TRUC child")
+ parent_target_vsize = 9990
+ child_target_vsize = 500
tx_v3_parent_large1 = self.wallet.send_self_transfer(
from_node=node,
- target_weight=parent_target_weight,
+ target_vsize=parent_target_vsize,
version=3
)
tx_v3_child_large1 = self.wallet.create_self_transfer(
utxo_to_spend=tx_v3_parent_large1["new_utxo"],
- target_weight=child_target_weight,
+ target_vsize=child_target_vsize,
version=3
)
# Parent and child are within v3 limits, but parent's 10kvB descendant limit is exceeded
- assert_greater_than_or_equal(V3_MAX_VSIZE, tx_v3_parent_large1["tx"].get_vsize())
- assert_greater_than_or_equal(1000, tx_v3_child_large1["tx"].get_vsize())
+ assert_greater_than_or_equal(TRUC_MAX_VSIZE, tx_v3_parent_large1["tx"].get_vsize())
+ assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_large1["tx"].get_vsize())
assert_greater_than(tx_v3_parent_large1["tx"].get_vsize() + tx_v3_child_large1["tx"].get_vsize(), 10000)
assert_raises_rpc_error(-26, f"too-long-mempool-chain, exceeds descendant size limit for tx {tx_v3_parent_large1['txid']}", node.sendrawtransaction, tx_v3_child_large1["hex"])
@@ -244,35 +245,35 @@ class MempoolAcceptV3(BitcoinTestFramework):
self.restart_node(0, extra_args=["-limitancestorsize=10", "-datacarriersize=40000"])
tx_v3_parent_large2 = self.wallet.send_self_transfer(
from_node=node,
- target_weight=parent_target_weight,
+ target_vsize=parent_target_vsize,
version=3
)
tx_v3_child_large2 = self.wallet.create_self_transfer(
utxo_to_spend=tx_v3_parent_large2["new_utxo"],
- target_weight=child_target_weight,
+ target_vsize=child_target_vsize,
version=3
)
- # Parent and child are within v3 limits
- assert_greater_than_or_equal(V3_MAX_VSIZE, tx_v3_parent_large2["tx"].get_vsize())
- assert_greater_than_or_equal(1000, tx_v3_child_large2["tx"].get_vsize())
+ # Parent and child are within TRUC limits
+ assert_greater_than_or_equal(TRUC_MAX_VSIZE, tx_v3_parent_large2["tx"].get_vsize())
+ assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_large2["tx"].get_vsize())
assert_greater_than(tx_v3_parent_large2["tx"].get_vsize() + tx_v3_child_large2["tx"].get_vsize(), 10000)
assert_raises_rpc_error(-26, f"too-long-mempool-chain, exceeds ancestor size limit", node.sendrawtransaction, tx_v3_child_large2["hex"])
self.check_mempool([tx_v3_parent_large2["txid"]])
@cleanup(extra_args=["-datacarriersize=1000"])
- def test_v3_ancestors_package(self):
- self.log.info("Test that v3 ancestor limits are checked within the package")
+ def test_truc_ancestors_package(self):
+ self.log.info("Test that TRUC ancestor limits are checked within the package")
node = self.nodes[0]
tx_v3_parent_normal = self.wallet.create_self_transfer(
fee_rate=0,
- target_weight=4004,
+ target_vsize=1001,
version=3
)
tx_v3_parent_2_normal = self.wallet.create_self_transfer(
fee_rate=0,
- target_weight=4004,
+ target_vsize=1001,
version=3
)
tx_v3_child_multiparent = self.wallet.create_self_transfer_multi(
@@ -282,41 +283,41 @@ class MempoolAcceptV3(BitcoinTestFramework):
)
tx_v3_child_heavy = self.wallet.create_self_transfer_multi(
utxos_to_spend=[tx_v3_parent_normal["new_utxo"]],
- target_weight=4004,
+ target_vsize=TRUC_CHILD_MAX_VSIZE + 1,
fee_per_output=10000,
version=3
)
self.check_mempool([])
result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_parent_2_normal["hex"], tx_v3_child_multiparent["hex"]])
- assert_equal(result['package_msg'], f"v3-violation, tx {tx_v3_child_multiparent['txid']} (wtxid={tx_v3_child_multiparent['wtxid']}) would have too many ancestors")
+ assert_equal(result['package_msg'], f"TRUC-violation, tx {tx_v3_child_multiparent['txid']} (wtxid={tx_v3_child_multiparent['wtxid']}) would have too many ancestors")
self.check_mempool([])
self.check_mempool([])
result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_child_heavy["hex"]])
- # tx_v3_child_heavy is heavy based on weight, not sigops.
- assert_equal(result['package_msg'], f"v3-violation, v3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big: {tx_v3_child_heavy['tx'].get_vsize()} > 1000 virtual bytes")
+ # tx_v3_child_heavy is heavy based on vsize, not sigops.
+ assert_equal(result['package_msg'], f"TRUC-violation, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big: {tx_v3_child_heavy['tx'].get_vsize()} > 1000 virtual bytes")
self.check_mempool([])
tx_v3_parent = self.wallet.create_self_transfer(version=3)
tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxo"], version=3)
tx_v3_grandchild = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_child["new_utxo"], version=3)
result = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child["hex"], tx_v3_grandchild["hex"]])
- assert all([txresult["package-error"] == f"v3-violation, tx {tx_v3_grandchild['txid']} (wtxid={tx_v3_grandchild['wtxid']}) would have too many ancestors" for txresult in result])
+ assert all([txresult["package-error"] == f"TRUC-violation, tx {tx_v3_grandchild['txid']} (wtxid={tx_v3_grandchild['wtxid']}) would have too many ancestors" for txresult in result])
@cleanup(extra_args=None)
- def test_v3_ancestors_package_and_mempool(self):
+ def test_truc_ancestors_package_and_mempool(self):
"""
- A v3 transaction in a package cannot have 2 v3 parents.
+ A TRUC transaction in a package cannot have 2 TRUC parents.
Test that if we have a transaction graph A -> B -> C, where A, B, C are
- all v3 transactions, that we cannot use submitpackage to get the
+ all TRUC transactions, that we cannot use submitpackage to get the
transactions all into the mempool.
Verify, in particular, that if A is already in the mempool, then
submitpackage(B, C) will fail.
"""
node = self.nodes[0]
- self.log.info("Test that v3 ancestor limits include transactions within the package and all in-mempool ancestors")
+ self.log.info("Test that TRUC ancestor limits include transactions within the package and all in-mempool ancestors")
# This is our transaction "A":
tx_in_mempool = self.wallet.send_self_transfer(from_node=node, version=3)
@@ -331,7 +332,7 @@ class MempoolAcceptV3(BitcoinTestFramework):
# submitpackage(B, C) should fail
result = node.submitpackage([tx_0fee_parent["hex"], tx_child_violator["hex"]])
- assert_equal(result['package_msg'], f"v3-violation, tx {tx_child_violator['txid']} (wtxid={tx_child_violator['wtxid']}) would have too many ancestors")
+ assert_equal(result['package_msg'], f"TRUC-violation, tx {tx_child_violator['txid']} (wtxid={tx_child_violator['wtxid']}) would have too many ancestors")
self.check_mempool([tx_in_mempool["txid"]])
@cleanup(extra_args=None)
@@ -341,7 +342,7 @@ class MempoolAcceptV3(BitcoinTestFramework):
However, this option is only available in single transaction acceptance. It doesn't work in
a multi-testmempoolaccept (where RBF is disabled) or when doing package CPFP.
"""
- self.log.info("Test v3 sibling eviction in submitpackage and multi-testmempoolaccept")
+ self.log.info("Test TRUC sibling eviction in submitpackage and multi-testmempoolaccept")
node = self.nodes[0]
# Add a parent + child to mempool
tx_mempool_parent = self.wallet.send_self_transfer_multi(
@@ -384,17 +385,17 @@ class MempoolAcceptV3(BitcoinTestFramework):
# Fails with another non-related transaction via testmempoolaccept
tx_unrelated = self.wallet.create_self_transfer(version=3)
result_test_unrelated = node.testmempoolaccept([tx_sibling_1["hex"], tx_unrelated["hex"]])
- assert_equal(result_test_unrelated[0]["reject-reason"], "v3-rule-violation")
+ assert_equal(result_test_unrelated[0]["reject-reason"], "TRUC-violation")
# Fails in a package via testmempoolaccept
result_test_1p1c = node.testmempoolaccept([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
- assert_equal(result_test_1p1c[0]["reject-reason"], "v3-rule-violation")
+ assert_equal(result_test_1p1c[0]["reject-reason"], "TRUC-violation")
# Allowed when tx is submitted in a package and evaluated individually.
# Note that the child failed since it would be the 3rd generation.
result_package_indiv = node.submitpackage([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
self.check_mempool([tx_mempool_parent["txid"], tx_sibling_1["txid"]])
- expected_error_gen3 = f"v3-rule-violation, tx {tx_has_mempool_uncle['txid']} (wtxid={tx_has_mempool_uncle['wtxid']}) would have too many ancestors"
+ expected_error_gen3 = f"TRUC-violation, tx {tx_has_mempool_uncle['txid']} (wtxid={tx_has_mempool_uncle['wtxid']}) would have too many ancestors"
assert_equal(result_package_indiv["tx-results"][tx_has_mempool_uncle['wtxid']]['error'], expected_error_gen3)
@@ -402,21 +403,21 @@ class MempoolAcceptV3(BitcoinTestFramework):
node.submitpackage([tx_mempool_parent["hex"], tx_sibling_2["hex"]])
self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
- # Child cannot pay for sibling eviction for parent, as it violates v3 topology limits
+ # Child cannot pay for sibling eviction for parent, as it violates TRUC topology limits
result_package_cpfp = node.submitpackage([tx_sibling_3["hex"], tx_bumps_parent_with_sibling["hex"]])
self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
- expected_error_cpfp = f"v3-rule-violation, tx {tx_mempool_parent['txid']} (wtxid={tx_mempool_parent['wtxid']}) would exceed descendant count limit"
+ expected_error_cpfp = f"TRUC-violation, tx {tx_mempool_parent['txid']} (wtxid={tx_mempool_parent['wtxid']}) would exceed descendant count limit"
assert_equal(result_package_cpfp["tx-results"][tx_sibling_3['wtxid']]['error'], expected_error_cpfp)
@cleanup(extra_args=["-datacarriersize=1000"])
- def test_v3_package_inheritance(self):
- self.log.info("Test that v3 inheritance is checked within package")
+ def test_truc_package_inheritance(self):
+ self.log.info("Test that TRUC inheritance is checked within package")
node = self.nodes[0]
tx_v3_parent = self.wallet.create_self_transfer(
fee_rate=0,
- target_weight=4004,
+ target_vsize=1001,
version=3
)
tx_v2_child = self.wallet.create_self_transfer_multi(
@@ -426,14 +427,14 @@ class MempoolAcceptV3(BitcoinTestFramework):
)
self.check_mempool([])
result = node.submitpackage([tx_v3_parent["hex"], tx_v2_child["hex"]])
- assert_equal(result['package_msg'], f"v3-violation, non-v3 tx {tx_v2_child['txid']} (wtxid={tx_v2_child['wtxid']}) cannot spend from v3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})")
+ assert_equal(result['package_msg'], f"TRUC-violation, non-version=3 tx {tx_v2_child['txid']} (wtxid={tx_v2_child['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})")
self.check_mempool([])
@cleanup(extra_args=None)
- def test_v3_in_testmempoolaccept(self):
+ def test_truc_in_testmempoolaccept(self):
node = self.nodes[0]
- self.log.info("Test that v3 inheritance is accurately assessed in testmempoolaccept")
+ self.log.info("Test that TRUC inheritance is accurately assessed in testmempoolaccept")
tx_v2 = self.wallet.create_self_transfer(version=2)
tx_v2_from_v2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v2["new_utxo"], version=2)
tx_v3_from_v2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v2["new_utxo"], version=3)
@@ -447,11 +448,11 @@ class MempoolAcceptV3(BitcoinTestFramework):
assert all([result["allowed"] for result in test_accept_v2_and_v3])
test_accept_v3_from_v2 = node.testmempoolaccept([tx_v2["hex"], tx_v3_from_v2["hex"]])
- expected_error_v3_from_v2 = f"v3-violation, v3 tx {tx_v3_from_v2['txid']} (wtxid={tx_v3_from_v2['wtxid']}) cannot spend from non-v3 tx {tx_v2['txid']} (wtxid={tx_v2['wtxid']})"
+ expected_error_v3_from_v2 = f"TRUC-violation, version=3 tx {tx_v3_from_v2['txid']} (wtxid={tx_v3_from_v2['wtxid']}) cannot spend from non-version=3 tx {tx_v2['txid']} (wtxid={tx_v2['wtxid']})"
assert all([result["package-error"] == expected_error_v3_from_v2 for result in test_accept_v3_from_v2])
test_accept_v2_from_v3 = node.testmempoolaccept([tx_v3["hex"], tx_v2_from_v3["hex"]])
- expected_error_v2_from_v3 = f"v3-violation, non-v3 tx {tx_v2_from_v3['txid']} (wtxid={tx_v2_from_v3['wtxid']}) cannot spend from v3 tx {tx_v3['txid']} (wtxid={tx_v3['wtxid']})"
+ expected_error_v2_from_v3 = f"TRUC-violation, non-version=3 tx {tx_v2_from_v3['txid']} (wtxid={tx_v2_from_v3['wtxid']}) cannot spend from version=3 tx {tx_v3['txid']} (wtxid={tx_v3['wtxid']})"
assert all([result["package-error"] == expected_error_v2_from_v3 for result in test_accept_v2_from_v3])
test_accept_pairs = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"], tx_v2_from_v2["hex"], tx_v3_from_v3["hex"]])
@@ -463,16 +464,16 @@ class MempoolAcceptV3(BitcoinTestFramework):
tx_v3_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][0], version=3)
tx_v3_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][1], version=3)
test_accept_2children = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
- expected_error_2children = f"v3-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
+ expected_error_2children = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
assert all([result["package-error"] == expected_error_2children for result in test_accept_2children])
- # Extra v3 transaction does not get incorrectly marked as extra descendant
+ # Extra TRUC transaction does not get incorrectly marked as extra descendant
test_accept_1child_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_independent["hex"]])
assert all([result["allowed"] for result in test_accept_1child_with_exra])
- # Extra v3 transaction does not make us ignore the extra descendant
+ # Extra TRUC transaction does not make us ignore the extra descendant
test_accept_2children_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"], tx_v3_independent["hex"]])
- expected_error_extra = f"v3-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
+ expected_error_extra = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
assert all([result["package-error"] == expected_error_extra for result in test_accept_2children_with_exra])
# Same result if the parent is already in mempool
node.sendrawtransaction(tx_v3_parent["hex"])
@@ -482,7 +483,7 @@ class MempoolAcceptV3(BitcoinTestFramework):
@cleanup(extra_args=None)
def test_reorg_2child_rbf(self):
node = self.nodes[0]
- self.log.info("Test that children of a v3 transaction can be replaced individually, even if there are multiple due to reorg")
+ self.log.info("Test that children of a TRUC transaction can be replaced individually, even if there are multiple due to reorg")
ancestor_tx = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
self.check_mempool([ancestor_tx["txid"]])
@@ -511,8 +512,8 @@ class MempoolAcceptV3(BitcoinTestFramework):
assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)
@cleanup(extra_args=None)
- def test_v3_sibling_eviction(self):
- self.log.info("Test sibling eviction for v3")
+ def test_truc_sibling_eviction(self):
+ self.log.info("Test sibling eviction for TRUC")
node = self.nodes[0]
tx_v3_parent = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)
# This is the sibling to replace
@@ -609,7 +610,7 @@ class MempoolAcceptV3(BitcoinTestFramework):
utxo_to_spend=tx_with_multi_children["new_utxos"][2],
fee_rate=DEFAULT_FEE*50
)
- expected_error_2siblings = f"v3-rule-violation, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"
+ expected_error_2siblings = f"TRUC-violation, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"
assert_raises_rpc_error(-26, expected_error_2siblings, node.sendrawtransaction, tx_with_sibling3["hex"])
# However, an RBF (with conflicting inputs) is possible even if the resulting cluster size exceeds 2
@@ -627,21 +628,21 @@ class MempoolAcceptV3(BitcoinTestFramework):
node = self.nodes[0]
self.wallet = MiniWallet(node)
self.generate(self.wallet, 120)
- self.test_v3_max_vsize()
- self.test_v3_acceptance()
- self.test_v3_replacement()
- self.test_v3_bip125()
- self.test_v3_reorg()
+ self.test_truc_max_vsize()
+ self.test_truc_acceptance()
+ self.test_truc_replacement()
+ self.test_truc_bip125()
+ self.test_truc_reorg()
self.test_nondefault_package_limits()
- self.test_v3_ancestors_package()
- self.test_v3_ancestors_package_and_mempool()
+ self.test_truc_ancestors_package()
+ self.test_truc_ancestors_package_and_mempool()
self.test_sibling_eviction_package()
- self.test_v3_package_inheritance()
- self.test_v3_in_testmempoolaccept()
+ self.test_truc_package_inheritance()
+ self.test_truc_in_testmempoolaccept()
self.test_reorg_2child_rbf()
- self.test_v3_sibling_eviction()
+ self.test_truc_sibling_eviction()
self.test_reorg_sibling_eviction_1p2c()
if __name__ == "__main__":
- MempoolAcceptV3().main()
+ MempoolTRUC(__file__).main()
diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py
index 12de750731..7c96b4b570 100755
--- a/test/functional/mempool_unbroadcast.py
+++ b/test/functional/mempool_unbroadcast.py
@@ -109,4 +109,4 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
if __name__ == "__main__":
- MempoolUnbroadcastTest().main()
+ MempoolUnbroadcastTest(__file__).main()
diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py
index 8350e9c91e..1754991756 100755
--- a/test/functional/mempool_updatefromblock.py
+++ b/test/functional/mempool_updatefromblock.py
@@ -103,4 +103,4 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
if __name__ == '__main__':
- MempoolUpdateFromBlockTest().main()
+ MempoolUpdateFromBlockTest(__file__).main()
diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py
index 5f2dde8eac..aca71933ec 100755
--- a/test/functional/mining_basic.py
+++ b/test/functional/mining_basic.py
@@ -28,12 +28,16 @@ from test_framework.p2p import P2PDataStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ assert_greater_than_or_equal,
assert_raises_rpc_error,
get_fee,
)
from test_framework.wallet import MiniWallet
+DIFFICULTY_ADJUSTMENT_INTERVAL = 144
+MAX_FUTURE_BLOCK_TIME = 2 * 3600
+MAX_TIMEWARP = 600
VERSIONBITS_TOP_BITS = 0x20000000
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB]
@@ -115,6 +119,55 @@ class MiningTest(BitcoinTestFramework):
assert tx_below_min_feerate['txid'] not in block_template_txids
assert tx_below_min_feerate['txid'] not in block_txids
+ def test_timewarp(self):
+ self.log.info("Test timewarp attack mitigation (BIP94)")
+ node = self.nodes[0]
+
+ self.log.info("Mine until the last block of the retarget period")
+ blockchain_info = self.nodes[0].getblockchaininfo()
+ n = DIFFICULTY_ADJUSTMENT_INTERVAL - blockchain_info['blocks'] % DIFFICULTY_ADJUSTMENT_INTERVAL - 2
+ t = blockchain_info['time']
+
+ for _ in range(n):
+ t += 600
+ self.nodes[0].setmocktime(t)
+ self.generate(self.wallet, 1, sync_fun=self.no_op)
+
+ self.log.info("Create block two hours in the future")
+ self.nodes[0].setmocktime(t + MAX_FUTURE_BLOCK_TIME)
+ self.generate(self.wallet, 1, sync_fun=self.no_op)
+ assert_equal(node.getblock(node.getbestblockhash())['time'], t + MAX_FUTURE_BLOCK_TIME)
+
+ self.log.info("First block template of retarget period can't use wall clock time")
+ self.nodes[0].setmocktime(t)
+ # The template will have an adjusted timestamp, which we then modify
+ tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
+ assert_greater_than_or_equal(tmpl['curtime'], t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP)
+
+ block = CBlock()
+ block.nVersion = tmpl["version"]
+ block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
+ block.nTime = tmpl["curtime"]
+ block.nBits = int(tmpl["bits"], 16)
+ block.nNonce = 0
+ block.vtx = [create_coinbase(height=int(tmpl["height"]))]
+ block.solve()
+ assert_template(node, block, None)
+
+ bad_block = copy.deepcopy(block)
+ bad_block.nTime = t
+ bad_block.solve()
+ assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex()))
+
+ self.log.info("Test timewarp protection boundary")
+ bad_block.nTime = t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP - 1
+ bad_block.solve()
+ assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex()))
+
+ bad_block.nTime = t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP
+ bad_block.solve()
+ node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex())
+
def run_test(self):
node = self.nodes[0]
self.wallet = MiniWallet(node)
@@ -322,7 +375,8 @@ class MiningTest(BitcoinTestFramework):
assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
self.test_blockmintxfee_parameter()
+ self.test_timewarp()
if __name__ == '__main__':
- MiningTest().main()
+ MiningTest(__file__).main()
diff --git a/test/functional/mining_getblocktemplate_longpoll.py b/test/functional/mining_getblocktemplate_longpoll.py
index c0e7195c82..2d15151e65 100755
--- a/test/functional/mining_getblocktemplate_longpoll.py
+++ b/test/functional/mining_getblocktemplate_longpoll.py
@@ -73,4 +73,4 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
assert not thr.is_alive()
if __name__ == '__main__':
- GetBlockTemplateLPTest().main()
+ GetBlockTemplateLPTest(__file__).main()
diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py
index c5f34e3ecb..eb55202e16 100755
--- a/test/functional/mining_prioritisetransaction.py
+++ b/test/functional/mining_prioritisetransaction.py
@@ -305,4 +305,4 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
assert template != new_template
if __name__ == '__main__':
- PrioritiseTransactionTest().main()
+ PrioritiseTransactionTest(__file__).main()
diff --git a/test/functional/p2p_1p1c_network.py b/test/functional/p2p_1p1c_network.py
index ea59248506..cdc4e1691d 100755
--- a/test/functional/p2p_1p1c_network.py
+++ b/test/functional/p2p_1p1c_network.py
@@ -49,9 +49,6 @@ class PackageRelayTest(BitcoinTestFramework):
def raise_network_minfee(self):
fill_mempool(self, self.nodes[0])
- self.log.debug("Wait for the network to sync mempools")
- self.sync_mempools()
-
self.log.debug("Check that all nodes' mempool minimum feerates are above min relay feerate")
for node in self.nodes:
assert_equal(node.getmempoolinfo()['minrelaytxfee'], FEERATE_1SAT_VB)
@@ -107,7 +104,7 @@ class PackageRelayTest(BitcoinTestFramework):
# 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
# We require packages to be child-with-unconfirmed-parents and only allow 1-parent-1-child packages.
- package_hex_3, parent_31, parent_32, child_3 = self.create_package_2p1c(self.wallet)
+ package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet)
# 4: parent + child package where the child spends 2 different outputs from the parent.
package_hex_4, parent_4, child_4 = self.create_package_2outs(self.wallet)
@@ -163,4 +160,4 @@ class PackageRelayTest(BitcoinTestFramework):
if __name__ == '__main__':
- PackageRelayTest().main()
+ PackageRelayTest(__file__).main()
diff --git a/test/functional/p2p_add_connections.py b/test/functional/p2p_add_connections.py
index bd766a279e..ee0cbdfa19 100755
--- a/test/functional/p2p_add_connections.py
+++ b/test/functional/p2p_add_connections.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2020-2021 The Bitcoin Core developers
+# Copyright (c) 2020-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test add_outbound_p2p_connection test framework functionality"""
@@ -11,6 +11,14 @@ from test_framework.util import (
check_node_connections,
)
+
+class VersionSender(P2PInterface):
+ def on_open(self):
+ assert self.on_connection_send_msg is not None
+ self.send_version()
+ assert self.on_connection_send_msg is None
+
+
class P2PFeelerReceiver(P2PInterface):
def on_version(self, message):
# The bitcoind node closes feeler connections as soon as a version
@@ -106,5 +114,19 @@ class P2PAddConnections(BitcoinTestFramework):
# Feeler connections do not request tx relay
assert_equal(feeler_conn.last_message["version"].relay, 0)
+ self.log.info("Send version message early to node")
+ # Normally the test framework would be shy and send the version message
+ # only after it received one. See the on_version method. Check that
+ # bitcoind behaves properly when a version is sent unexpectedly (but
+ # tolerably) early.
+ #
+ # This checks that bitcoind sends its own version prior to processing
+ # the remote version (and replying with a verack). Otherwise it would
+ # be violating its own rules, such as "non-version message before
+ # version handshake".
+ ver_conn = self.nodes[0].add_outbound_p2p_connection(VersionSender(), p2p_idx=6, connection_type="outbound-full-relay", supports_v2_p2p=False, advertise_v2_p2p=False)
+ ver_conn.sync_with_ping()
+
+
if __name__ == '__main__':
- P2PAddConnections().main()
+ P2PAddConnections(__file__).main()
diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py
index d10e47e036..56a9e6a84e 100755
--- a/test/functional/p2p_addr_relay.py
+++ b/test/functional/p2p_addr_relay.py
@@ -441,4 +441,4 @@ class AddrTest(BitcoinTestFramework):
if __name__ == '__main__':
- AddrTest().main()
+ AddrTest(__file__).main()
diff --git a/test/functional/p2p_addrfetch.py b/test/functional/p2p_addrfetch.py
index 3ead653ba6..69cc106341 100755
--- a/test/functional/p2p_addrfetch.py
+++ b/test/functional/p2p_addrfetch.py
@@ -83,4 +83,4 @@ class P2PAddrFetch(BitcoinTestFramework):
if __name__ == '__main__':
- P2PAddrFetch().main()
+ P2PAddrFetch(__file__).main()
diff --git a/test/functional/p2p_addrv2_relay.py b/test/functional/p2p_addrv2_relay.py
index 4ec8e0bc04..d5ded926d3 100755
--- a/test/functional/p2p_addrv2_relay.py
+++ b/test/functional/p2p_addrv2_relay.py
@@ -110,4 +110,4 @@ class AddrTest(BitcoinTestFramework):
if __name__ == '__main__':
- AddrTest().main()
+ AddrTest(__file__).main()
diff --git a/test/functional/p2p_block_sync.py b/test/functional/p2p_block_sync.py
index 6c7f08364e..51bbac1738 100755
--- a/test/functional/p2p_block_sync.py
+++ b/test/functional/p2p_block_sync.py
@@ -34,4 +34,4 @@ class BlockSyncTest(BitcoinTestFramework):
if __name__ == '__main__':
- BlockSyncTest().main()
+ BlockSyncTest(__file__).main()
diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py
index 680fa9c7fa..88d5aa1408 100755
--- a/test/functional/p2p_blockfilters.py
+++ b/test/functional/p2p_blockfilters.py
@@ -282,4 +282,4 @@ def compute_last_header(prev_header, hashes):
if __name__ == '__main__':
- CompactFiltersTest().main()
+ CompactFiltersTest(__file__).main()
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index 637644e6e4..b9e6dc9056 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -125,4 +125,4 @@ class P2PBlocksOnly(BitcoinTestFramework):
if __name__ == '__main__':
- P2PBlocksOnly().main()
+ P2PBlocksOnly(__file__).main()
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 9e314db110..49cf26d425 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -965,4 +965,4 @@ class CompactBlocksTest(BitcoinTestFramework):
if __name__ == '__main__':
- CompactBlocksTest().main()
+ CompactBlocksTest(__file__).main()
diff --git a/test/functional/p2p_compactblocks_blocksonly.py b/test/functional/p2p_compactblocks_blocksonly.py
index 761cd3a218..b92efc875c 100755
--- a/test/functional/p2p_compactblocks_blocksonly.py
+++ b/test/functional/p2p_compactblocks_blocksonly.py
@@ -127,4 +127,4 @@ class P2PCompactBlocksBlocksOnly(BitcoinTestFramework):
p2p_conn_blocksonly.wait_until(lambda: test_for_cmpctblock(block2))
if __name__ == '__main__':
- P2PCompactBlocksBlocksOnly().main()
+ P2PCompactBlocksBlocksOnly(__file__).main()
diff --git a/test/functional/p2p_compactblocks_hb.py b/test/functional/p2p_compactblocks_hb.py
index 023b33ff6d..e4f58e9cf7 100755
--- a/test/functional/p2p_compactblocks_hb.py
+++ b/test/functional/p2p_compactblocks_hb.py
@@ -97,4 +97,4 @@ class CompactBlocksConnectionTest(BitcoinTestFramework):
if __name__ == '__main__':
- CompactBlocksConnectionTest().main()
+ CompactBlocksConnectionTest(__file__).main()
diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py
index e47f9c732b..94be86475e 100755
--- a/test/functional/p2p_disconnect_ban.py
+++ b/test/functional/p2p_disconnect_ban.py
@@ -147,4 +147,4 @@ class DisconnectBanTest(BitcoinTestFramework):
assert not [node for node in self.nodes[0].getpeerinfo() if node['id'] == id1]
if __name__ == '__main__':
- DisconnectBanTest().main()
+ DisconnectBanTest(__file__).main()
diff --git a/test/functional/p2p_dns_seeds.py b/test/functional/p2p_dns_seeds.py
index e58ad8e0fc..a2d4ea110f 100755
--- a/test/functional/p2p_dns_seeds.py
+++ b/test/functional/p2p_dns_seeds.py
@@ -126,4 +126,4 @@ class P2PDNSSeeds(BitcoinTestFramework):
if __name__ == '__main__':
- P2PDNSSeeds().main()
+ P2PDNSSeeds(__file__).main()
diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py
index 4b4346af49..fbb5d716f5 100755
--- a/test/functional/p2p_dos_header_tree.py
+++ b/test/functional/p2p_dos_header_tree.py
@@ -85,4 +85,4 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
if __name__ == '__main__':
- RejectLowDifficultyHeadersTest().main()
+ RejectLowDifficultyHeadersTest(__file__).main()
diff --git a/test/functional/p2p_eviction.py b/test/functional/p2p_eviction.py
index 8b31dfa549..0d2bdcc429 100755
--- a/test/functional/p2p_eviction.py
+++ b/test/functional/p2p_eviction.py
@@ -124,4 +124,4 @@ class P2PEvict(BitcoinTestFramework):
if __name__ == '__main__':
- P2PEvict().main()
+ P2PEvict(__file__).main()
diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py
index bcba534f9a..6b44118467 100755
--- a/test/functional/p2p_feefilter.py
+++ b/test/functional/p2p_feefilter.py
@@ -132,4 +132,4 @@ class FeeFilterTest(BitcoinTestFramework):
if __name__ == '__main__':
- FeeFilterTest().main()
+ FeeFilterTest(__file__).main()
diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py
index 7c8ed58e51..e4aaf507cf 100755
--- a/test/functional/p2p_filter.py
+++ b/test/functional/p2p_filter.py
@@ -252,4 +252,4 @@ class FilterTest(BitcoinTestFramework):
if __name__ == '__main__':
- FilterTest().main()
+ FilterTest(__file__).main()
diff --git a/test/functional/p2p_fingerprint.py b/test/functional/p2p_fingerprint.py
index e8bba8555f..f49be62056 100755
--- a/test/functional/p2p_fingerprint.py
+++ b/test/functional/p2p_fingerprint.py
@@ -130,4 +130,4 @@ class P2PFingerprintTest(BitcoinTestFramework):
if __name__ == '__main__':
- P2PFingerprintTest().main()
+ P2PFingerprintTest(__file__).main()
diff --git a/test/functional/p2p_getaddr_caching.py b/test/functional/p2p_getaddr_caching.py
index 60b43c32ae..6626b14ee0 100755
--- a/test/functional/p2p_getaddr_caching.py
+++ b/test/functional/p2p_getaddr_caching.py
@@ -119,4 +119,4 @@ class AddrTest(BitcoinTestFramework):
if __name__ == '__main__':
- AddrTest().main()
+ AddrTest(__file__).main()
diff --git a/test/functional/p2p_getdata.py b/test/functional/p2p_getdata.py
index 89d68d5ba0..6153d08d9b 100755
--- a/test/functional/p2p_getdata.py
+++ b/test/functional/p2p_getdata.py
@@ -46,4 +46,4 @@ class GetdataTest(BitcoinTestFramework):
if __name__ == '__main__':
- GetdataTest().main()
+ GetdataTest(__file__).main()
diff --git a/test/functional/p2p_handshake.py b/test/functional/p2p_handshake.py
index dd19fe9333..18307a2824 100755
--- a/test/functional/p2p_handshake.py
+++ b/test/functional/p2p_handshake.py
@@ -17,6 +17,7 @@ from test_framework.messages import (
NODE_WITNESS,
)
from test_framework.p2p import P2PInterface
+from test_framework.util import p2p_port
# Desirable service flags for outbound non-pruned and pruned peers. Note that
@@ -88,6 +89,12 @@ class P2PHandshakeTest(BitcoinTestFramework):
with node.assert_debug_log([f"feeler connection completed"]):
self.add_outbound_connection(node, "feeler", NODE_NONE, wait_for_disconnect=True)
+ self.log.info("Check that connecting to ourself leads to immediate disconnect")
+ with node.assert_debug_log(["connected to self", "disconnecting"]):
+ node_listen_addr = f"127.0.0.1:{p2p_port(0)}"
+ node.addconnection(node_listen_addr, "outbound-full-relay", self.options.v2transport)
+ self.wait_until(lambda: len(node.getpeerinfo()) == 0)
+
if __name__ == '__main__':
- P2PHandshakeTest().main()
+ P2PHandshakeTest(__file__).main()
diff --git a/test/functional/p2p_headers_sync_with_minchainwork.py b/test/functional/p2p_headers_sync_with_minchainwork.py
index 832fd7e0e9..6e7b4b399e 100755
--- a/test/functional/p2p_headers_sync_with_minchainwork.py
+++ b/test/functional/p2p_headers_sync_with_minchainwork.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2019-2022 The Bitcoin Core developers
+# Copyright (c) 2019-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that we reject low difficulty headers to prevent our block tree from filling up with useless bloat"""
@@ -21,6 +21,8 @@ from test_framework.blocktools import (
from test_framework.util import assert_equal
+import time
+
NODE1_BLOCKS_REQUIRED = 15
NODE2_BLOCKS_REQUIRED = 2047
@@ -48,6 +50,10 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
self.connect_nodes(0, 2)
self.connect_nodes(0, 3)
+ def mocktime_all(self, time):
+ for n in self.nodes:
+ n.setmocktime(time)
+
def test_chains_sync_when_long_enough(self):
self.log.info("Generate blocks on the node with no required chainwork, and verify nodes 1 and 2 have no new headers in their headers tree")
with self.nodes[1].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 14"]):
@@ -149,7 +155,9 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
self.reconnect_all()
+ self.mocktime_all(int(time.time())) # Temporarily hold time to avoid internal timeouts
self.sync_blocks(timeout=300) # Ensure tips eventually agree
+ self.mocktime_all(0)
def run_test(self):
@@ -162,4 +170,4 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
if __name__ == '__main__':
- RejectLowDifficultyHeadersTest().main()
+ RejectLowDifficultyHeadersTest(__file__).main()
diff --git a/test/functional/p2p_i2p_ports.py b/test/functional/p2p_i2p_ports.py
index 20dcb50a57..b1a3c61c7a 100755
--- a/test/functional/p2p_i2p_ports.py
+++ b/test/functional/p2p_i2p_ports.py
@@ -32,4 +32,4 @@ class I2PPorts(BitcoinTestFramework):
if __name__ == '__main__':
- I2PPorts().main()
+ I2PPorts(__file__).main()
diff --git a/test/functional/p2p_i2p_sessions.py b/test/functional/p2p_i2p_sessions.py
index 9e7fdc6e14..67474f6c0e 100755
--- a/test/functional/p2p_i2p_sessions.py
+++ b/test/functional/p2p_i2p_sessions.py
@@ -33,4 +33,4 @@ class I2PSessions(BitcoinTestFramework):
if __name__ == '__main__':
- I2PSessions().main()
+ I2PSessions(__file__).main()
diff --git a/test/functional/p2p_ibd_stalling.py b/test/functional/p2p_ibd_stalling.py
index 830f374d63..fa07873929 100755
--- a/test/functional/p2p_ibd_stalling.py
+++ b/test/functional/p2p_ibd_stalling.py
@@ -73,6 +73,7 @@ class P2PIBDStallingTest(BitcoinTestFramework):
peers = []
self.log.info("Check that a staller does not get disconnected if the 1024 block lookahead buffer is filled")
+ self.mocktime = int(time.time()) + 1
for id in range(NUM_PEERS):
peers.append(node.add_outbound_p2p_connection(P2PStaller(stall_block), p2p_idx=id, connection_type="outbound-full-relay"))
peers[-1].block_store = block_dict
@@ -85,7 +86,7 @@ class P2PIBDStallingTest(BitcoinTestFramework):
self.all_sync_send_with_ping(peers)
# If there was a peer marked for stalling, it would get disconnected
- self.mocktime = int(time.time()) + 3
+ self.mocktime += 3
node.setmocktime(self.mocktime)
self.all_sync_send_with_ping(peers)
assert_equal(node.num_test_p2p_connections(), NUM_PEERS)
@@ -162,4 +163,4 @@ class P2PIBDStallingTest(BitcoinTestFramework):
if __name__ == '__main__':
- P2PIBDStallingTest().main()
+ P2PIBDStallingTest(__file__).main()
diff --git a/test/functional/p2p_ibd_txrelay.py b/test/functional/p2p_ibd_txrelay.py
index b93e39a925..882f5b5c13 100755
--- a/test/functional/p2p_ibd_txrelay.py
+++ b/test/functional/p2p_ibd_txrelay.py
@@ -86,4 +86,4 @@ class P2PIBDTxRelayTest(BitcoinTestFramework):
peer_txer.send_and_ping(msg_tx(tx))
if __name__ == '__main__':
- P2PIBDTxRelayTest().main()
+ P2PIBDTxRelayTest(__file__).main()
diff --git a/test/functional/p2p_initial_headers_sync.py b/test/functional/p2p_initial_headers_sync.py
index bc6e0fb355..5c17b75677 100755
--- a/test/functional/p2p_initial_headers_sync.py
+++ b/test/functional/p2p_initial_headers_sync.py
@@ -96,5 +96,5 @@ class HeadersSyncTest(BitcoinTestFramework):
self.log.info("Success!")
if __name__ == '__main__':
- HeadersSyncTest().main()
+ HeadersSyncTest(__file__).main()
diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py
index 8ec62ae5ee..c4c79dcbb8 100755
--- a/test/functional/p2p_invalid_block.py
+++ b/test/functional/p2p_invalid_block.py
@@ -138,4 +138,4 @@ class InvalidBlockRequestTest(BitcoinTestFramework):
if __name__ == '__main__':
- InvalidBlockRequestTest().main()
+ InvalidBlockRequestTest(__file__).main()
diff --git a/test/functional/p2p_invalid_locator.py b/test/functional/p2p_invalid_locator.py
index 32a23532a2..bde01d5c95 100755
--- a/test/functional/p2p_invalid_locator.py
+++ b/test/functional/p2p_invalid_locator.py
@@ -39,4 +39,4 @@ class InvalidLocatorTest(BitcoinTestFramework):
if __name__ == '__main__':
- InvalidLocatorTest().main()
+ InvalidLocatorTest(__file__).main()
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
index 8e459ba676..507393fb70 100755
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -356,4 +356,4 @@ class InvalidMessagesTest(BitcoinTestFramework):
if __name__ == '__main__':
- InvalidMessagesTest().main()
+ InvalidMessagesTest(__file__).main()
diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py
index 0ae05d4b0b..241aefab24 100755
--- a/test/functional/p2p_invalid_tx.py
+++ b/test/functional/p2p_invalid_tx.py
@@ -224,4 +224,4 @@ class InvalidTxRequestTest(BitcoinTestFramework):
if __name__ == '__main__':
- InvalidTxRequestTest().main()
+ InvalidTxRequestTest(__file__).main()
diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py
index 645488f24d..f800e815d8 100755
--- a/test/functional/p2p_leak.py
+++ b/test/functional/p2p_leak.py
@@ -178,4 +178,4 @@ class P2PLeakTest(BitcoinTestFramework):
if __name__ == '__main__':
- P2PLeakTest().main()
+ P2PLeakTest(__file__).main()
diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py
index f53f98e06d..a2eec49b72 100755
--- a/test/functional/p2p_leak_tx.py
+++ b/test/functional/p2p_leak_tx.py
@@ -104,4 +104,4 @@ class P2PLeakTxTest(BitcoinTestFramework):
if __name__ == '__main__':
- P2PLeakTxTest().main()
+ P2PLeakTxTest(__file__).main()
diff --git a/test/functional/p2p_message_capture.py b/test/functional/p2p_message_capture.py
index 691a0b6409..4c82adc58c 100755
--- a/test/functional/p2p_message_capture.py
+++ b/test/functional/p2p_message_capture.py
@@ -69,4 +69,4 @@ class MessageCaptureTest(BitcoinTestFramework):
if __name__ == '__main__':
- MessageCaptureTest().main()
+ MessageCaptureTest(__file__).main()
diff --git a/test/functional/p2p_mutated_blocks.py b/test/functional/p2p_mutated_blocks.py
index 708b19b1e5..4a790168da 100755
--- a/test/functional/p2p_mutated_blocks.py
+++ b/test/functional/p2p_mutated_blocks.py
@@ -112,4 +112,4 @@ class MutatedBlocksTest(BitcoinTestFramework):
if __name__ == '__main__':
- MutatedBlocksTest().main()
+ MutatedBlocksTest(__file__).main()
diff --git a/test/functional/p2p_net_deadlock.py b/test/functional/p2p_net_deadlock.py
index 1a357b944b..4f1b731e21 100755
--- a/test/functional/p2p_net_deadlock.py
+++ b/test/functional/p2p_net_deadlock.py
@@ -34,4 +34,4 @@ class NetDeadlockTest(BitcoinTestFramework):
if __name__ == '__main__':
- NetDeadlockTest().main()
+ NetDeadlockTest(__file__).main()
diff --git a/test/functional/p2p_nobloomfilter_messages.py b/test/functional/p2p_nobloomfilter_messages.py
index 507a71b2a9..dd2dc9ae69 100755
--- a/test/functional/p2p_nobloomfilter_messages.py
+++ b/test/functional/p2p_nobloomfilter_messages.py
@@ -45,4 +45,4 @@ class P2PNoBloomFilterMessages(BitcoinTestFramework):
if __name__ == '__main__':
- P2PNoBloomFilterMessages().main()
+ P2PNoBloomFilterMessages(__file__).main()
diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py
index 8b63d8ee26..7788be6adb 100755
--- a/test/functional/p2p_node_network_limited.py
+++ b/test/functional/p2p_node_network_limited.py
@@ -102,10 +102,10 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
tip_height = pruned_node.getblockcount()
limit_buffer = 2
# Prevent races by waiting for the tip to arrive first
- self.wait_until(lambda: not try_rpc(-1, "Block not found", full_node.getblock, pruned_node.getbestblockhash()))
+ self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", full_node.getblock, pruned_node.getbestblockhash()))
for height in range(start_height_full_node + 1, tip_height + 1):
if height <= tip_height - (NODE_NETWORK_LIMITED_MIN_BLOCKS - limit_buffer):
- assert_raises_rpc_error(-1, "Block not found on disk", full_node.getblock, pruned_node.getblockhash(height))
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", full_node.getblock, pruned_node.getblockhash(height))
else:
full_node.getblock(pruned_node.getblockhash(height)) # just assert it does not throw an exception
@@ -172,4 +172,4 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
self.test_avoid_requesting_historical_blocks()
if __name__ == '__main__':
- NodeNetworkLimitedTest().main()
+ NodeNetworkLimitedTest(__file__).main()
diff --git a/test/functional/p2p_opportunistic_1p1c.py b/test/functional/p2p_opportunistic_1p1c.py
index aec6e95fbc..4477046c8d 100755
--- a/test/functional/p2p_opportunistic_1p1c.py
+++ b/test/functional/p2p_opportunistic_1p1c.py
@@ -412,4 +412,4 @@ class PackageRelayTest(BitcoinTestFramework):
if __name__ == '__main__':
- PackageRelayTest().main()
+ PackageRelayTest(__file__).main()
diff --git a/test/functional/p2p_orphan_handling.py b/test/functional/p2p_orphan_handling.py
index f525d87cca..22600bf8a4 100755
--- a/test/functional/p2p_orphan_handling.py
+++ b/test/functional/p2p_orphan_handling.py
@@ -585,4 +585,4 @@ class OrphanHandlingTest(BitcoinTestFramework):
if __name__ == '__main__':
- OrphanHandlingTest().main()
+ OrphanHandlingTest(__file__).main()
diff --git a/test/functional/p2p_outbound_eviction.py b/test/functional/p2p_outbound_eviction.py
index 8d89688999..30ac85e32f 100755
--- a/test/functional/p2p_outbound_eviction.py
+++ b/test/functional/p2p_outbound_eviction.py
@@ -250,4 +250,4 @@ class P2POutEvict(BitcoinTestFramework):
if __name__ == '__main__':
- P2POutEvict().main()
+ P2POutEvict(__file__).main()
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index 80a27943fd..c37061c307 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -14,8 +14,10 @@ from test_framework.p2p import P2PDataStore
from test_framework.test_node import ErrorMatch
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
+ append_config,
assert_equal,
p2p_port,
+ tor_port,
)
from test_framework.wallet import MiniWallet
@@ -57,11 +59,14 @@ class P2PPermissionsTests(BitcoinTestFramework):
# by modifying the configuration file.
ip_port = "127.0.0.1:{}".format(p2p_port(1))
self.nodes[1].replace_in_config([("bind=127.0.0.1", "whitebind=bloomfilter,forcerelay@" + ip_port)])
+ # Explicitly bind the tor port to prevent collisions with the default tor port
+ append_config(self.nodes[1].datadir_path, [f"bind=127.0.0.1:{tor_port(self.nodes[1].index)}=onion"])
self.checkpermission(
["-whitelist=noban@127.0.0.1"],
# Check parameter interaction forcerelay should activate relay
["noban", "bloomfilter", "forcerelay", "relay", "download"])
self.nodes[1].replace_in_config([("whitebind=bloomfilter,forcerelay@" + ip_port, "bind=127.0.0.1")])
+ self.nodes[1].replace_in_config([(f"bind=127.0.0.1:{tor_port(self.nodes[1].index)}=onion", "")])
self.checkpermission(
# legacy whitelistrelay should be ignored
@@ -119,14 +124,17 @@ class P2PPermissionsTests(BitcoinTestFramework):
self.log.debug("Check that node[1] will not send an invalid tx to node[0]")
tx.vout[0].nValue += 1
+ # add dust to cause policy rejection but no disconnection
+ tx.vout.append(tx.vout[0])
+ tx.vout[-1].nValue = 0
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 m_recent_rejects filter.
+ # with a mempool transaction. The second time, it'll be in the m_lazy_recent_rejects filter.
p2p_rebroadcast_wallet.send_txs_and_test(
[tx],
self.nodes[1],
success=False,
- reject_reason='{} (wtxid={}) from peer=0 was not accepted: txn-mempool-conflict'.format(txid, tx.getwtxid())
+ reject_reason='{} (wtxid={}) from peer=0 was not accepted: dust'.format(txid, tx.getwtxid())
)
p2p_rebroadcast_wallet.send_txs_and_test(
@@ -147,4 +155,4 @@ class P2PPermissionsTests(BitcoinTestFramework):
if __name__ == '__main__':
- P2PPermissionsTests().main()
+ P2PPermissionsTests(__file__).main()
diff --git a/test/functional/p2p_ping.py b/test/functional/p2p_ping.py
index 3ba30a42b1..5c10ac2a4b 100755
--- a/test/functional/p2p_ping.py
+++ b/test/functional/p2p_ping.py
@@ -117,4 +117,4 @@ class PingPongTest(BitcoinTestFramework):
if __name__ == '__main__':
- PingPongTest().main()
+ PingPongTest(__file__).main()
diff --git a/test/functional/p2p_seednode.py b/test/functional/p2p_seednode.py
new file mode 100755
index 0000000000..6c510a6a0b
--- /dev/null
+++ b/test/functional/p2p_seednode.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019-2021 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 seednode interaction with the AddrMan
+"""
+import random
+import time
+
+from test_framework.test_framework import BitcoinTestFramework
+
+ADD_NEXT_SEEDNODE = 10
+
+
+class P2PSeedNodes(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.disable_autoconnect = False
+
+ def test_no_seednode(self):
+ # Check that if no seednode is provided, the node proceeds as usual (without waiting)
+ with self.nodes[0].assert_debug_log(expected_msgs=[], unexpected_msgs=["Empty addrman, adding seednode", f"Couldn't connect to peers from addrman after {ADD_NEXT_SEEDNODE} seconds. Adding seednode"], timeout=ADD_NEXT_SEEDNODE):
+ self.restart_node(0)
+
+ def test_seednode_empty_addrman(self):
+ seed_node = "0.0.0.1"
+ # Check that the seednode is added to m_addr_fetches on bootstrap on an empty addrman
+ with self.nodes[0].assert_debug_log(expected_msgs=[f"Empty addrman, adding seednode ({seed_node}) to addrfetch"], timeout=ADD_NEXT_SEEDNODE):
+ self.restart_node(0, extra_args=[f'-seednode={seed_node}'])
+
+ def test_seednode_addrman_unreachable_peers(self):
+ seed_node = "0.0.0.2"
+ node = self.nodes[0]
+ # Fill the addrman with unreachable nodes
+ for i in range(10):
+ ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}"
+ port = 8333 + i
+ node.addpeeraddress(ip, port)
+
+ # Restart the node so seednode is processed again
+ with node.assert_debug_log(expected_msgs=[f"Couldn't connect to peers from addrman after {ADD_NEXT_SEEDNODE} seconds. Adding seednode ({seed_node}) to addrfetch"], unexpected_msgs=["Empty addrman, adding seednode"], timeout=ADD_NEXT_SEEDNODE * 1.5):
+ self.restart_node(0, extra_args=[f'-seednode={seed_node}'])
+ node.setmocktime(int(time.time()) + ADD_NEXT_SEEDNODE + 1)
+
+ def run_test(self):
+ self.test_no_seednode()
+ self.test_seednode_empty_addrman()
+ self.test_seednode_addrman_unreachable_peers()
+
+
+if __name__ == '__main__':
+ P2PSeedNodes(__file__).main()
+
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index d20cf41a72..9be53d2ab8 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -2067,4 +2067,4 @@ class SegWitTest(BitcoinTestFramework):
if __name__ == '__main__':
- SegWitTest().main()
+ SegWitTest(__file__).main()
diff --git a/test/functional/p2p_sendheaders.py b/test/functional/p2p_sendheaders.py
index 5c463267a1..db706556d8 100755
--- a/test/functional/p2p_sendheaders.py
+++ b/test/functional/p2p_sendheaders.py
@@ -568,4 +568,4 @@ class SendHeadersTest(BitcoinTestFramework):
assert "getdata" not in inv_node.last_message
if __name__ == '__main__':
- SendHeadersTest().main()
+ SendHeadersTest(__file__).main()
diff --git a/test/functional/p2p_sendtxrcncl.py b/test/functional/p2p_sendtxrcncl.py
index 8f5e6c0387..2c7216b5ca 100755
--- a/test/functional/p2p_sendtxrcncl.py
+++ b/test/functional/p2p_sendtxrcncl.py
@@ -232,4 +232,4 @@ class SendTxRcnclTest(BitcoinTestFramework):
if __name__ == '__main__':
- SendTxRcnclTest().main()
+ SendTxRcnclTest(__file__).main()
diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py
index 80d7b6e9ae..1fd78e163b 100755
--- a/test/functional/p2p_timeouts.py
+++ b/test/functional/p2p_timeouts.py
@@ -109,4 +109,4 @@ class TimeoutsTest(BitcoinTestFramework):
if __name__ == '__main__':
- TimeoutsTest().main()
+ TimeoutsTest(__file__).main()
diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py
index 0af6b1d2c9..c69d6ff405 100755
--- a/test/functional/p2p_tx_download.py
+++ b/test/functional/p2p_tx_download.py
@@ -156,9 +156,9 @@ class TxDownloadTest(BitcoinTestFramework):
# 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_expiry, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1)
+ _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
+ 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)
self.restart_node(0) # reset mocktime
@@ -250,7 +250,7 @@ class TxDownloadTest(BitcoinTestFramework):
def test_rejects_filter_reset(self):
self.log.info('Check that rejected tx is not requested again')
node = self.nodes[0]
- fill_mempool(self, node)
+ fill_mempool(self, node, tx_sync_fun=self.no_op)
self.wallet.rescan_utxos()
mempoolminfee = node.getmempoolinfo()['mempoolminfee']
peer = node.add_p2p_connection(TestP2PConn())
@@ -306,4 +306,4 @@ class TxDownloadTest(BitcoinTestFramework):
if __name__ == '__main__':
- TxDownloadTest().main()
+ TxDownloadTest(__file__).main()
diff --git a/test/functional/p2p_tx_privacy.py b/test/functional/p2p_tx_privacy.py
index e674f6c3eb..afe9df8a0f 100755
--- a/test/functional/p2p_tx_privacy.py
+++ b/test/functional/p2p_tx_privacy.py
@@ -74,4 +74,4 @@ class TxPrivacyTest(BitcoinTestFramework):
spy.wait_for_inv_match(CInv(MSG_WTX, tx2.calc_sha256(True)))
if __name__ == '__main__':
- TxPrivacyTest().main()
+ TxPrivacyTest(__file__).main()
diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py
index 776eaf5255..1430131a97 100755
--- a/test/functional/p2p_unrequested_blocks.py
+++ b/test/functional/p2p_unrequested_blocks.py
@@ -119,7 +119,7 @@ class AcceptBlockTest(BitcoinTestFramework):
assert_equal(x['status'], "headers-only")
tip_entry_found = True
assert tip_entry_found
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_h1f.hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_h1f.hash)
# 4. Send another two block that build on the fork.
block_h2f = create_block(block_h1f.sha256, create_coinbase(2), block_time)
@@ -191,7 +191,7 @@ class AcceptBlockTest(BitcoinTestFramework):
# Blocks 1-287 should be accepted, block 288 should be ignored because it's too far ahead
for x in all_blocks[:-1]:
self.nodes[0].getblock(x.hash)
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[-1].hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[-1].hash)
# 5. Test handling of unrequested block on the node that didn't process
# Should still not be processed (even though it has a child that has more
@@ -230,7 +230,7 @@ class AcceptBlockTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getblockcount(), 290)
self.nodes[0].getblock(all_blocks[286].hash)
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash)
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[287].hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[287].hash)
self.log.info("Successfully reorged to longer chain")
# 8. Create a chain which is invalid at a height longer than the
@@ -260,7 +260,7 @@ class AcceptBlockTest(BitcoinTestFramework):
assert_equal(x['status'], "headers-only")
tip_entry_found = True
assert tip_entry_found
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_292.hash)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_292.hash)
test_node.send_message(msg_block(block_289f))
test_node.send_and_ping(msg_block(block_290f))
@@ -296,4 +296,4 @@ class AcceptBlockTest(BitcoinTestFramework):
self.log.info("Successfully synced nodes 1 and 0")
if __name__ == '__main__':
- AcceptBlockTest().main()
+ AcceptBlockTest(__file__).main()
diff --git a/test/functional/p2p_v2_earlykeyresponse.py b/test/functional/p2p_v2_earlykeyresponse.py
deleted file mode 100755
index 32d2e1148a..0000000000
--- a/test/functional/p2p_v2_earlykeyresponse.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2022 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-import random
-
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.crypto.ellswift import ellswift_create
-from test_framework.p2p import P2PInterface
-from test_framework.v2_p2p import EncryptedP2PState
-
-
-class TestEncryptedP2PState(EncryptedP2PState):
- """ Modify v2 P2P protocol functions for testing that "The responder waits until one byte is received which does
- not match the 16 bytes consisting of the network magic followed by "version\x00\x00\x00\x00\x00"." (see BIP 324)
-
- - if `send_net_magic` is True, send first 4 bytes of ellswift (match network magic) else send remaining 60 bytes
- - `can_data_be_received` is a variable used to assert if data is received on recvbuf.
- - v2 TestNode shouldn't respond back if we send V1_PREFIX and data shouldn't be received on recvbuf.
- This state is represented using `can_data_be_received` = False.
- - v2 TestNode responds back when mismatch from V1_PREFIX happens and data can be received on recvbuf.
- This state is represented using `can_data_be_received` = True.
- """
-
- def __init__(self):
- super().__init__(initiating=True, net='regtest')
- self.send_net_magic = True
- self.can_data_be_received = False
-
- def initiate_v2_handshake(self, garbage_len=random.randrange(4096)):
- """Initiator begins the v2 handshake by sending its ellswift bytes and garbage.
- Here, the 64 bytes ellswift is assumed to have it's 4 bytes match network magic bytes. It is sent in 2 phases:
- 1. when `send_network_magic` = True, send first 4 bytes of ellswift (matches network magic bytes)
- 2. when `send_network_magic` = False, send remaining 60 bytes of ellswift
- """
- if self.send_net_magic:
- self.privkey_ours, self.ellswift_ours = ellswift_create()
- self.sent_garbage = random.randbytes(garbage_len)
- self.send_net_magic = False
- return b"\xfa\xbf\xb5\xda"
- else:
- self.can_data_be_received = True
- return self.ellswift_ours[4:] + self.sent_garbage
-
-
-class PeerEarlyKey(P2PInterface):
- """Custom implementation of P2PInterface which uses modified v2 P2P protocol functions for testing purposes."""
- def __init__(self):
- super().__init__()
- self.v2_state = None
- self.connection_opened = False
-
- def connection_made(self, transport):
- """64 bytes ellswift is sent in 2 parts during `initial_v2_handshake()`"""
- self.v2_state = TestEncryptedP2PState()
- super().connection_made(transport)
-
- def data_received(self, t):
- # check that data can be received on recvbuf only when mismatch from V1_PREFIX happens (send_net_magic = False)
- assert self.v2_state.can_data_be_received and not self.v2_state.send_net_magic
-
- def on_open(self):
- self.connection_opened = True
-
-class P2PEarlyKey(BitcoinTestFramework):
- def set_test_params(self):
- self.num_nodes = 1
- self.extra_args = [["-v2transport=1", "-peertimeout=3"]]
-
- def run_test(self):
- self.log.info('Sending ellswift bytes in parts to ensure that response from responder is received only when')
- self.log.info('ellswift bytes have a mismatch from the 16 bytes(network magic followed by "version\\x00\\x00\\x00\\x00\\x00")')
- node0 = self.nodes[0]
- self.log.info('Sending first 4 bytes of ellswift which match network magic')
- self.log.info('If a response is received, assertion failure would happen in our custom data_received() function')
- # send happens in `initiate_v2_handshake()` in `connection_made()`
- peer1 = node0.add_p2p_connection(PeerEarlyKey(), wait_for_verack=False, send_version=False, supports_v2_p2p=True, wait_for_v2_handshake=False)
- self.wait_until(lambda: peer1.connection_opened)
- self.log.info('Sending remaining ellswift and garbage which are different from V1_PREFIX. Since a response is')
- self.log.info('expected now, our custom data_received() function wouldn\'t result in assertion failure')
- ellswift_and_garbage_data = peer1.v2_state.initiate_v2_handshake()
- peer1.send_raw_message(ellswift_and_garbage_data)
- peer1.wait_for_disconnect(timeout=5)
- self.log.info('successful disconnection when MITM happens in the key exchange phase')
-
-
-if __name__ == '__main__':
- P2PEarlyKey().main()
diff --git a/test/functional/p2p_v2_encrypted.py b/test/functional/p2p_v2_encrypted.py
index 05755dece0..3e8ce09d24 100755
--- a/test/functional/p2p_v2_encrypted.py
+++ b/test/functional/p2p_v2_encrypted.py
@@ -131,4 +131,4 @@ class P2PEncrypted(BitcoinTestFramework):
if __name__ == '__main__':
- P2PEncrypted().main()
+ P2PEncrypted(__file__).main()
diff --git a/test/functional/p2p_v2_misbehaving.py b/test/functional/p2p_v2_misbehaving.py
new file mode 100755
index 0000000000..0af96a4f8c
--- /dev/null
+++ b/test/functional/p2p_v2_misbehaving.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import random
+import time
+from enum import Enum
+
+from test_framework.messages import MAGIC_BYTES
+from test_framework.p2p import P2PInterface
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import random_bitflip
+from test_framework.v2_p2p import (
+ EncryptedP2PState,
+ MAX_GARBAGE_LEN,
+)
+
+
+class TestType(Enum):
+ """ Scenarios to be tested:
+
+ 1. EARLY_KEY_RESPONSE - The responder needs to wait until one byte is received which does not match the 16 bytes
+ consisting of network magic followed by "version\x00\x00\x00\x00\x00" before sending out its ellswift + garbage bytes
+ 2. EXCESS_GARBAGE - Disconnection happens when > MAX_GARBAGE_LEN bytes garbage is sent
+ 3. WRONG_GARBAGE_TERMINATOR - Disconnection happens when incorrect garbage terminator is sent
+ 4. WRONG_GARBAGE - Disconnection happens when garbage bytes that is sent is different from what the peer receives
+ 5. SEND_NO_AAD - Disconnection happens when AAD of first encrypted packet after the garbage terminator is not filled
+ 6. SEND_NON_EMPTY_VERSION_PACKET - non-empty version packet is simply ignored
+ """
+ EARLY_KEY_RESPONSE = 0
+ EXCESS_GARBAGE = 1
+ WRONG_GARBAGE_TERMINATOR = 2
+ WRONG_GARBAGE = 3
+ SEND_NO_AAD = 4
+ SEND_NON_EMPTY_VERSION_PACKET = 5
+
+
+class EarlyKeyResponseState(EncryptedP2PState):
+ """ Modify v2 P2P protocol functions for testing EARLY_KEY_RESPONSE scenario"""
+ def __init__(self, initiating, net):
+ super().__init__(initiating=initiating, net=net)
+ self.can_data_be_received = False # variable used to assert if data is received on recvbuf.
+
+ def initiate_v2_handshake(self):
+ """Send ellswift and garbage bytes in 2 parts when TestType = (EARLY_KEY_RESPONSE)"""
+ self.generate_keypair_and_garbage()
+ return b""
+
+
+class ExcessGarbageState(EncryptedP2PState):
+ """Generate > MAX_GARBAGE_LEN garbage bytes"""
+ def generate_keypair_and_garbage(self):
+ garbage_len = MAX_GARBAGE_LEN + random.randrange(1, MAX_GARBAGE_LEN + 1)
+ return super().generate_keypair_and_garbage(garbage_len)
+
+
+class WrongGarbageTerminatorState(EncryptedP2PState):
+ """Add option for sending wrong garbage terminator"""
+ def generate_keypair_and_garbage(self):
+ garbage_len = random.randrange(MAX_GARBAGE_LEN//2)
+ return super().generate_keypair_and_garbage(garbage_len)
+
+ def complete_handshake(self, response):
+ length, handshake_bytes = super().complete_handshake(response)
+ # first 16 bytes returned by complete_handshake() is the garbage terminator
+ wrong_garbage_terminator = random_bitflip(handshake_bytes[:16])
+ return length, wrong_garbage_terminator + handshake_bytes[16:]
+
+
+class WrongGarbageState(EncryptedP2PState):
+ """Generate tampered garbage bytes"""
+ def generate_keypair_and_garbage(self):
+ garbage_len = random.randrange(1, MAX_GARBAGE_LEN)
+ ellswift_garbage_bytes = super().generate_keypair_and_garbage(garbage_len)
+ # assume that garbage bytes sent to TestNode were tampered with
+ return ellswift_garbage_bytes[:64] + random_bitflip(ellswift_garbage_bytes[64:])
+
+
+class NoAADState(EncryptedP2PState):
+ """Add option for not filling first encrypted packet after garbage terminator with AAD"""
+ def generate_keypair_and_garbage(self):
+ garbage_len = random.randrange(1, MAX_GARBAGE_LEN)
+ return super().generate_keypair_and_garbage(garbage_len)
+
+ def complete_handshake(self, response):
+ self.sent_garbage = b'' # do not authenticate the garbage which is sent
+ return super().complete_handshake(response)
+
+
+class NonEmptyVersionPacketState(EncryptedP2PState):
+ """"Add option for sending non-empty transport version packet."""
+ def complete_handshake(self, response):
+ self.transport_version = random.randbytes(5)
+ return super().complete_handshake(response)
+
+
+class MisbehavingV2Peer(P2PInterface):
+ """Custom implementation of P2PInterface which uses modified v2 P2P protocol functions for testing purposes."""
+ def __init__(self, test_type):
+ super().__init__()
+ self.test_type = test_type
+
+ def connection_made(self, transport):
+ if self.test_type == TestType.EARLY_KEY_RESPONSE:
+ self.v2_state = EarlyKeyResponseState(initiating=True, net='regtest')
+ elif self.test_type == TestType.EXCESS_GARBAGE:
+ self.v2_state = ExcessGarbageState(initiating=True, net='regtest')
+ elif self.test_type == TestType.WRONG_GARBAGE_TERMINATOR:
+ self.v2_state = WrongGarbageTerminatorState(initiating=True, net='regtest')
+ elif self.test_type == TestType.WRONG_GARBAGE:
+ self.v2_state = WrongGarbageState(initiating=True, net='regtest')
+ elif self.test_type == TestType.SEND_NO_AAD:
+ self.v2_state = NoAADState(initiating=True, net='regtest')
+ elif TestType.SEND_NON_EMPTY_VERSION_PACKET:
+ self.v2_state = NonEmptyVersionPacketState(initiating=True, net='regtest')
+ super().connection_made(transport)
+
+ def data_received(self, t):
+ if self.test_type == TestType.EARLY_KEY_RESPONSE:
+ # check that data can be received on recvbuf only when mismatch from V1_PREFIX happens
+ assert self.v2_state.can_data_be_received
+ else:
+ super().data_received(t)
+
+
+class EncryptedP2PMisbehaving(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.extra_args = [["-v2transport=1", "-peertimeout=3"]]
+
+ def run_test(self):
+ self.test_earlykeyresponse()
+ self.test_v2disconnection()
+
+ def test_earlykeyresponse(self):
+ self.log.info('Sending ellswift bytes in parts to ensure that response from responder is received only when')
+ self.log.info('ellswift bytes have a mismatch from the 16 bytes(network magic followed by "version\\x00\\x00\\x00\\x00\\x00")')
+ node0 = self.nodes[0]
+ node0.setmocktime(int(time.time()))
+ self.log.info('Sending first 4 bytes of ellswift which match network magic')
+ self.log.info('If a response is received, assertion failure would happen in our custom data_received() function')
+ with node0.wait_for_new_peer():
+ peer1 = node0.add_p2p_connection(MisbehavingV2Peer(TestType.EARLY_KEY_RESPONSE), wait_for_verack=False, send_version=False, supports_v2_p2p=True, wait_for_v2_handshake=False)
+ peer1.send_raw_message(MAGIC_BYTES['regtest'])
+ self.log.info('Sending remaining ellswift and garbage which are different from V1_PREFIX. Since a response is')
+ self.log.info('expected now, our custom data_received() function wouldn\'t result in assertion failure')
+ peer1.v2_state.can_data_be_received = True
+ self.wait_until(lambda: peer1.v2_state.ellswift_ours)
+ peer1.send_raw_message(peer1.v2_state.ellswift_ours[4:] + peer1.v2_state.sent_garbage)
+ # Ensure that the bytes sent after 4 bytes network magic are actually received.
+ self.wait_until(lambda: node0.getpeerinfo()[-1]["bytesrecv"] > 4)
+ self.wait_until(lambda: node0.getpeerinfo()[-1]["bytessent"] > 0)
+ with node0.assert_debug_log(['V2 handshake timeout peer=0']):
+ node0.bumpmocktime(4) # `InactivityCheck()` triggers now
+ peer1.wait_for_disconnect(timeout=1)
+ self.log.info('successful disconnection since modified ellswift was sent as response')
+
+ def test_v2disconnection(self):
+ # test v2 disconnection scenarios
+ node0 = self.nodes[0]
+ expected_debug_message = [
+ [], # EARLY_KEY_RESPONSE
+ ["V2 transport error: missing garbage terminator, peer=1"], # EXCESS_GARBAGE
+ ["V2 handshake timeout peer=3"], # WRONG_GARBAGE_TERMINATOR
+ ["V2 transport error: packet decryption failure"], # WRONG_GARBAGE
+ ["V2 transport error: packet decryption failure"], # SEND_NO_AAD
+ [], # SEND_NON_EMPTY_VERSION_PACKET
+ ]
+ for test_type in TestType:
+ if test_type == TestType.EARLY_KEY_RESPONSE:
+ continue
+ elif test_type == TestType.SEND_NON_EMPTY_VERSION_PACKET:
+ node0.add_p2p_connection(MisbehavingV2Peer(test_type), wait_for_verack=True, send_version=True, supports_v2_p2p=True)
+ self.log.info(f"No disconnection for {test_type.name}")
+ else:
+ with node0.assert_debug_log(expected_debug_message[test_type.value], timeout=5):
+ node0.setmocktime(int(time.time()))
+ peer1 = node0.add_p2p_connection(MisbehavingV2Peer(test_type), wait_for_verack=False, send_version=False, supports_v2_p2p=True, expect_success=False)
+ # Make a passing connection for more robust disconnection checking.
+ peer2 = node0.add_p2p_connection(P2PInterface())
+ assert peer2.is_connected
+ node0.bumpmocktime(4) # `InactivityCheck()` triggers now
+ peer1.wait_for_disconnect()
+ self.log.info(f"Expected disconnection for {test_type.name}")
+
+
+if __name__ == '__main__':
+ EncryptedP2PMisbehaving(__file__).main()
diff --git a/test/functional/p2p_v2_transport.py b/test/functional/p2p_v2_transport.py
index fe2449124d..94c91906e6 100755
--- a/test/functional/p2p_v2_transport.py
+++ b/test/functional/p2p_v2_transport.py
@@ -168,4 +168,4 @@ class V2TransportTest(BitcoinTestFramework):
if __name__ == '__main__':
- V2TransportTest().main()
+ V2TransportTest(__file__).main()
diff --git a/test/functional/rpc_bind.py b/test/functional/rpc_bind.py
index 3106419e5c..69afd45b9a 100755
--- a/test/functional/rpc_bind.py
+++ b/test/functional/rpc_bind.py
@@ -45,6 +45,19 @@ class RPCBindTest(BitcoinTestFramework):
assert_equal(set(get_bind_addrs(pid)), set(expected))
self.stop_nodes()
+ def run_invalid_bind_test(self, allow_ips, addresses):
+ '''
+ Attempt to start a node with requested rpcallowip and rpcbind
+ parameters, expecting that the node will fail.
+ '''
+ self.log.info(f'Invalid bind test for {addresses}')
+ base_args = ['-disablewallet', '-nolisten']
+ if allow_ips:
+ base_args += ['-rpcallowip=' + x for x in allow_ips]
+ init_error = 'Error: Invalid port specified in -rpcbind: '
+ for addr in addresses:
+ self.nodes[0].assert_start_raises_init_error(base_args + [f'-rpcbind={addr}'], init_error + f"'{addr}'")
+
def run_allowip_test(self, allow_ips, rpchost, rpcport):
'''
Start a node with rpcallow IP, and request getnetworkinfo
@@ -84,6 +97,10 @@ class RPCBindTest(BitcoinTestFramework):
if not self.options.run_nonloopback:
self._run_loopback_tests()
+ if self.options.run_ipv4:
+ self.run_invalid_bind_test(['127.0.0.1'], ['127.0.0.1:notaport', '127.0.0.1:-18443', '127.0.0.1:0', '127.0.0.1:65536'])
+ if self.options.run_ipv6:
+ self.run_invalid_bind_test(['[::1]'], ['[::1]:notaport', '[::1]:-18443', '[::1]:0', '[::1]:65536'])
if not self.options.run_ipv4 and not self.options.run_ipv6:
self._run_nonloopback_tests()
@@ -124,4 +141,4 @@ class RPCBindTest(BitcoinTestFramework):
assert_raises_rpc_error(-342, "non-JSON HTTP response with '403 Forbidden' from server", self.run_allowip_test, ['1.1.1.1'], self.non_loopback_ip, self.defaultport)
if __name__ == '__main__':
- RPCBindTest().main()
+ RPCBindTest(__file__).main()
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 9b7743cafa..f02e6914ef 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -32,14 +32,16 @@ from test_framework.blocktools import (
TIME_GENESIS_BLOCK,
create_block,
create_coinbase,
+ create_tx_with_script,
)
from test_framework.messages import (
CBlockHeader,
+ COIN,
from_hex,
msg_block,
)
from test_framework.p2p import P2PInterface
-from test_framework.script import hash256
+from test_framework.script import hash256, OP_TRUE
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -58,7 +60,7 @@ TIME_RANGE_STEP = 600 # ten-minute steps
TIME_RANGE_MTP = TIME_GENESIS_BLOCK + (HEIGHT - 6) * TIME_RANGE_STEP
TIME_RANGE_TIP = TIME_GENESIS_BLOCK + (HEIGHT - 1) * TIME_RANGE_STEP
TIME_RANGE_END = TIME_GENESIS_BLOCK + HEIGHT * TIME_RANGE_STEP
-DIFFICULTY_ADJUSTMENT_INTERVAL = 2016
+DIFFICULTY_ADJUSTMENT_INTERVAL = 144
class BlockchainTest(BitcoinTestFramework):
@@ -88,6 +90,7 @@ class BlockchainTest(BitcoinTestFramework):
self._test_getdifficulty()
self._test_getnetworkhashps()
self._test_stopatheight()
+ self._test_waitforblock() # also tests waitfornewblock
self._test_waitforblockheight()
self._test_getblock()
self._test_getdeploymentinfo()
@@ -505,6 +508,38 @@ class BlockchainTest(BitcoinTestFramework):
self.start_node(0)
assert_equal(self.nodes[0].getblockcount(), HEIGHT + 7)
+ def _test_waitforblock(self):
+ self.log.info("Test waitforblock and waitfornewblock")
+ node = self.nodes[0]
+
+ current_height = node.getblock(node.getbestblockhash())['height']
+ current_hash = node.getblock(node.getbestblockhash())['hash']
+
+ self.log.debug("Roll the chain back a few blocks and then reconsider it")
+ rollback_height = current_height - 100
+ rollback_hash = node.getblockhash(rollback_height)
+ rollback_header = node.getblockheader(rollback_hash)
+
+ node.invalidateblock(rollback_hash)
+ assert_equal(node.getblockcount(), rollback_height - 1)
+
+ self.log.debug("waitforblock should return the same block after its timeout")
+ assert_equal(node.waitforblock(blockhash=current_hash, timeout=1)['hash'], rollback_header['previousblockhash'])
+
+ node.reconsiderblock(rollback_hash)
+ # The chain has probably already been restored by the time reconsiderblock returns,
+ # but poll anyway.
+ self.wait_until(lambda: node.waitforblock(blockhash=current_hash, timeout=100)['hash'] == current_hash)
+
+ # roll back again
+ node.invalidateblock(rollback_hash)
+ assert_equal(node.getblockcount(), rollback_height - 1)
+
+ node.reconsiderblock(rollback_hash)
+ # The chain has probably already been restored by the time reconsiderblock returns,
+ # but poll anyway.
+ self.wait_until(lambda: node.waitfornewblock(timeout=100)['hash'] == current_hash)
+
def _test_waitforblockheight(self):
self.log.info("Test waitforblockheight")
node = self.nodes[0]
@@ -556,12 +591,12 @@ class BlockchainTest(BitcoinTestFramework):
block = node.getblock(blockhash, verbosity)
assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex())
- def assert_fee_not_in_block(verbosity):
- block = node.getblock(blockhash, verbosity)
+ def assert_fee_not_in_block(hash, verbosity):
+ block = node.getblock(hash, verbosity)
assert 'fee' not in block['tx'][1]
- def assert_fee_in_block(verbosity):
- block = node.getblock(blockhash, verbosity)
+ def assert_fee_in_block(hash, verbosity):
+ block = node.getblock(hash, verbosity)
tx = block['tx'][1]
assert 'fee' in tx
assert_equal(tx['fee'], tx['vsize'] * fee_per_byte)
@@ -580,8 +615,8 @@ class BlockchainTest(BitcoinTestFramework):
total_vout += vout["value"]
assert_equal(total_vin, total_vout + tx["fee"])
- def assert_vin_does_not_contain_prevout(verbosity):
- block = node.getblock(blockhash, verbosity)
+ def assert_vin_does_not_contain_prevout(hash, verbosity):
+ block = node.getblock(hash, verbosity)
tx = block["tx"][1]
if isinstance(tx, str):
# In verbosity level 1, only the transaction hashes are written
@@ -595,16 +630,16 @@ class BlockchainTest(BitcoinTestFramework):
assert_hexblock_hashes(False)
self.log.info("Test that getblock with verbosity 1 doesn't include fee")
- assert_fee_not_in_block(1)
- assert_fee_not_in_block(True)
+ assert_fee_not_in_block(blockhash, 1)
+ assert_fee_not_in_block(blockhash, True)
self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee')
- assert_fee_in_block(2)
- assert_fee_in_block(3)
+ assert_fee_in_block(blockhash, 2)
+ assert_fee_in_block(blockhash, 3)
self.log.info("Test that getblock with verbosity 1 and 2 does not include prevout")
- assert_vin_does_not_contain_prevout(1)
- assert_vin_does_not_contain_prevout(2)
+ assert_vin_does_not_contain_prevout(blockhash, 1)
+ assert_vin_does_not_contain_prevout(blockhash, 2)
self.log.info("Test that getblock with verbosity 3 includes prevout")
assert_vin_contains_prevout(3)
@@ -612,7 +647,7 @@ class BlockchainTest(BitcoinTestFramework):
self.log.info("Test getblock with invalid verbosity type returns proper error message")
assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2")
- self.log.info("Test that getblock with verbosity 2 and 3 still works with pruned Undo data")
+ self.log.info("Test that getblock doesn't work with deleted Undo data")
def move_block_file(old, new):
old_path = self.nodes[0].blocks_path / old
@@ -622,10 +657,8 @@ class BlockchainTest(BitcoinTestFramework):
# Move instead of deleting so we can restore chain state afterwards
move_block_file('rev00000.dat', 'rev_wrong')
- assert_fee_not_in_block(2)
- assert_fee_not_in_block(3)
- assert_vin_does_not_contain_prevout(2)
- assert_vin_does_not_contain_prevout(3)
+ assert_raises_rpc_error(-32603, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.", lambda: node.getblock(blockhash, 2))
+ assert_raises_rpc_error(-32603, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.", lambda: node.getblock(blockhash, 3))
# Restore chain state
move_block_file('rev_wrong', 'rev00000.dat')
@@ -633,6 +666,31 @@ class BlockchainTest(BitcoinTestFramework):
assert 'previousblockhash' not in node.getblock(node.getblockhash(0))
assert 'nextblockhash' not in node.getblock(node.getbestblockhash())
+ self.log.info("Test getblock when only header is known")
+ current_height = node.getblock(node.getbestblockhash())['height']
+ block_time = node.getblock(node.getbestblockhash())['time'] + 1
+ block = create_block(int(blockhash, 16), create_coinbase(current_height + 1, nValue=100), block_time)
+ block.solve()
+ node.submitheader(block.serialize().hex())
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: node.getblock(block.hash))
+
+ self.log.info("Test getblock when block data is available but undo data isn't")
+ # Submits a block building on the header-only block, so it can't be connected and has no undo data
+ tx = create_tx_with_script(block.vtx[0], 0, script_sig=bytes([OP_TRUE]), amount=50 * COIN)
+ block_noundo = create_block(block.sha256, create_coinbase(current_height + 2, nValue=100), block_time + 1, txlist=[tx])
+ block_noundo.solve()
+ node.submitblock(block_noundo.serialize().hex())
+
+ assert_fee_not_in_block(block_noundo.hash, 2)
+ assert_fee_not_in_block(block_noundo.hash, 3)
+ assert_vin_does_not_contain_prevout(block_noundo.hash, 2)
+ assert_vin_does_not_contain_prevout(block_noundo.hash, 3)
+
+ self.log.info("Test getblock when block is missing")
+ move_block_file('blk00000.dat', 'blk00000.dat.bak')
+ assert_raises_rpc_error(-1, "Block not found on disk", node.getblock, blockhash)
+ move_block_file('blk00000.dat.bak', 'blk00000.dat')
+
if __name__ == '__main__':
- BlockchainTest().main()
+ BlockchainTest(__file__).main()
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 37656341d2..d95820bbf8 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -47,7 +47,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
return node.get_wallet_rpc(wallet_name)
def run_test(self):
- node0, node1, node2 = self.nodes
+ node0, node1, _node2 = self.nodes
self.wallet = MiniWallet(test_node=node0)
if self.is_wallet_compiled():
@@ -122,7 +122,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Unsupported multisig script size for legacy wallet. Upgrade to descriptors to overcome this limitation for p2sh-segwit or bech32 scripts", wallet_multi.addmultisigaddress, 16, pubkeys, '', 'bech32')
def do_multisig(self, nkeys, nsigs, output_type, wallet_multi):
- node0, node1, node2 = self.nodes
+ node0, _node1, node2 = self.nodes
pub_keys = self.pub[0: nkeys]
priv_keys = self.priv[0: nkeys]
@@ -257,4 +257,4 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
if __name__ == '__main__':
- RpcCreateMultiSigTest().main()
+ RpcCreateMultiSigTest(__file__).main()
diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py
index f37e61ab50..4f127fa8b7 100755
--- a/test/functional/rpc_decodescript.py
+++ b/test/functional/rpc_decodescript.py
@@ -187,6 +187,16 @@ class DecodeScriptTest(BitcoinTestFramework):
assert_equal('1 ' + xonly_public_key, rpc_result['asm'])
assert 'segwit' not in rpc_result
+ self.log.info("- P2A (anchor)")
+ # 1 <4e73>
+ witprog_hex = '4e73'
+ rpc_result = self.nodes[0].decodescript('5102' + witprog_hex)
+ assert_equal('anchor', rpc_result['type'])
+ # in the disassembly, the witness program is shown as single decimal due to its small size
+ witprog_as_decimal = int.from_bytes(bytes.fromhex(witprog_hex), 'little')
+ assert_equal(f'1 {witprog_as_decimal}', rpc_result['asm'])
+ assert_equal('bcrt1pfeesnyr2tx', rpc_result['address'])
+
def decoderawtransaction_asm_sighashtype(self):
"""Test decoding scripts via RPC command "decoderawtransaction".
@@ -289,4 +299,4 @@ class DecodeScriptTest(BitcoinTestFramework):
self.decodescript_miniscript()
if __name__ == '__main__':
- DecodeScriptTest().main()
+ DecodeScriptTest(__file__).main()
diff --git a/test/functional/rpc_deprecated.py b/test/functional/rpc_deprecated.py
index 15c77ed856..4a415d57f5 100755
--- a/test/functional/rpc_deprecated.py
+++ b/test/functional/rpc_deprecated.py
@@ -26,4 +26,4 @@ class DeprecatedRpcTest(BitcoinTestFramework):
self.log.info("No tested deprecated RPC methods")
if __name__ == '__main__':
- DeprecatedRpcTest().main()
+ DeprecatedRpcTest(__file__).main()
diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py
index 64994d6bb3..32fdfa350a 100755
--- a/test/functional/rpc_deriveaddresses.py
+++ b/test/functional/rpc_deriveaddresses.py
@@ -29,6 +29,9 @@ class DeriveaddressesTest(BitcoinTestFramework):
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, [1, 2]), ["bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"])
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 2), [address, "bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"])
+ ranged_descriptor = descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/<0;1>/*)")
+ assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, [1, 2]), [["bcrt1q7c8mdmdktrzs8xgpjmqw90tjn65j5a3yj04m3n", "bcrt1qs6n37uzu0v0qfzf0r0csm0dwa7prc0v5uavgy0"], ["bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"]])
+
assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"), [0, 2])
assert_raises_rpc_error(-8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"))
@@ -61,4 +64,4 @@ class DeriveaddressesTest(BitcoinTestFramework):
assert_raises_rpc_error(-5, "Descriptor does not have a corresponding address", self.nodes[0].deriveaddresses, bare_multisig_descriptor)
if __name__ == '__main__':
- DeriveaddressesTest().main()
+ DeriveaddressesTest(__file__).main()
diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py
index c92c8da357..ad05060210 100755
--- a/test/functional/rpc_dumptxoutset.py
+++ b/test/functional/rpc_dumptxoutset.py
@@ -19,6 +19,17 @@ class DumptxoutsetTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
+ def check_expected_network(self, node, active):
+ rev_file = node.blocks_path / "rev00000.dat"
+ bogus_file = node.blocks_path / "bogus.dat"
+ rev_file.rename(bogus_file)
+ assert_raises_rpc_error(
+ -1, 'Could not roll back to requested height.', node.dumptxoutset, 'utxos.dat', rollback=99)
+ assert_equal(node.getnetworkinfo()['networkactive'], active)
+
+ # Cleanup
+ bogus_file.rename(rev_file)
+
def run_test(self):
"""Test a trivial usage of the dumptxoutset RPC command."""
node = self.nodes[0]
@@ -27,8 +38,8 @@ class DumptxoutsetTest(BitcoinTestFramework):
self.generate(node, COINBASE_MATURITY)
FILENAME = 'txoutset.dat'
- out = node.dumptxoutset(FILENAME)
- expected_path = node.datadir_path / self.chain / FILENAME
+ out = node.dumptxoutset(FILENAME, "latest")
+ expected_path = node.chain_path / FILENAME
assert expected_path.is_file()
@@ -43,7 +54,7 @@ class DumptxoutsetTest(BitcoinTestFramework):
# UTXO snapshot hash should be deterministic based on mocked time.
assert_equal(
sha256sum_file(str(expected_path)).hex(),
- '2f775f82811150d310527b5ff773f81fb0fb517e941c543c1f7c4d38fd2717b3')
+ '31fcdd0cf542a4b1dfc13c3c05106620ce48951ef62907dd8e5e8c15a0aa993b')
assert_equal(
out['txoutset_hash'], 'a0b7baa3bf5ccbd3279728f230d7ca0c44a76e9923fca8f32dbfd08d65ea496a')
@@ -51,11 +62,23 @@ class DumptxoutsetTest(BitcoinTestFramework):
# Specifying a path to an existing or invalid file will fail.
assert_raises_rpc_error(
- -8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME)
+ -8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME, "latest")
invalid_path = node.datadir_path / "invalid" / "path"
assert_raises_rpc_error(
- -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path)
+ -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path, "latest")
+
+ self.log.info(f"Test that dumptxoutset with unknown dump type fails")
+ assert_raises_rpc_error(
+ -8, 'Invalid snapshot type "bogus" specified. Please specify "rollback" or "latest"', node.dumptxoutset, 'utxos.dat', "bogus")
+
+ self.log.info(f"Test that dumptxoutset failure does not leave the network activity suspended when it was on previously")
+ self.check_expected_network(node, True)
+
+ self.log.info(f"Test that dumptxoutset failure leaves the network activity suspended when it was off")
+ node.setnetworkactive(False)
+ self.check_expected_network(node, False)
+ node.setnetworkactive(True)
if __name__ == '__main__':
- DumptxoutsetTest().main()
+ DumptxoutsetTest(__file__).main()
diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py
index 6643799a76..05ceafbe4e 100755
--- a/test/functional/rpc_estimatefee.py
+++ b/test/functional/rpc_estimatefee.py
@@ -52,4 +52,4 @@ class EstimateFeeTest(BitcoinTestFramework):
if __name__ == '__main__':
- EstimateFeeTest().main()
+ EstimateFeeTest(__file__).main()
diff --git a/test/functional/rpc_generate.py b/test/functional/rpc_generate.py
index 3e250925e7..68de900664 100755
--- a/test/functional/rpc_generate.py
+++ b/test/functional/rpc_generate.py
@@ -126,4 +126,4 @@ class RPCGenerateTest(BitcoinTestFramework):
if __name__ == "__main__":
- RPCGenerateTest().main()
+ RPCGenerateTest(__file__).main()
diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py
index b09af9e078..245bcec8e8 100755
--- a/test/functional/rpc_getblockfilter.py
+++ b/test/functional/rpc_getblockfilter.py
@@ -61,4 +61,4 @@ class GetBlockFilterTest(BitcoinTestFramework):
self.nodes[0].getblockfilter, genesis_hash, filter_type)
if __name__ == '__main__':
- GetBlockFilterTest().main()
+ GetBlockFilterTest(__file__).main()
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
index 1ab1023cf1..62b3d664e0 100755
--- a/test/functional/rpc_getblockfrompeer.py
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -58,7 +58,7 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
self.log.info("Node 0 should only have the header for node 1's block 3")
x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips()))
assert_equal(x['status'], "headers-only")
- assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, short_tip)
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, short_tip)
self.log.info("Fetch block from node 1")
peers = self.nodes[0].getpeerinfo()
@@ -154,4 +154,4 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
if __name__ == '__main__':
- GetBlockFromPeerTest().main()
+ GetBlockFromPeerTest(__file__).main()
diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py
index bf261befcc..002763201a 100755
--- a/test/functional/rpc_getblockstats.py
+++ b/test/functional/rpc_getblockstats.py
@@ -114,7 +114,7 @@ class GetblockstatsTest(BitcoinTestFramework):
assert_equal(stats[self.max_stat_pos]['height'], self.start_height + self.max_stat_pos)
for i in range(self.max_stat_pos+1):
- self.log.info('Checking block %d\n' % (i))
+ self.log.info('Checking block %d' % (i))
assert_equal(stats[i], self.expected_stats[i])
# Check selecting block by hash too
@@ -182,5 +182,16 @@ class GetblockstatsTest(BitcoinTestFramework):
assert_equal(tip_stats["utxo_increase_actual"], 4)
assert_equal(tip_stats["utxo_size_inc_actual"], 300)
+ self.log.info("Test when only header is known")
+ block = self.generateblock(self.nodes[0], output="raw(55)", transactions=[], submit=False)
+ self.nodes[0].submitheader(block["hex"])
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: self.nodes[0].getblockstats(block['hash']))
+
+ self.log.info('Test when block is missing')
+ (self.nodes[0].blocks_path / 'blk00000.dat').rename(self.nodes[0].blocks_path / 'blk00000.dat.backup')
+ assert_raises_rpc_error(-1, 'Block not found on disk', self.nodes[0].getblockstats, hash_or_height=1)
+ (self.nodes[0].blocks_path / 'blk00000.dat.backup').rename(self.nodes[0].blocks_path / 'blk00000.dat')
+
+
if __name__ == '__main__':
- GetblockstatsTest().main()
+ GetblockstatsTest(__file__).main()
diff --git a/test/functional/rpc_getchaintips.py b/test/functional/rpc_getchaintips.py
index 7efa306c8c..1bd4413ce1 100755
--- a/test/functional/rpc_getchaintips.py
+++ b/test/functional/rpc_getchaintips.py
@@ -58,4 +58,4 @@ class GetChainTipsTest (BitcoinTestFramework):
assert_equal (tips[1], shortTip)
if __name__ == '__main__':
- GetChainTipsTest ().main ()
+ GetChainTipsTest(__file__).main()
diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py
index 2eb36f260c..e4e6d6f0f3 100755
--- a/test/functional/rpc_getdescriptorinfo.py
+++ b/test/functional/rpc_getdescriptorinfo.py
@@ -19,10 +19,15 @@ class DescriptorTest(BitcoinTestFramework):
self.extra_args = [["-disablewallet"]]
self.wallet_names = []
- def test_desc(self, desc, isrange, issolvable, hasprivatekeys):
+ def test_desc(self, desc, isrange, issolvable, hasprivatekeys, expanded_descs=None):
info = self.nodes[0].getdescriptorinfo(desc)
assert_equal(info, self.nodes[0].getdescriptorinfo(descsum_create(desc)))
- assert_equal(info['descriptor'], descsum_create(desc))
+ if expanded_descs is not None:
+ assert_equal(info["descriptor"], descsum_create(expanded_descs[0]))
+ assert_equal(info["multipath_expansion"], [descsum_create(x) for x in expanded_descs])
+ else:
+ assert_equal(info['descriptor'], descsum_create(desc))
+ assert "multipath_expansion" not in info
assert_equal(info['isrange'], isrange)
assert_equal(info['issolvable'], issolvable)
assert_equal(info['hasprivatekeys'], hasprivatekeys)
@@ -60,7 +65,12 @@ class DescriptorTest(BitcoinTestFramework):
self.test_desc("pkh([d34db33f/44h/0h/0h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/*)", isrange=True, issolvable=True, hasprivatekeys=False)
# A set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default).
self.test_desc("wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/0/*))", isrange=True, issolvable=True, hasprivatekeys=False)
+ # A multipath descriptor
+ self.test_desc("wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<0;1>/*)", isrange=True, issolvable=True, hasprivatekeys=False,
+ expanded_descs=["wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/*)", "wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/*)"])
+ self.test_desc("wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<1;2>/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<2;3>/0/*))", isrange=True, issolvable=True, hasprivatekeys=False,
+ expanded_descs=["wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/2/0/*))", "wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/2/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/3/0/*))"])
if __name__ == '__main__':
- DescriptorTest().main()
+ DescriptorTest(__file__).main()
diff --git a/test/functional/rpc_getorphantxs.py b/test/functional/rpc_getorphantxs.py
new file mode 100755
index 0000000000..8d32ce1638
--- /dev/null
+++ b/test/functional/rpc_getorphantxs.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-2024 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test the getorphantxs RPC."""
+
+from test_framework.mempool_util import tx_in_orphanage
+from test_framework.messages import msg_tx
+from test_framework.p2p import P2PInterface
+from test_framework.util import assert_equal
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.wallet import MiniWallet
+
+
+class GetOrphanTxsTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
+ self.test_orphan_activity()
+ self.test_orphan_details()
+
+ def test_orphan_activity(self):
+ self.log.info("Check that orphaned transactions are returned with getorphantxs")
+ node = self.nodes[0]
+
+ self.log.info("Create two 1P1C packages, but only broadcast the children")
+ tx_parent_1 = self.wallet.create_self_transfer()
+ tx_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_1["new_utxo"])
+ tx_parent_2 = self.wallet.create_self_transfer()
+ tx_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_2["new_utxo"])
+ peer = node.add_p2p_connection(P2PInterface())
+ peer.send_and_ping(msg_tx(tx_child_1["tx"]))
+ peer.send_and_ping(msg_tx(tx_child_2["tx"]))
+
+ self.log.info("Check that neither parent is in the mempool")
+ assert_equal(node.getmempoolinfo()["size"], 0)
+
+ self.log.info("Check that both children are in the orphanage")
+
+ orphanage = node.getorphantxs(verbosity=0)
+ self.log.info("Check the size of the orphanage")
+ assert_equal(len(orphanage), 2)
+ self.log.info("Check that negative verbosity is treated as 0")
+ assert_equal(orphanage, node.getorphantxs(verbosity=-1))
+ assert tx_in_orphanage(node, tx_child_1["tx"])
+ assert tx_in_orphanage(node, tx_child_2["tx"])
+
+ self.log.info("Broadcast parent 1")
+ peer.send_and_ping(msg_tx(tx_parent_1["tx"]))
+ self.log.info("Check that parent 1 and child 1 are in the mempool")
+ raw_mempool = node.getrawmempool()
+ assert_equal(len(raw_mempool), 2)
+ assert tx_parent_1["txid"] in raw_mempool
+ assert tx_child_1["txid"] in raw_mempool
+
+ self.log.info("Check that orphanage only contains child 2")
+ orphanage = node.getorphantxs()
+ assert_equal(len(orphanage), 1)
+ assert tx_in_orphanage(node, tx_child_2["tx"])
+
+ peer.send_and_ping(msg_tx(tx_parent_2["tx"]))
+ self.log.info("Check that all parents and children are now in the mempool")
+ raw_mempool = node.getrawmempool()
+ assert_equal(len(raw_mempool), 4)
+ assert tx_parent_1["txid"] in raw_mempool
+ assert tx_child_1["txid"] in raw_mempool
+ assert tx_parent_2["txid"] in raw_mempool
+ assert tx_child_2["txid"] in raw_mempool
+ self.log.info("Check that the orphanage is empty")
+ assert_equal(len(node.getorphantxs()), 0)
+
+ self.log.info("Confirm the transactions (clears mempool)")
+ self.generate(node, 1)
+ assert_equal(node.getmempoolinfo()["size"], 0)
+
+ def test_orphan_details(self):
+ self.log.info("Check the transaction details returned from getorphantxs")
+ node = self.nodes[0]
+
+ self.log.info("Create two orphans, from different peers")
+ tx_parent_1 = self.wallet.create_self_transfer()
+ tx_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_1["new_utxo"])
+ tx_parent_2 = self.wallet.create_self_transfer()
+ tx_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_2["new_utxo"])
+ peer_1 = node.add_p2p_connection(P2PInterface())
+ peer_2 = node.add_p2p_connection(P2PInterface())
+ peer_1.send_and_ping(msg_tx(tx_child_1["tx"]))
+ peer_2.send_and_ping(msg_tx(tx_child_2["tx"]))
+
+ orphanage = node.getorphantxs(verbosity=2)
+ assert tx_in_orphanage(node, tx_child_1["tx"])
+ assert tx_in_orphanage(node, tx_child_2["tx"])
+
+ self.log.info("Check that orphan 1 and 2 were from different peers")
+ assert orphanage[0]["from"][0] != orphanage[1]["from"][0]
+
+ self.log.info("Unorphan child 2")
+ peer_2.send_and_ping(msg_tx(tx_parent_2["tx"]))
+ assert not tx_in_orphanage(node, tx_child_2["tx"])
+
+ self.log.info("Checking orphan details")
+ orphanage = node.getorphantxs(verbosity=1)
+ assert_equal(len(node.getorphantxs()), 1)
+ orphan_1 = orphanage[0]
+ self.orphan_details_match(orphan_1, tx_child_1, verbosity=1)
+
+ self.log.info("Checking orphan details (verbosity 2)")
+ orphanage = node.getorphantxs(verbosity=2)
+ orphan_1 = orphanage[0]
+ self.orphan_details_match(orphan_1, tx_child_1, verbosity=2)
+
+ def orphan_details_match(self, orphan, tx, verbosity):
+ self.log.info("Check txid/wtxid of orphan")
+ assert_equal(orphan["txid"], tx["txid"])
+ assert_equal(orphan["wtxid"], tx["wtxid"])
+
+ self.log.info("Check the sizes of orphan")
+ assert_equal(orphan["bytes"], len(tx["tx"].serialize()))
+ assert_equal(orphan["vsize"], tx["tx"].get_vsize())
+ assert_equal(orphan["weight"], tx["tx"].get_weight())
+
+ if verbosity == 2:
+ self.log.info("Check the transaction hex of orphan")
+ assert_equal(orphan["hex"], tx["hex"])
+
+
+if __name__ == '__main__':
+ GetOrphanTxsTest(__file__).main()
diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py
index 53c5aa05e5..4ce24ecb67 100755
--- a/test/functional/rpc_help.py
+++ b/test/functional/rpc_help.py
@@ -132,4 +132,4 @@ class HelpRpcTest(BitcoinTestFramework):
if __name__ == '__main__':
- HelpRpcTest().main()
+ HelpRpcTest(__file__).main()
diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py
index 6759b69dd1..07795534a0 100755
--- a/test/functional/rpc_invalid_address_message.py
+++ b/test/functional/rpc_invalid_address_message.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2020-2022 The Bitcoin Core developers
+# Copyright (c) 2020-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test error messages for 'getaddressinfo' and 'validateaddress' RPC commands."""
@@ -12,6 +12,7 @@ from test_framework.util import (
)
BECH32_VALID = 'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv'
+BECH32_VALID_UNKNOWN_WITNESS = 'bcrt1p424qxxyd0r'
BECH32_VALID_CAPITALS = 'BCRT1QPLMTZKC2XHARPPZDLNPAQL78RSHJ68U33RAH7R'
BECH32_VALID_MULTISIG = 'bcrt1qdg3myrgvzw7ml9q0ejxhlkyxm7vl9r56yzkfgvzclrf4hkpx9yfqhpsuks'
@@ -80,6 +81,7 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
# Valid Bech32
self.check_valid(BECH32_VALID)
+ self.check_valid(BECH32_VALID_UNKNOWN_WITNESS)
self.check_valid(BECH32_VALID_CAPITALS)
self.check_valid(BECH32_VALID_MULTISIG)
@@ -109,6 +111,7 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
assert_raises_rpc_error(-5, "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", node.getaddressinfo, BECH32_INVALID_PREFIX)
assert_raises_rpc_error(-5, "Invalid or unsupported Base58-encoded address.", node.getaddressinfo, BASE58_INVALID_PREFIX)
assert_raises_rpc_error(-5, "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", node.getaddressinfo, INVALID_ADDRESS)
+ assert "isscript" not in node.getaddressinfo(BECH32_VALID_UNKNOWN_WITNESS)
def run_test(self):
self.test_validateaddress()
@@ -119,4 +122,4 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework):
if __name__ == '__main__':
- InvalidAddressErrorMessageTest().main()
+ InvalidAddressErrorMessageTest(__file__).main()
diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py
index 69c5397ce2..db79d55259 100755
--- a/test/functional/rpc_invalidateblock.py
+++ b/test/functional/rpc_invalidateblock.py
@@ -90,4 +90,4 @@ class InvalidateTest(BitcoinTestFramework):
if __name__ == '__main__':
- InvalidateTest().main()
+ InvalidateTest(__file__).main()
diff --git a/test/functional/rpc_mempool_info.py b/test/functional/rpc_mempool_info.py
index 246af22e50..231d93a7b1 100755
--- a/test/functional/rpc_mempool_info.py
+++ b/test/functional/rpc_mempool_info.py
@@ -96,4 +96,4 @@ class RPCMempoolInfoTest(BitcoinTestFramework):
if __name__ == '__main__':
- RPCMempoolInfoTest().main()
+ RPCMempoolInfoTest(__file__).main()
diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py
index 20485c01d3..b5c12b28c1 100755
--- a/test/functional/rpc_misc.py
+++ b/test/functional/rpc_misc.py
@@ -102,4 +102,4 @@ class RpcMiscTest(BitcoinTestFramework):
if __name__ == '__main__':
- RpcMiscTest().main()
+ RpcMiscTest(__file__).main()
diff --git a/test/functional/rpc_named_arguments.py b/test/functional/rpc_named_arguments.py
index 46d9ffceae..0385c33267 100755
--- a/test/functional/rpc_named_arguments.py
+++ b/test/functional/rpc_named_arguments.py
@@ -35,4 +35,4 @@ class NamedArgumentTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Parameter arg1 specified twice both as positional and named argument", node.echo, 0, None, 2, arg1=1)
if __name__ == '__main__':
- NamedArgumentTest().main()
+ NamedArgumentTest(__file__).main()
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 2701d2471d..b63059b1ee 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -237,28 +237,35 @@ class NetTest(BitcoinTestFramework):
def test_addnode_getaddednodeinfo(self):
self.log.info("Test addnode and getaddednodeinfo")
assert_equal(self.nodes[0].getaddednodeinfo(), [])
- # add a node (node2) to node0
+ self.log.info("Add a node (node2) to node0")
ip_port = "127.0.0.1:{}".format(p2p_port(2))
self.nodes[0].addnode(node=ip_port, command='add')
- # try to add an equivalent ip
- # (note that OpenBSD doesn't support the IPv4 shorthand notation with omitted zero-bytes)
+ self.log.info("Try to add an equivalent ip and check it fails")
+ self.log.debug("(note that OpenBSD doesn't support the IPv4 shorthand notation with omitted zero-bytes)")
if platform.system() != "OpenBSD":
ip_port2 = "127.1:{}".format(p2p_port(2))
assert_raises_rpc_error(-23, "Node already added", self.nodes[0].addnode, node=ip_port2, command='add')
- # check that the node has indeed been added
+ self.log.info("Check that the node has indeed been added")
added_nodes = self.nodes[0].getaddednodeinfo()
assert_equal(len(added_nodes), 1)
assert_equal(added_nodes[0]['addednode'], ip_port)
- # check that node cannot be added again
+ self.log.info("Check that filtering by node works")
+ self.nodes[0].addnode(node="11.22.33.44", command='add')
+ first_added_node = self.nodes[0].getaddednodeinfo(node=ip_port)
+ assert_equal(added_nodes, first_added_node)
+ assert_equal(len(self.nodes[0].getaddednodeinfo()), 2)
+ self.log.info("Check that node cannot be added again")
assert_raises_rpc_error(-23, "Node already added", self.nodes[0].addnode, node=ip_port, command='add')
- # check that node can be removed
+ self.log.info("Check that node can be removed")
self.nodes[0].addnode(node=ip_port, command='remove')
- assert_equal(self.nodes[0].getaddednodeinfo(), [])
- # check that an invalid command returns an error
+ added_nodes = self.nodes[0].getaddednodeinfo()
+ assert_equal(len(added_nodes), 1)
+ assert_equal(added_nodes[0]['addednode'], "11.22.33.44")
+ self.log.info("Check that an invalid command returns an error")
assert_raises_rpc_error(-1, 'addnode "node" "command"', self.nodes[0].addnode, node=ip_port, command='abc')
- # check that trying to remove the node again returns an error
+ self.log.info("Check that trying to remove the node again returns an error")
assert_raises_rpc_error(-24, "Node could not be removed", self.nodes[0].addnode, node=ip_port, command='remove')
- # check that a non-existent node returns an error
+ self.log.info("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_service_flags(self):
@@ -567,4 +574,4 @@ class NetTest(BitcoinTestFramework):
if __name__ == '__main__':
- NetTest().main()
+ NetTest(__file__).main()
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 1acd586d2c..ef2f66b3a0 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -489,4 +489,4 @@ class RPCPackagesTest(BitcoinTestFramework):
assert_equal(node.getrawmempool(), [chained_txns_burn[0]["txid"]])
if __name__ == "__main__":
- RPCPackagesTest().main()
+ RPCPackagesTest(__file__).main()
diff --git a/test/functional/rpc_preciousblock.py b/test/functional/rpc_preciousblock.py
index 3062a86565..224bba6f9b 100755
--- a/test/functional/rpc_preciousblock.py
+++ b/test/functional/rpc_preciousblock.py
@@ -109,4 +109,4 @@ class PreciousTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbestblockhash(), hashH)
if __name__ == '__main__':
- PreciousTest().main()
+ PreciousTest(__file__).main()
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 6ee7e56886..8042bdf071 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -8,6 +8,9 @@ from decimal import Decimal
from itertools import product
from random import randbytes
+from test_framework.blocktools import (
+ MAX_STANDARD_TX_WEIGHT,
+)
from test_framework.descriptors import descsum_create
from test_framework.key import H_POINT
from test_framework.messages import (
@@ -16,6 +19,7 @@ from test_framework.messages import (
CTxIn,
CTxOut,
MAX_BIP125_RBF_SEQUENCE,
+ WITNESS_SCALE_FACTOR,
)
from test_framework.psbt import (
PSBT,
@@ -30,6 +34,7 @@ from test_framework.psbt import (
PSBT_OUT_TAP_TREE,
)
from test_framework.script import CScript, OP_TRUE
+from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
@@ -68,6 +73,28 @@ class PSBTTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
+ def test_psbt_incomplete_after_invalid_modification(self):
+ self.log.info("Check that PSBT is correctly marked as incomplete after invalid modification")
+ node = self.nodes[2]
+ wallet = node.get_wallet_rpc(self.default_wallet_name)
+ address = wallet.getnewaddress()
+ wallet.sendtoaddress(address=address, amount=1.0)
+ self.generate(node, nblocks=1, sync_fun=lambda: self.sync_all(self.nodes[:2]))
+
+ utxos = wallet.listunspent(addresses=[address])
+ psbt = wallet.createpsbt([{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], [{wallet.getnewaddress(): 0.9999}])
+ signed_psbt = wallet.walletprocesspsbt(psbt)["psbt"]
+
+ # Modify the raw transaction by changing the output address, so the signature is no longer valid
+ signed_psbt_obj = PSBT.from_base64(signed_psbt)
+ substitute_addr = wallet.getnewaddress()
+ raw = wallet.createrawtransaction([{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], [{substitute_addr: 0.9999}])
+ signed_psbt_obj.g.map[PSBT_GLOBAL_UNSIGNED_TX] = bytes.fromhex(raw)
+
+ # Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete
+ signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False)
+ assert signed_psbt_incomplete["complete"] is False
+
def test_utxo_conversion(self):
self.log.info("Check that non-witness UTXOs are removed for segwit v1+ inputs")
mining_node = self.nodes[2]
@@ -186,6 +213,46 @@ class PSBTTest(BitcoinTestFramework):
# Create and fund a raw tx for sending 10 BTC
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']
+ self.log.info("Test for invalid maximum transaction weights")
+ dest_arg = [{self.nodes[0].getnewaddress(): 1}]
+ min_tx_weight = MIN_STANDARD_TX_NONWITNESS_SIZE * WITNESS_SCALE_FACTOR
+ assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": -1})
+ assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": 0})
+ assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": MAX_STANDARD_TX_WEIGHT + 1})
+
+ # Base transaction vsize: version (4) + locktime (4) + input count (1) + witness overhead (1) = 10 vbytes
+ base_tx_vsize = 10
+ # One P2WPKH output vsize: outpoint (31 vbytes)
+ p2wpkh_output_vsize = 31
+ # 1 vbyte for output count
+ output_count = 1
+ tx_weight_without_inputs = (base_tx_vsize + output_count + p2wpkh_output_vsize) * WITNESS_SCALE_FACTOR
+ # min_tx_weight is greater than transaction weight without inputs
+ assert_greater_than(min_tx_weight, tx_weight_without_inputs)
+
+ # In order to test for when the passed max weight is less than the transaction weight without inputs
+ # Define destination with two outputs.
+ dest_arg_large = [{self.nodes[0].getnewaddress(): 1}, {self.nodes[0].getnewaddress(): 1}]
+ large_tx_vsize_without_inputs = base_tx_vsize + output_count + (p2wpkh_output_vsize * 2)
+ large_tx_weight_without_inputs = large_tx_vsize_without_inputs * WITNESS_SCALE_FACTOR
+ assert_greater_than(large_tx_weight_without_inputs, min_tx_weight)
+ # Test for max_tx_weight less than Transaction weight without inputs
+ assert_raises_rpc_error(-4, "Maximum transaction weight is less than transaction weight without inputs", self.nodes[0].walletcreatefundedpsbt, [], dest_arg_large, 0, {"max_tx_weight": min_tx_weight})
+ assert_raises_rpc_error(-4, "Maximum transaction weight is less than transaction weight without inputs", self.nodes[0].walletcreatefundedpsbt, [], dest_arg_large, 0, {"max_tx_weight": large_tx_weight_without_inputs})
+
+ # Test for max_tx_weight just enough to include inputs but not change output
+ assert_raises_rpc_error(-4, "Maximum transaction weight is too low, can not accommodate change output", self.nodes[0].walletcreatefundedpsbt, [], dest_arg_large, 0, {"max_tx_weight": (large_tx_vsize_without_inputs + 1) * WITNESS_SCALE_FACTOR})
+ self.log.info("Test that a funded PSBT is always faithful to max_tx_weight option")
+ large_tx_vsize_with_change = large_tx_vsize_without_inputs + p2wpkh_output_vsize
+ # It's enough but won't accommodate selected input size
+ assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight", self.nodes[0].walletcreatefundedpsbt, [], dest_arg_large, 0, {"max_tx_weight": (large_tx_vsize_with_change) * WITNESS_SCALE_FACTOR})
+
+ max_tx_weight_sufficient = 1000 # 1k vbytes is enough
+ psbt = self.nodes[0].walletcreatefundedpsbt(outputs=dest_arg,locktime=0, options={"max_tx_weight": max_tx_weight_sufficient})["psbt"]
+ weight = self.nodes[0].decodepsbt(psbt)["tx"]["weight"]
+ # ensure the transaction's weight is below the specified max_tx_weight.
+ assert_greater_than_or_equal(max_tx_weight_sufficient, weight)
+
# If inputs are specified, do not automatically add more:
utxo1 = self.nodes[0].listunspent()[0]
assert_raises_rpc_error(-4, "The preselected coins total amount does not cover the transaction target. "
@@ -589,6 +656,7 @@ class PSBTTest(BitcoinTestFramework):
if self.options.descriptors:
self.test_utxo_conversion()
+ self.test_psbt_incomplete_after_invalid_modification()
self.test_input_confs_control()
@@ -700,11 +768,9 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(analysis['next'], 'creator')
assert_equal(analysis['error'], 'PSBT is not valid. Output amount invalid')
- analysis = self.nodes[0].analyzepsbt('cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==')
- assert_equal(analysis['next'], 'creator')
- assert_equal(analysis['error'], 'PSBT is not valid. Input 0 specifies invalid prevout')
+ assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].analyzepsbt, "cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==")
- assert_raises_rpc_error(-25, 'Inputs missing or spent', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==')
+ assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].walletprocesspsbt, "cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==")
self.log.info("Test that we can fund psbts with external inputs specified")
@@ -987,4 +1053,4 @@ class PSBTTest(BitcoinTestFramework):
if __name__ == '__main__':
- PSBTTest().main()
+ PSBTTest(__file__).main()
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index f974a05f7b..18b1fc1896 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -430,13 +430,13 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(testres['allowed'], True)
self.nodes[2].sendrawtransaction(hexstring=tx['hex'], maxfeerate='0.20000000')
- self.log.info("Test sendrawtransaction/testmempoolaccept with tx already in the chain")
+ self.log.info("Test sendrawtransaction/testmempoolaccept with tx outputs already in the utxo set")
self.generate(self.nodes[2], 1)
for node in self.nodes:
testres = node.testmempoolaccept([tx['hex']])[0]
assert_equal(testres['allowed'], False)
assert_equal(testres['reject-reason'], 'txn-already-known')
- assert_raises_rpc_error(-27, 'Transaction already in block chain', node.sendrawtransaction, tx['hex'])
+ assert_raises_rpc_error(-27, 'Transaction outputs already in utxo set', node.sendrawtransaction, tx['hex'])
def decoderawtransaction_tests(self):
self.log.info("Test decoderawtransaction")
@@ -612,4 +612,4 @@ class RawTransactionsTest(BitcoinTestFramework):
if __name__ == '__main__':
- RawTransactionsTest().main()
+ RawTransactionsTest(__file__).main()
diff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py
index 8b4aebc77a..d05ba09ba5 100755
--- a/test/functional/rpc_scanblocks.py
+++ b/test/functional/rpc_scanblocks.py
@@ -136,4 +136,4 @@ class ScanblocksTest(BitcoinTestFramework):
if __name__ == '__main__':
- ScanblocksTest().main()
+ ScanblocksTest(__file__).main()
diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py
index 9f77f209ef..a7b560ef57 100755
--- a/test/functional/rpc_scantxoutset.py
+++ b/test/functional/rpc_scantxoutset.py
@@ -110,6 +110,7 @@ class ScantxoutsetTest(BitcoinTestFramework):
assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288"))
assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": [1500, 1500]}])['total_amount'], Decimal("16.384"))
+ assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/<0;1>)"}])["total_amount"], Decimal("12.288"))
# Test the reported descriptors for a few matches
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/*)", "range": 1499}])), ["pkh([0c5f9a1e/0h/0h/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)#rthll0rg", "pkh([0c5f9a1e/0h/0h/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)#mcjajulr"])
@@ -120,7 +121,15 @@ class ScantxoutsetTest(BitcoinTestFramework):
assert_equal(self.nodes[0].scantxoutset("status"), None)
assert_equal(self.nodes[0].scantxoutset("abort"), False)
- # check that first arg is needed
+ # Check that the blockhash and confirmations fields are correct
+ self.generate(self.nodes[0], 2)
+ unspent = self.nodes[0].scantxoutset("start", ["addr(mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq)"])["unspents"][0]
+ blockhash = self.nodes[0].getblockhash(info["height"])
+ assert_equal(unspent["height"], info["height"])
+ assert_equal(unspent["blockhash"], blockhash)
+ assert_equal(unspent["confirmations"], 3)
+
+ # Check that first arg is needed
assert_raises_rpc_error(-1, "scantxoutset \"action\" ( [scanobjects,...] )", self.nodes[0].scantxoutset)
# Check that second arg is needed for start
@@ -131,4 +140,4 @@ class ScantxoutsetTest(BitcoinTestFramework):
if __name__ == "__main__":
- ScantxoutsetTest().main()
+ ScantxoutsetTest(__file__).main()
diff --git a/test/functional/rpc_setban.py b/test/functional/rpc_setban.py
index ba86b278bd..c4e5ebccca 100755
--- a/test/functional/rpc_setban.py
+++ b/test/functional/rpc_setban.py
@@ -75,4 +75,4 @@ class SetBanTests(BitcoinTestFramework):
assert_equal(banned['ban_duration'], 1234)
if __name__ == '__main__':
- SetBanTests().main()
+ SetBanTests(__file__).main()
diff --git a/test/functional/rpc_signer.py b/test/functional/rpc_signer.py
index 488682e959..7dd16ea8fe 100755
--- a/test/functional/rpc_signer.py
+++ b/test/functional/rpc_signer.py
@@ -77,4 +77,4 @@ class RPCSignerTest(BitcoinTestFramework):
assert_equal({'fingerprint': '00000001', 'name': 'trezor_t'} in self.nodes[1].enumeratesigners()['signers'], True)
if __name__ == '__main__':
- RPCSignerTest().main()
+ RPCSignerTest(__file__).main()
diff --git a/test/functional/rpc_signmessagewithprivkey.py b/test/functional/rpc_signmessagewithprivkey.py
index c5df22157d..8e86698781 100755
--- a/test/functional/rpc_signmessagewithprivkey.py
+++ b/test/functional/rpc_signmessagewithprivkey.py
@@ -60,4 +60,4 @@ class SignMessagesWithPrivTest(BitcoinTestFramework):
if __name__ == '__main__':
- SignMessagesWithPrivTest().main()
+ SignMessagesWithPrivTest(__file__).main()
diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py
index f4fec13495..b359a08f39 100755
--- a/test/functional/rpc_signrawtransactionwithkey.py
+++ b/test/functional/rpc_signrawtransactionwithkey.py
@@ -4,18 +4,18 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test transaction signing using the signrawtransactionwithkey RPC."""
-from test_framework.blocktools import (
- COINBASE_MATURITY,
+from test_framework.messages import (
+ COIN,
)
from test_framework.address import (
address_to_scriptpubkey,
+ p2a,
script_to_p2sh,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- find_vout_for_address,
)
from test_framework.script_util import (
key_to_p2pk_script,
@@ -25,6 +25,7 @@ from test_framework.script_util import (
)
from test_framework.wallet import (
getnewdestination,
+ MiniWallet,
)
from test_framework.wallet_util import (
generate_keypair,
@@ -45,16 +46,12 @@ OUTPUTS = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
class SignRawTransactionWithKeyTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = True
- self.num_nodes = 2
+ self.num_nodes = 1
def send_to_address(self, addr, amount):
- input = {"txid": self.nodes[0].getblock(self.block_hash[self.blk_idx])["tx"][0], "vout": 0}
- output = {addr: amount}
- self.blk_idx += 1
- rawtx = self.nodes[0].createrawtransaction([input], output)
- txid = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransactionwithkey(rawtx, [self.nodes[0].get_deterministic_priv_key().key])["hex"], 0)
- return txid
+ script_pub_key = address_to_scriptpubkey(addr)
+ tx = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=script_pub_key, amount=int(amount * COIN))
+ return tx["txid"], tx["sent_vout"]
def assert_signing_completed_successfully(self, signed_tx):
assert 'errors' not in signed_tx
@@ -79,14 +76,12 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet")
# Create a new P2SH-P2WSH 1-of-1 multisig address:
embedded_privkey, embedded_pubkey = generate_keypair(wif=True)
- p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey.hex()], "p2sh-segwit")
+ p2sh_p2wsh_address = self.nodes[0].createmultisig(1, [embedded_pubkey.hex()], "p2sh-segwit")
# send transaction to P2SH-P2WSH 1-of-1 multisig address
- self.block_hash = self.generate(self.nodes[0], COINBASE_MATURITY + 1)
- self.blk_idx = 0
self.send_to_address(p2sh_p2wsh_address["address"], 49.999)
self.generate(self.nodes[0], 1)
# Get the UTXO info from scantxoutset
- unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0]
+ unspent_output = self.nodes[0].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()
@@ -100,6 +95,18 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent
self.verify_txn_with_witness_script(tx_type)
+ def keyless_signing_test(self):
+ self.log.info("Test that keyless 'signing' of pay-to-anchor input succeeds")
+ [txid, vout] = self.send_to_address(p2a(), 49.999)
+ spending_tx = self.nodes[0].createrawtransaction(
+ [{"txid": txid, "vout": vout}],
+ [{getnewdestination()[2]: Decimal("49.998")}])
+ spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [], [])
+ self.assert_signing_completed_successfully(spending_tx_signed)
+ assert self.nodes[0].testmempoolaccept([spending_tx_signed["hex"]])[0]["allowed"]
+ # 'signing' a P2A prevout is a no-op, so signed and unsigned txs shouldn't differ
+ assert_equal(spending_tx, spending_tx_signed["hex"])
+
def verify_txn_with_witness_script(self, tx_type):
self.log.info("Test with a {} script as the witnessScript".format(tx_type))
embedded_privkey, embedded_pubkey = generate_keypair(wif=True)
@@ -111,9 +118,7 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
addr = script_to_p2sh(redeem_script)
script_pub_key = address_to_scriptpubkey(addr).hex()
# Fund that address
- txid = self.send_to_address(addr, 10)
- vout = find_vout_for_address(self.nodes[0], txid, addr)
- self.generate(self.nodes[0], 1)
+ [txid, vout] = self.send_to_address(addr, 10)
# 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([{'txid': txid, 'vout': vout}], {getnewdestination()[2]: Decimal("9.999")})
spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [{'txid': txid, 'vout': vout, 'scriptPubKey': script_pub_key, 'redeemScript': redeem_script, 'witnessScript': witness_script, 'amount': 10}])
@@ -136,11 +141,13 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework):
assert_raises_rpc_error(-22, "TX decode failed. Make sure the tx has at least one input.", self.nodes[0].signrawtransactionwithkey, tx + "00", privkeys)
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
self.successful_signing_test()
self.witness_script_test()
+ self.keyless_signing_test()
self.invalid_sighashtype_test()
self.invalid_private_key_and_tx()
if __name__ == '__main__':
- SignRawTransactionWithKeyTest().main()
+ SignRawTransactionWithKeyTest(__file__).main()
diff --git a/test/functional/rpc_txoutproof.py b/test/functional/rpc_txoutproof.py
index 60b7ce8d20..90572245d6 100755
--- a/test/functional/rpc_txoutproof.py
+++ b/test/functional/rpc_txoutproof.py
@@ -67,6 +67,10 @@ class MerkleBlockTest(BitcoinTestFramework):
assert_equal(self.nodes[0].verifytxoutproof(self.nodes[0].gettxoutproof([txid_spent], blockhash)), [txid_spent])
# We can't get the proof if we specify a non-existent block
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].gettxoutproof, [txid_spent], "0000000000000000000000000000000000000000000000000000000000000000")
+ # We can't get the proof if we only have the header of the specified block
+ block = self.generateblock(self.nodes[0], output="raw(55)", transactions=[], submit=False)
+ self.nodes[0].submitheader(block["hex"])
+ assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].gettxoutproof, [txid_spent], block['hash'])
# We can get the proof if the transaction is unspent
assert_equal(self.nodes[0].verifytxoutproof(self.nodes[0].gettxoutproof([txid_unspent])), [txid_unspent])
# We can get the proof if we provide a list of transactions and one of them is unspent. The ordering of the list should not matter.
@@ -104,4 +108,4 @@ class MerkleBlockTest(BitcoinTestFramework):
# verify that the proofs are invalid
if __name__ == '__main__':
- MerkleBlockTest().main()
+ MerkleBlockTest(__file__).main()
diff --git a/test/functional/rpc_uptime.py b/test/functional/rpc_uptime.py
index f8df59d02a..fdf459953c 100755
--- a/test/functional/rpc_uptime.py
+++ b/test/functional/rpc_uptime.py
@@ -32,4 +32,4 @@ class UptimeTest(BitcoinTestFramework):
if __name__ == '__main__':
- UptimeTest().main()
+ UptimeTest(__file__).main()
diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py
index 66cdd7cf9a..49eb64abad 100755
--- a/test/functional/rpc_users.py
+++ b/test/functional/rpc_users.py
@@ -11,12 +11,15 @@ from test_framework.util import (
)
import http.client
+import os
+import platform
import urllib.parse
import subprocess
from random import SystemRandom
import string
import configparser
import sys
+from typing import Optional
def call_with_auth(node, user, password):
@@ -84,6 +87,40 @@ class HTTPBasicsTest(BitcoinTestFramework):
self.log.info('Wrong...')
assert_equal(401, call_with_auth(node, user + 'wrong', password + 'wrong').status)
+ def test_rpccookieperms(self):
+ p = {"owner": 0o600, "group": 0o640, "all": 0o644}
+
+ if platform.system() == 'Windows':
+ self.log.info(f"Skip cookie file permissions checks as OS detected as: {platform.system()=}")
+ return
+
+ self.log.info('Check cookie file permissions can be set using -rpccookieperms')
+
+ cookie_file_path = self.nodes[1].chain_path / '.cookie'
+ PERM_BITS_UMASK = 0o777
+
+ def test_perm(perm: Optional[str]):
+ if not perm:
+ perm = 'owner'
+ self.restart_node(1)
+ else:
+ self.restart_node(1, extra_args=[f"-rpccookieperms={perm}"])
+
+ file_stat = os.stat(cookie_file_path)
+ actual_perms = file_stat.st_mode & PERM_BITS_UMASK
+ expected_perms = p[perm]
+ assert_equal(expected_perms, actual_perms)
+
+ # Remove any leftover rpc{user|password} config options from previous tests
+ self.nodes[1].replace_in_config([("rpcuser", "#rpcuser"), ("rpcpassword", "#rpcpassword")])
+
+ self.log.info('Check default cookie permission')
+ test_perm(None)
+
+ self.log.info('Check custom cookie permissions')
+ for perm in ["owner", "group", "all"]:
+ test_perm(perm)
+
def run_test(self):
self.conf_setup()
self.log.info('Check correctness of the rpcauth config option')
@@ -102,19 +139,38 @@ class HTTPBasicsTest(BitcoinTestFramework):
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.log.info('Empty -rpcauth are treated as error')
self.stop_node(0)
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth'])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth='])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=""'])
+ self.log.info('Check malformed -rpcauth')
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.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar:baz'])
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar:baz'])
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar$baz'])
+ self.log.info('Check interactions between blank and non-blank rpcauth')
+ # pw = bitcoin
+ rpcauth_user1 = '-rpcauth=user1:6dd184e5e69271fdd69103464630014f$eb3d7ce67c4d1ff3564270519b03b636c0291012692a5fa3dd1d2075daedd07b'
+ rpcauth_user2 = '-rpcauth=user2:57b2f77c919eece63cfa46c2f06e46ae$266b63902f99f97eeaab882d4a87f8667ab84435c3799f2ce042ef5a994d620b'
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=[rpcauth_user1, rpcauth_user2, '-rpcauth='])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=[rpcauth_user1, '-rpcauth=', rpcauth_user2])
+ self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=', rpcauth_user1, rpcauth_user2])
+
+ self.log.info('Check -norpcauth disables previous -rpcauth params')
+ self.restart_node(0, extra_args=[rpcauth_user1, rpcauth_user2, '-norpcauth'])
+ assert_equal(401, call_with_auth(self.nodes[0], 'user1', 'bitcoin').status)
+ assert_equal(401, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
+ self.stop_node(0)
+
self.log.info('Check that failure to write cookie file will abort the node gracefully')
(self.nodes[0].chain_path / ".cookie.tmp").mkdir()
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error)
+ self.test_rpccookieperms()
+
if __name__ == '__main__':
- HTTPBasicsTest().main()
+ HTTPBasicsTest(__file__).main()
diff --git a/test/functional/rpc_validateaddress.py b/test/functional/rpc_validateaddress.py
index d87ba098c3..bf094a7df8 100755
--- a/test/functional/rpc_validateaddress.py
+++ b/test/functional/rpc_validateaddress.py
@@ -166,6 +166,11 @@ VALID_DATA = [
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
"512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
),
+ # PayToAnchor(P2A)
+ (
+ "bc1pfeessrawgf",
+ "51024e73",
+ ),
]
@@ -200,4 +205,4 @@ class ValidateAddressMainTest(BitcoinTestFramework):
if __name__ == "__main__":
- ValidateAddressMainTest().main()
+ ValidateAddressMainTest(__file__).main()
diff --git a/test/functional/rpc_whitelist.py b/test/functional/rpc_whitelist.py
index fb404fb479..5f74fe8274 100755
--- a/test/functional/rpc_whitelist.py
+++ b/test/functional/rpc_whitelist.py
@@ -93,4 +93,4 @@ class RPCWhitelistTest(BitcoinTestFramework):
assert_equal(200, rpccall(self.nodes[0], self.strange_users[4], "getblockcount").status)
if __name__ == "__main__":
- RPCWhitelistTest().main()
+ RPCWhitelistTest(__file__).main()
diff --git a/test/functional/test-shell.md b/test/functional/test-shell.md
index 4cd62c4ef3..d79c4a0ab6 100644
--- a/test/functional/test-shell.md
+++ b/test/functional/test-shell.md
@@ -169,7 +169,7 @@ can be called after the TestShell is shut down.
| Test parameter key | Default Value | Description |
|---|---|---|
-| `bind_to_localhost_only` | `True` | Binds bitcoind RPC services to `127.0.0.1` if set to `True`.|
+| `bind_to_localhost_only` | `True` | Binds bitcoind P2P services to `127.0.0.1` if set to `True`.|
| `cachedir` | `"/path/to/bitcoin/test/cache"` | Sets the bitcoind datadir directory. |
| `chain` | `"regtest"` | Sets the chain-type for the underlying test bitcoind processes. |
| `configfile` | `"/path/to/bitcoin/test/config.ini"` | Sets the location of the test framework config file. |
diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py
index bcb38b21cd..2c754e35aa 100644
--- a/test/functional/test_framework/address.py
+++ b/test/functional/test_framework/address.py
@@ -53,13 +53,14 @@ def create_deterministic_address_bcrt1_p2tr_op_true(explicit_internal_key=None):
can be spent with a witness stack of OP_TRUE and the control block
with internal public key (script-path spending).
- Returns a tuple with the generated address and the internal key.
+ Returns a tuple with the generated address and the TaprootInfo object.
"""
internal_key = explicit_internal_key or (1).to_bytes(32, 'big')
- address = output_key_to_p2tr(taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).output_pubkey)
+ taproot_info = taproot_construct(internal_key, [("only-path", CScript([OP_TRUE]))])
+ address = output_key_to_p2tr(taproot_info.output_pubkey)
if explicit_internal_key is None:
assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka')
- return (address, internal_key)
+ return (address, taproot_info)
def byte_to_base58(b, version):
@@ -154,6 +155,9 @@ def output_key_to_p2tr(key, main=False):
assert len(key) == 32
return program_to_witness(1, key, main)
+def p2a(main=False):
+ return program_to_witness(1, "4e73", main)
+
def check_key(key):
if (type(key) is str):
key = bytes.fromhex(key) # Assuming this is hex string
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index f0dc866f69..705b8e8fe5 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# Copyright (c) 2015-2022 The Bitcoin Core developers
+# Copyright (c) 2015-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Utilities for manipulating blocks and transactions."""
@@ -48,6 +48,7 @@ from .util import assert_equal
MAX_BLOCK_SIGOPS = 20000
MAX_BLOCK_SIGOPS_WEIGHT = MAX_BLOCK_SIGOPS * WITNESS_SCALE_FACTOR
+MAX_STANDARD_TX_WEIGHT = 400000
# Genesis block time (regtest)
TIME_GENESIS_BLOCK = 1296688602
@@ -73,7 +74,7 @@ def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl
block.nVersion = version or tmpl.get('version') or VERSIONBITS_LAST_OLD_BLOCK_VERSION
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:
+ if tmpl and tmpl.get('bits') is not None:
block.nBits = struct.unpack('>I', bytes.fromhex(tmpl['bits']))[0]
else:
block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams
@@ -153,16 +154,18 @@ def create_coinbase(height, pubkey=None, *, script_pubkey=None, extra_output_scr
coinbase.calc_sha256()
return coinbase
-def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=CScript()):
+def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, output_script=None):
"""Return one-input, one-output transaction object
spending the prevtx's n-th output with the given amount.
Can optionally pass scriptPubKey and scriptSig, default is anyone-can-spend output.
"""
+ if output_script is None:
+ output_script = CScript()
tx = CTransaction()
assert n < len(prevtx.vout)
tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, SEQUENCE_FINAL))
- tx.vout.append(CTxOut(amount, script_pub_key))
+ tx.vout.append(CTxOut(amount, output_script))
tx.calc_sha256()
return tx
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
index 06252f8996..939c7cbef6 100644
--- a/test/functional/test_framework/key.py
+++ b/test/functional/test_framework/key.py
@@ -14,6 +14,7 @@ import random
import unittest
from test_framework.crypto import secp256k1
+from test_framework.util import random_bitflip
# Point with no known discrete log.
H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
@@ -292,11 +293,6 @@ def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False):
class TestFrameworkKey(unittest.TestCase):
def test_ecdsa_and_schnorr(self):
"""Test the Python ECDSA and Schnorr implementations."""
- def random_bitflip(sig):
- sig = list(sig)
- sig[random.randrange(len(sig))] ^= (1 << (random.randrange(8)))
- return bytes(sig)
-
byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, ORDER - 1, ORDER, 2**256 - 1]]
keys = {}
for privkey_bytes in byte_arrays: # build array of key/pubkey pairs
diff --git a/test/functional/test_framework/mempool_util.py b/test/functional/test_framework/mempool_util.py
index 148cc935ed..a6a7940c60 100644
--- a/test/functional/test_framework/mempool_util.py
+++ b/test/functional/test_framework/mempool_util.py
@@ -8,6 +8,7 @@ from decimal import Decimal
from .blocktools import (
COINBASE_MATURITY,
)
+from .messages import CTransaction
from .util import (
assert_equal,
assert_greater_than,
@@ -19,14 +20,11 @@ from .wallet import (
)
-def fill_mempool(test_framework, node):
+def fill_mempool(test_framework, node, *, tx_sync_fun=None):
"""Fill mempool until eviction.
Allows for simpler testing of scenarios with floating mempoolminfee > minrelay
- Requires -datacarriersize=100000 and
- -maxmempool=5.
- It will not ensure mempools become synced as it
- is based on a single node and assumes -minrelaytxfee
+ Requires -datacarriersize=100000 and -maxmempool=5 and assumes -minrelaytxfee
is 1 sat/vbyte.
To avoid unintentional tx dependencies, the mempool filling txs are created with a
tagged ephemeral miniwallet instance.
@@ -57,18 +55,25 @@ def fill_mempool(test_framework, node):
tx_to_be_evicted_id = ephemeral_miniwallet.send_self_transfer(
from_node=node, utxo_to_spend=confirmed_utxos.pop(0), fee_rate=relayfee)["txid"]
+ def send_batch(fee):
+ utxos = confirmed_utxos[:tx_batch_size]
+ create_lots_of_big_transactions(ephemeral_miniwallet, node, fee, tx_batch_size, txouts, utxos)
+ del confirmed_utxos[:tx_batch_size]
+
# Increase the tx fee rate to give the subsequent transactions a higher priority in the mempool
# The tx has an approx. vsize of 65k, i.e. multiplying the previous fee rate (in sats/kvB)
# by 130 should result in a fee that corresponds to 2x of that fee rate
base_fee = relayfee * 130
+ batch_fees = [(i + 1) * base_fee for i in range(num_of_batches)]
test_framework.log.debug("Fill up the mempool with txs with higher fee rate")
- with node.assert_debug_log(["rolling minimum fee bumped"]):
- for batch_of_txid in range(num_of_batches):
- fee = (batch_of_txid + 1) * base_fee
- utxos = confirmed_utxos[:tx_batch_size]
- create_lots_of_big_transactions(ephemeral_miniwallet, node, fee, tx_batch_size, txouts, utxos)
- del confirmed_utxos[:tx_batch_size]
+ for fee in batch_fees[:-3]:
+ send_batch(fee)
+ tx_sync_fun() if tx_sync_fun else test_framework.sync_mempools() # sync before any eviction
+ assert_equal(node.getmempoolinfo()["mempoolminfee"], Decimal("0.00001000"))
+ for fee in batch_fees[-3:]:
+ send_batch(fee)
+ tx_sync_fun() if tx_sync_fun else test_framework.sync_mempools() # sync after all evictions
test_framework.log.debug("The tx should be evicted by now")
# The number of transactions created should be greater than the ones present in the mempool
@@ -79,3 +84,8 @@ def fill_mempool(test_framework, node):
test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee")
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
+
+def tx_in_orphanage(node, tx: CTransaction) -> bool:
+ """Returns true if the transaction is in the orphanage."""
+ found = [o for o in node.getorphantxs(verbosity=1) if o["txid"] == tx.rehash() and o["wtxid"] == tx.getwtxid()]
+ return len(found) == 1
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 005f7546a8..1f566a1348 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -1294,8 +1294,11 @@ class msg_tx:
__slots__ = ("tx",)
msgtype = b"tx"
- def __init__(self, tx=CTransaction()):
- self.tx = tx
+ def __init__(self, tx=None):
+ if tx is None:
+ self.tx = CTransaction()
+ else:
+ self.tx = tx
def deserialize(self, f):
self.tx.deserialize(f)
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index 4b846df94a..4f1265eb54 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -223,6 +223,7 @@ class P2PConnection(asyncio.Protocol):
# send the initial handshake immediately
if self.supports_v2_p2p and self.v2_state.initiating and not self.v2_state.tried_v2_handshake:
send_handshake_bytes = self.v2_state.initiate_v2_handshake()
+ logger.debug(f"sending {len(self.v2_state.sent_garbage)} bytes of garbage data")
self.send_raw_message(send_handshake_bytes)
# for v1 outbound connections, send version message immediately after opening
# (for v2 outbound connections, send it after the initial v2 handshake)
@@ -262,6 +263,7 @@ class P2PConnection(asyncio.Protocol):
self.v2_state = None
return
elif send_handshake_bytes:
+ logger.debug(f"sending {len(self.v2_state.sent_garbage)} bytes of garbage data")
self.send_raw_message(send_handshake_bytes)
elif send_handshake_bytes == b"":
return # only after send_handshake_bytes are sent can `complete_handshake()` be done
diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py
index 97d62f957b..d510cf9b1c 100644
--- a/test/functional/test_framework/script.py
+++ b/test/functional/test_framework/script.py
@@ -810,7 +810,7 @@ def BIP341_sha_sequences(txTo):
def BIP341_sha_outputs(txTo):
return sha256(b"".join(o.serialize() for o in txTo.vout))
-def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index = 0, scriptpath = False, script = CScript(), codeseparator_pos = -1, annex = None, leaf_ver = LEAF_VERSION_TAPSCRIPT):
+def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index=0, *, scriptpath=False, leaf_script=None, 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
@@ -829,7 +829,7 @@ def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index = 0, scriptpat
spend_type = 0
if annex is not None:
spend_type |= 1
- if (scriptpath):
+ if scriptpath:
spend_type |= 2
ss += bytes([spend_type])
if in_type == SIGHASH_ANYONECANPAY:
@@ -846,11 +846,11 @@ def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index = 0, scriptpat
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))
+ if scriptpath:
+ ss += TaggedHash("TapLeaf", bytes([leaf_ver]) + ser_string(leaf_script))
ss += bytes([0])
ss += codeseparator_pos.to_bytes(4, "little", signed=True)
- 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
+ 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 ss
def TaprootSignatureHash(*args, **kwargs):
diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py
index 855f3b8cf5..938183ece4 100755
--- a/test/functional/test_framework/script_util.py
+++ b/test/functional/test_framework/script_util.py
@@ -8,6 +8,7 @@ import unittest
from test_framework.script import (
CScript,
OP_0,
+ OP_1,
OP_15,
OP_16,
OP_CHECKMULTISIG,
@@ -42,6 +43,8 @@ assert MIN_PADDING == 5
DUMMY_MIN_OP_RETURN_SCRIPT = CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 1)))
assert len(DUMMY_MIN_OP_RETURN_SCRIPT) == MIN_PADDING
+PAY_TO_ANCHOR = CScript([OP_1, bytes.fromhex("4e73")])
+
def key_to_p2pk_script(key):
key = check_key(key)
return CScript([key, OP_CHECKSIG])
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 9e44a11143..49212eb019 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -92,7 +92,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
This class also contains various public and private helper methods."""
- def __init__(self) -> None:
+ def __init__(self, test_file) -> None:
"""Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method"""
self.chain: str = 'regtest'
self.setup_clean_chain: bool = False
@@ -103,7 +103,7 @@ 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.parse_args()
+ self.parse_args(test_file)
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
@@ -155,14 +155,14 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
exit_code = self.shutdown()
sys.exit(exit_code)
- def parse_args(self):
+ def parse_args(self, test_file):
previous_releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases"
parser = argparse.ArgumentParser(usage="%(prog)s [options]")
parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true",
help="Leave bitcoinds and test.* datadir on exit or error")
parser.add_argument("--noshutdown", dest="noshutdown", default=False, action="store_true",
help="Don't stop bitcoinds after the test execution")
- parser.add_argument("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"),
+ parser.add_argument("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(test_file) + "/../cache"),
help="Directory for caching pregenerated datadirs (default: %(default)s)")
parser.add_argument("--tmpdir", dest="tmpdir", help="Root directory for datadirs (must not exist)")
parser.add_argument("-l", "--loglevel", dest="loglevel", default="INFO",
@@ -177,7 +177,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
parser.add_argument("--coveragedir", dest="coveragedir",
help="Write tested RPC commands into this directory")
parser.add_argument("--configfile", dest="configfile",
- default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../config.ini"),
+ default=os.path.abspath(os.path.dirname(test_file) + "/../config.ini"),
help="Location of the test framework config file (default: %(default)s)")
parser.add_argument("--pdbonfailure", dest="pdbonfailure", default=False, action="store_true",
help="Attach a python debugger if test fails")
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 0ac0af27d5..60ca9269a5 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -39,9 +39,15 @@ from .util import (
rpc_url,
wait_until_helper_internal,
p2p_port,
+ tor_port,
)
BITCOIND_PROC_WAIT_TIMEOUT = 60
+# The size of the blocks xor key
+# from InitBlocksdirXorKey::xor_key.size()
+NUM_XOR_BYTES = 8
+# The null blocks key (all 0s)
+NULL_BLK_XOR_KEY = bytes([0] * NUM_XOR_BYTES)
class FailedToStartError(Exception):
@@ -88,8 +94,11 @@ class TestNode():
self.coverage_dir = coverage_dir
self.cwd = cwd
self.descriptors = descriptors
+ self.has_explicit_bind = False
if extra_conf is not None:
append_config(self.datadir_path, extra_conf)
+ # Remember if there is bind=... in the config file.
+ self.has_explicit_bind = any(e.startswith("bind=") for e in extra_conf)
# Most callers will just need to add extra args to the standard list below.
# For those callers that need more flexibility, they can just set the args property directly.
# Note that common args are set in the config file (see initialize_datadir)
@@ -210,6 +219,17 @@ class TestNode():
if extra_args is None:
extra_args = self.extra_args
+ # If listening and no -bind is given, then bitcoind would bind P2P ports on
+ # 0.0.0.0:P and 127.0.0.1:18445 (for incoming Tor connections), where P is
+ # a unique port chosen by the test framework and configured as port=P in
+ # bitcoin.conf. To avoid collisions on 127.0.0.1:18445, change it to
+ # 127.0.0.1:tor_port().
+ will_listen = all(e != "-nolisten" and e != "-listen=0" for e in extra_args)
+ has_explicit_bind = self.has_explicit_bind or any(e.startswith("-bind=") for e in extra_args)
+ if will_listen and not has_explicit_bind:
+ extra_args.append(f"-bind=0.0.0.0:{p2p_port(self.index)}")
+ extra_args.append(f"-bind=127.0.0.1:{tor_port(self.index)}=onion")
+
self.use_v2transport = "-v2transport=1" in extra_args or (self.default_to_v2 and "-v2transport=0" not in extra_args)
# Add a new stdout and stderr file each time bitcoind is started
@@ -451,6 +471,14 @@ class TestNode():
return self.chain_path / "blocks"
@property
+ def blocks_key_path(self) -> Path:
+ return self.blocks_path / "xor.dat"
+
+ def read_xor_key(self) -> bytes:
+ with open(self.blocks_key_path, "rb") as xor_f:
+ return xor_f.read(NUM_XOR_BYTES)
+
+ @property
def wallets_path(self) -> Path:
return self.chain_path / "wallets"
@@ -666,7 +694,7 @@ class TestNode():
assert_msg += "with expected error " + expected_msg
self._raise_assertion_error(assert_msg)
- def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, send_version=True, supports_v2_p2p=None, wait_for_v2_handshake=True, **kwargs):
+ def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, send_version=True, supports_v2_p2p=None, wait_for_v2_handshake=True, expect_success=True, **kwargs):
"""Add an inbound p2p connection to the node.
This method adds the p2p connection to the self.p2ps list and also
@@ -686,7 +714,6 @@ class TestNode():
if supports_v2_p2p is None:
supports_v2_p2p = self.use_v2transport
-
p2p_conn.p2p_connected_to_node = True
if self.use_v2transport:
kwargs['services'] = kwargs.get('services', P2P_SERVICES) | NODE_P2P_V2
@@ -694,6 +721,8 @@ class TestNode():
p2p_conn.peer_connect(**kwargs, send_version=send_version, net=self.chain, timeout_factor=self.timeout_factor, supports_v2_p2p=supports_v2_p2p)()
self.p2ps.append(p2p_conn)
+ if not expect_success:
+ return p2p_conn
p2p_conn.wait_until(lambda: p2p_conn.is_connected, check_connected=False)
if supports_v2_p2p and wait_for_v2_handshake:
p2p_conn.wait_until(lambda: p2p_conn.v2_state.tried_v2_handshake)
diff --git a/test/functional/test_framework/test_shell.py b/test/functional/test_framework/test_shell.py
index 09ccec28a1..e232430507 100644
--- a/test/functional/test_framework/test_shell.py
+++ b/test/functional/test_framework/test_shell.py
@@ -2,9 +2,11 @@
# Copyright (c) 2019-2022 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+import pathlib
from test_framework.test_framework import BitcoinTestFramework
+
class TestShell:
"""Wrapper Class for BitcoinTestFramework.
@@ -67,7 +69,13 @@ class TestShell:
# This implementation enforces singleton pattern, and will return the
# previously initialized instance if available
if not TestShell.instance:
- TestShell.instance = TestShell.__TestShell()
+ # BitcoinTestFramework instances are supposed to be constructed with the path
+ # of the calling test in order to find shared data like configuration and the
+ # cache. Since TestShell is meant for interactive use, there is no concrete
+ # test; passing a dummy name is fine though, as only the containing directory
+ # is relevant for successful initialization.
+ tests_directory = pathlib.Path(__file__).resolve().parent.parent
+ TestShell.instance = TestShell.__TestShell(tests_directory / "testshell_dummy.py")
TestShell.instance.running = False
return TestShell.instance
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index c5b69a3954..ce68de7eaa 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -5,7 +5,7 @@
"""Helpful routines for regression testing."""
from base64 import b64encode
-from decimal import Decimal, ROUND_DOWN
+from decimal import Decimal
from subprocess import CalledProcessError
import hashlib
import inspect
@@ -14,13 +14,16 @@ import logging
import os
import pathlib
import platform
+import random
import re
import time
from . import coverage
from .authproxy import AuthServiceProxy, JSONRPCException
from collections.abc import Callable
-from typing import Optional
+from typing import Optional, Union
+
+SATOSHI_PRECISION = Decimal('0.00000001')
logger = logging.getLogger("TestFramework.utils")
@@ -247,6 +250,12 @@ def ceildiv(a, b):
return -(-a // b)
+def random_bitflip(data):
+ data = list(data)
+ data[random.randrange(len(data))] ^= (1 << (random.randrange(8)))
+ return bytes(data)
+
+
def get_fee(tx_size, feerate_btc_kvb):
"""Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee"""
feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors
@@ -254,8 +263,9 @@ def get_fee(tx_size, feerate_btc_kvb):
return target_fee_sat / Decimal(1e8) # Return result in BTC
-def satoshi_round(amount):
- return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
+def satoshi_round(amount: Union[int, float, str], *, rounding: str) -> Decimal:
+ """Rounds a Decimal amount to the nearest satoshi using the specified rounding mode."""
+ return Decimal(amount).quantize(SATOSHI_PRECISION, rounding=rounding)
def wait_until_helper_internal(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0):
@@ -304,14 +314,21 @@ def sha256sum_file(filename):
return h.digest()
+def util_xor(data, key, *, offset):
+ data = bytearray(data)
+ for i in range(len(data)):
+ data[i] ^= key[(i + offset) % len(key)]
+ return bytes(data)
+
+
# RPC/P2P connection constants and functions
############################################
# The maximum number of nodes a single test can spawn
MAX_NODES = 12
-# Don't assign rpc or p2p ports lower than this
+# Don't assign p2p, rpc or tor ports lower than this
PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=11000))
-# The number of ports to "reserve" for p2p and rpc, each
+# The number of ports to "reserve" for p2p, rpc and tor, each
PORT_RANGE = 5000
@@ -351,7 +368,11 @@ def p2p_port(n):
def rpc_port(n):
- return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
+ return p2p_port(n) + PORT_RANGE
+
+
+def tor_port(n):
+ return p2p_port(n) + PORT_RANGE * 2
def rpc_url(datadir, i, chain, rpchost):
diff --git a/test/functional/test_framework/v2_p2p.py b/test/functional/test_framework/v2_p2p.py
index 8f79623bd8..87600c36de 100644
--- a/test/functional/test_framework/v2_p2p.py
+++ b/test/functional/test_framework/v2_p2p.py
@@ -4,7 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Class for v2 P2P protocol (see BIP 324)"""
-import logging
import random
from .crypto.bip324_cipher import FSChaCha20Poly1305
@@ -14,14 +13,12 @@ from .crypto.hkdf import hkdf_sha256
from .key import TaggedHash
from .messages import MAGIC_BYTES
-logger = logging.getLogger("TestFramework.v2_p2p")
CHACHA20POLY1305_EXPANSION = 16
HEADER_LEN = 1
IGNORE_BIT_POS = 7
LENGTH_FIELD_LEN = 3
MAX_GARBAGE_LEN = 4095
-TRANSPORT_VERSION = b''
SHORTID = {
1: b"addr",
@@ -95,6 +92,7 @@ class EncryptedP2PState:
# has been decrypted. set to -1 if decryption hasn't been done yet.
self.contents_len = -1
self.found_garbage_terminator = False
+ self.transport_version = b''
@staticmethod
def v2_ecdh(priv, ellswift_theirs, ellswift_ours, initiating):
@@ -111,12 +109,12 @@ class EncryptedP2PState:
# Responding, place their public key encoding first.
return TaggedHash("bip324_ellswift_xonly_ecdh", ellswift_theirs + ellswift_ours + ecdh_point_x32)
- def generate_keypair_and_garbage(self):
+ def generate_keypair_and_garbage(self, garbage_len=None):
"""Generates ellswift keypair and 4095 bytes garbage at max"""
self.privkey_ours, self.ellswift_ours = ellswift_create()
- garbage_len = random.randrange(MAX_GARBAGE_LEN + 1)
+ if garbage_len is None:
+ garbage_len = random.randrange(MAX_GARBAGE_LEN + 1)
self.sent_garbage = random.randbytes(garbage_len)
- logger.debug(f"sending {garbage_len} bytes of garbage data")
return self.ellswift_ours + self.sent_garbage
def initiate_v2_handshake(self):
@@ -172,7 +170,7 @@ class EncryptedP2PState:
msg_to_send += self.v2_enc_packet(decoy_content_len * b'\x00', aad=aad, ignore=True)
aad = b''
# Send version packet.
- msg_to_send += self.v2_enc_packet(TRANSPORT_VERSION, aad=aad)
+ msg_to_send += self.v2_enc_packet(self.transport_version, aad=aad)
return 64 - len(self.received_prefix), msg_to_send
def authenticate_handshake(self, response):
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index cb0d291361..1cef714705 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -7,7 +7,6 @@
from copy import deepcopy
from decimal import Decimal
from enum import Enum
-import math
from typing import (
Any,
Optional,
@@ -35,11 +34,9 @@ from test_framework.messages import (
CTxOut,
hash256,
ser_compact_size,
- WITNESS_SCALE_FACTOR,
)
from test_framework.script import (
CScript,
- LEAF_VERSION_TAPSCRIPT,
OP_1,
OP_NOP,
OP_RETURN,
@@ -106,8 +103,8 @@ class MiniWallet:
pub_key = self._priv_key.get_pubkey()
self._scriptPubKey = key_to_p2pk_script(pub_key.get_bytes())
elif mode == MiniWalletMode.ADDRESS_OP_TRUE:
- internal_key = None if tag_name is None else hash256(tag_name.encode())
- self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true(internal_key)
+ internal_key = None if tag_name is None else compute_xonly_pubkey(hash256(tag_name.encode()))[0]
+ self._address, self._taproot_info = create_deterministic_address_bcrt1_p2tr_op_true(internal_key)
self._scriptPubKey = address_to_scriptpubkey(self._address)
# When the pre-mined test framework chain is used, it contains coinbase
@@ -120,20 +117,18 @@ class MiniWallet:
def _create_utxo(self, *, txid, vout, value, height, coinbase, confirmations):
return {"txid": txid, "vout": vout, "value": value, "height": height, "coinbase": coinbase, "confirmations": confirmations}
- def _bulk_tx(self, tx, target_weight):
- """Pad a transaction with extra outputs until it reaches a target weight (or higher).
+ def _bulk_tx(self, tx, target_vsize):
+ """Pad a transaction with extra outputs until it reaches a target vsize.
returns the tx
"""
tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN])))
- # determine number of needed padding bytes by converting weight difference to vbytes
- dummy_vbytes = (target_weight - tx.get_weight() + 3) // 4
+ # determine number of needed padding bytes
+ dummy_vbytes = target_vsize - tx.get_vsize()
# compensate for the increase of the compact-size encoded script length
# (note that the length encoding of the unpadded output script needs one byte)
dummy_vbytes -= len(ser_compact_size(dummy_vbytes)) - 1
tx.vout[-1].scriptPubKey = CScript([OP_RETURN] + [OP_1] * dummy_vbytes)
- # Actual weight should be at most 3 higher than target weight
- assert_greater_than_or_equal(tx.get_weight(), target_weight)
- assert_greater_than_or_equal(target_weight + 3, tx.get_weight())
+ assert_equal(tx.get_vsize(), target_vsize)
def get_balance(self):
return sum(u['value'] for u in self._utxos)
@@ -195,7 +190,12 @@ class MiniWallet:
elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE:
tx.wit.vtxinwit = [CTxInWitness()] * len(tx.vin)
for i in tx.wit.vtxinwit:
- i.scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key]
+ assert_equal(len(self._taproot_info.leaves), 1)
+ leaf_info = list(self._taproot_info.leaves.values())[0]
+ i.scriptWitness.stack = [
+ leaf_info.script,
+ bytes([leaf_info.version | self._taproot_info.negflag]) + self._taproot_info.internal_pubkey,
+ ]
else:
assert False
@@ -305,7 +305,7 @@ class MiniWallet:
locktime=0,
sequence=0,
fee_per_output=1000,
- target_weight=0,
+ target_vsize=0,
confirmed_only=False,
):
"""
@@ -334,8 +334,8 @@ class MiniWallet:
self.sign_tx(tx)
- if target_weight:
- self._bulk_tx(tx, target_weight)
+ if target_vsize:
+ self._bulk_tx(tx, target_vsize)
txid = tx.rehash()
return {
@@ -360,7 +360,7 @@ class MiniWallet:
fee_rate=Decimal("0.003"),
fee=Decimal("0"),
utxo_to_spend=None,
- target_weight=0,
+ target_vsize=0,
confirmed_only=False,
**kwargs,
):
@@ -375,20 +375,18 @@ class MiniWallet:
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
else:
assert False
- if target_weight and not fee: # respect fee_rate if target weight is passed
- # the actual weight might be off by 3 WUs, so calculate based on that (see self._bulk_tx)
- max_actual_weight = target_weight + 3
- fee = get_fee(math.ceil(max_actual_weight / WITNESS_SCALE_FACTOR), fee_rate)
+ if target_vsize and not fee: # respect fee_rate if target vsize is passed
+ fee = get_fee(target_vsize, fee_rate)
send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000))
# create tx
tx = self.create_self_transfer_multi(
utxos_to_spend=[utxo_to_spend],
amount_per_output=int(COIN * send_value),
- target_weight=target_weight,
+ target_vsize=target_vsize,
**kwargs,
)
- if not target_weight:
+ if not target_vsize:
assert_equal(tx["tx"].get_vsize(), vsize)
tx["new_utxo"] = tx.pop("new_utxos")[0]
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 8475dc5faa..3d8c230066 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -96,10 +96,13 @@ BASE_SCRIPTS = [
'feature_fee_estimation.py',
'feature_taproot.py',
'feature_block.py',
+ 'p2p_node_network_limited.py --v1transport',
+ 'p2p_node_network_limited.py --v2transport',
# vv Tests less than 2m vv
'mining_getblocktemplate_longpoll.py',
'p2p_segwit.py',
'feature_maxuploadtarget.py',
+ 'feature_assumeutxo.py',
'mempool_updatefromblock.py',
'mempool_persist.py --descriptors',
# vv Tests less than 60s vv
@@ -157,6 +160,7 @@ BASE_SCRIPTS = [
'wallet_importmulti.py --legacy-wallet',
'mempool_limit.py',
'rpc_txoutproof.py',
+ 'rpc_getorphantxs.py',
'wallet_listreceivedby.py --legacy-wallet',
'wallet_listreceivedby.py --descriptors',
'wallet_abandonconflict.py --legacy-wallet',
@@ -264,9 +268,9 @@ BASE_SCRIPTS = [
'p2p_invalid_tx.py --v2transport',
'p2p_v2_transport.py',
'p2p_v2_encrypted.py',
- 'p2p_v2_earlykeyresponse.py',
+ 'p2p_v2_misbehaving.py',
'example_test.py',
- 'mempool_accept_v3.py',
+ 'mempool_truc.py',
'wallet_txn_doublespend.py --legacy-wallet',
'wallet_multisig_descriptor_psbt.py --descriptors',
'wallet_txn_doublespend.py --descriptors',
@@ -284,6 +288,7 @@ BASE_SCRIPTS = [
'mempool_package_limits.py',
'mempool_package_rbf.py',
'feature_versionbits_warning.py',
+ 'feature_blocksxor.py',
'rpc_preciousblock.py',
'wallet_importprunedfunds.py --legacy-wallet',
'wallet_importprunedfunds.py --descriptors',
@@ -353,7 +358,6 @@ BASE_SCRIPTS = [
'wallet_coinbase_category.py --descriptors',
'feature_filelock.py',
'feature_loadblock.py',
- 'feature_assumeutxo.py',
'wallet_assumeutxo.py --descriptors',
'p2p_dos_header_tree.py',
'p2p_add_connections.py',
@@ -384,8 +388,6 @@ BASE_SCRIPTS = [
'feature_coinstatsindex.py',
'wallet_orphanedreward.py',
'wallet_timelock.py',
- 'p2p_node_network_limited.py --v1transport',
- 'p2p_node_network_limited.py --v2transport',
'p2p_permissions.py',
'feature_blocksdir.py',
'wallet_startup.py',
@@ -405,6 +407,7 @@ BASE_SCRIPTS = [
'feature_shutdown.py',
'wallet_migration.py',
'p2p_ibd_txrelay.py',
+ 'p2p_seednode.py',
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
]
@@ -444,8 +447,8 @@ def main():
help="Leave bitcoinds and test.* datadir on exit or error")
parser.add_argument('--resultsfile', '-r', help='store test results (as CSV) to the provided file')
-
args, unknown_args = parser.parse_known_args()
+ fail_on_warn = args.ci
if not args.ansi:
global DEFAULT, BOLD, GREEN, RED
DEFAULT = ("", "")
@@ -486,7 +489,7 @@ def main():
if not enable_bitcoind:
print("No functional tests to run.")
- print("Rerun ./configure with --with-daemon and then make")
+ print("Re-compile with the -DBUILD_DAEMON=ON build option")
sys.exit(1)
# Build list of tests
@@ -520,15 +523,28 @@ def main():
test_list += BASE_SCRIPTS
# Remove the test cases that the user has explicitly asked to exclude.
+ # The user can specify a test case with or without the .py extension.
if args.exclude:
- exclude_tests = [test.split('.py')[0] for test in args.exclude.split(',')]
- for exclude_test in exclude_tests:
- # Remove <test_name>.py and <test_name>.py --arg from the test list
- exclude_list = [test for test in test_list if test.split('.py')[0] == exclude_test]
+
+ def print_warning_missing_test(test_name):
+ print("{}WARNING!{} Test '{}' not found in current test list. Check the --exclude list.".format(BOLD[1], BOLD[0], test_name))
+ if fail_on_warn:
+ sys.exit(1)
+
+ def remove_tests(exclude_list):
+ if not exclude_list:
+ print_warning_missing_test(exclude_test)
for exclude_item in exclude_list:
test_list.remove(exclude_item)
- if not exclude_list:
- print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], exclude_test))
+
+ exclude_tests = [test.strip() for test in args.exclude.split(",")]
+ for exclude_test in exclude_tests:
+ # A space in the name indicates it has arguments such as "wallet_basic.py --descriptors"
+ if ' ' in exclude_test:
+ remove_tests([test for test in test_list if test.replace('.py', '') == exclude_test.replace('.py', '')])
+ else:
+ # Exclude all variants of a test
+ remove_tests([test for test in test_list if test.split('.py')[0] == exclude_test.split('.py')[0]])
if args.filter:
test_list = list(filter(re.compile(args.filter).search, test_list))
@@ -551,7 +567,7 @@ def main():
f"A minimum of {MIN_NO_CLEANUP_SPACE // (1024 * 1024 * 1024)} GB of free space is required.")
passon_args.append("--nocleanup")
- check_script_list(src_dir=config["environment"]["SRCDIR"], fail_on_warn=args.ci)
+ check_script_list(src_dir=config["environment"]["SRCDIR"], fail_on_warn=fail_on_warn)
check_script_prefixes()
if not args.keepcache:
@@ -559,7 +575,6 @@ def main():
run_tests(
test_list=test_list,
- src_dir=config["environment"]["SRCDIR"],
build_dir=config["environment"]["BUILDDIR"],
tmpdir=tmpdir,
jobs=args.jobs,
@@ -571,7 +586,7 @@ def main():
results_filepath=results_filepath,
)
-def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, results_filepath=None):
+def run_tests(*, test_list, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, results_filepath=None):
args = args or []
# Warn if bitcoind is already running
@@ -594,7 +609,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
print(f"{BOLD[1]}WARNING!{BOLD[0]} There may be insufficient free space in {tmpdir} to run the Bitcoin functional test suite. "
f"Running the test suite with fewer than {min_space // (1024 * 1024)} MB of free space might cause tests to fail.")
- tests_dir = src_dir + '/test/functional/'
+ tests_dir = f"{build_dir}/test/functional/"
# This allows `test_runner.py` to work from an out-of-source build directory using a symlink,
# a hard link or a copy on any platform. See https://github.com/bitcoin/bitcoin/pull/27561.
sys.path.append(tests_dir)
@@ -861,7 +876,6 @@ def check_script_list(*, src_dir, fail_on_warn):
if len(missed_tests) != 0:
print("%sWARNING!%s The following scripts are not being run: %s. Check the test lists in test_runner.py." % (BOLD[1], BOLD[0], str(missed_tests)))
if fail_on_warn:
- # On CI this warning is an error to prevent merging incomplete commits into master
sys.exit(1)
diff --git a/test/functional/tool_signet_miner.py b/test/functional/tool_signet_miner.py
index 1ad2a579bf..67fb5c9f94 100755
--- a/test/functional/tool_signet_miner.py
+++ b/test/functional/tool_signet_miner.py
@@ -57,9 +57,10 @@ class SignetMinerTest(BitcoinTestFramework):
f'--grind-cmd={self.options.bitcoinutil} grind',
'--nbits=1d00ffff',
f'--set-block-time={int(time.time())}',
+ '--poolnum=99',
], check=True, stderr=subprocess.STDOUT)
assert_equal(node.getblockcount(), 1)
if __name__ == "__main__":
- SignetMinerTest().main()
+ SignetMinerTest(__file__).main()
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index dcf74f6075..784a769882 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -563,4 +563,4 @@ class ToolWalletTest(BitcoinTestFramework):
self.test_dump_very_large_records()
if __name__ == '__main__':
- ToolWalletTest().main()
+ ToolWalletTest(__file__).main()
diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py
index dda48aae1b..ce0f4d099b 100755
--- a/test/functional/wallet_abandonconflict.py
+++ b/test/functional/wallet_abandonconflict.py
@@ -241,4 +241,4 @@ class AbandonConflictTest(BitcoinTestFramework):
assert_equal(newbalance, balance - Decimal("20"))
if __name__ == '__main__':
- AbandonConflictTest().main()
+ AbandonConflictTest(__file__).main()
diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py
index 6b27b32dea..e326d3e89e 100755
--- a/test/functional/wallet_address_types.py
+++ b/test/functional/wallet_address_types.py
@@ -387,4 +387,4 @@ class AddressTypeTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Legacy wallets cannot provide bech32m addresses", self.nodes[0].getrawchangeaddress, "bech32m")
if __name__ == '__main__':
- AddressTypeTest().main()
+ AddressTypeTest(__file__).main()
diff --git a/test/functional/wallet_assumeutxo.py b/test/functional/wallet_assumeutxo.py
index 30396da015..76cd2097a3 100755
--- a/test/functional/wallet_assumeutxo.py
+++ b/test/functional/wallet_assumeutxo.py
@@ -11,7 +11,9 @@ See feature_assumeutxo.py for background.
- TODO: test loading a wallet (backup) on a pruned node
"""
+from test_framework.address import address_to_scriptpubkey
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.messages import COIN
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
@@ -62,8 +64,16 @@ class AssumeutxoTest(BitcoinTestFramework):
for n in self.nodes:
n.setmocktime(n.getblockheader(n.getbestblockhash())['time'])
+ # Create a wallet that we will create a backup for later (at snapshot height)
n0.createwallet('w')
w = n0.get_wallet_rpc("w")
+ w_address = w.getnewaddress()
+
+ # Create another wallet and backup now (before snapshot height)
+ n0.createwallet('w2')
+ w2 = n0.get_wallet_rpc("w2")
+ w2_address = w2.getnewaddress()
+ w2.backupwallet("backup_w2.dat")
# Generate a series of blocks that `n0` will have in the snapshot,
# but that n1 doesn't yet see. In order for the snapshot to activate,
@@ -84,6 +94,8 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(n.getblockchaininfo()[
"headers"], SNAPSHOT_BASE_HEIGHT)
+ # This backup is created at the snapshot height, so it's
+ # not part of the background sync anymore
w.backupwallet("backup_w.dat")
self.log.info("-- Testing assumeutxo")
@@ -93,7 +105,7 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info(
f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
- dump_output = n0.dumptxoutset('utxos.dat')
+ dump_output = n0.dumptxoutset('utxos.dat', "latest")
assert_equal(
dump_output['txoutset_hash'],
@@ -103,7 +115,13 @@ class AssumeutxoTest(BitcoinTestFramework):
# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
# will allow us to test n1's sync-to-tip on top of a snapshot.
- self.generate(n0, nblocks=100, sync_fun=self.no_op)
+ w_skp = address_to_scriptpubkey(w_address)
+ w2_skp = address_to_scriptpubkey(w2_address)
+ for i in range(100):
+ if i % 3 == 0:
+ self.mini_wallet.send_to(from_node=n0, scriptPubKey=w_skp, amount=1 * COIN)
+ self.mini_wallet.send_to(from_node=n0, scriptPubKey=w2_skp, amount=10 * COIN)
+ self.generate(n0, nblocks=1, sync_fun=self.no_op)
assert_equal(n0.getblockcount(), FINAL_HEIGHT)
assert_equal(n1.getblockcount(), START_HEIGHT)
@@ -126,8 +144,13 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(n1.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
- self.log.info("Backup can't be loaded during background sync")
- assert_raises_rpc_error(-4, "Wallet loading failed. Error loading wallet. Wallet requires blocks to be downloaded, and software does not currently support loading wallets while blocks are being downloaded out of order when using assumeutxo snapshots. Wallet should be able to load successfully after node sync reaches height 299", n1.restorewallet, "w", "backup_w.dat")
+ self.log.info("Backup from the snapshot height can be loaded during background sync")
+ n1.restorewallet("w", "backup_w.dat")
+ # Balance of w wallet is still still 0 because n1 has not synced yet
+ assert_equal(n1.getbalance(), 0)
+
+ self.log.info("Backup from before the snapshot height can't be loaded during background sync")
+ assert_raises_rpc_error(-4, "Wallet loading failed. Error loading wallet. Wallet requires blocks to be downloaded, and software does not currently support loading wallets while blocks are being downloaded out of order when using assumeutxo snapshots. Wallet should be able to load successfully after node sync reaches height 299", n1.restorewallet, "w2", "backup_w2.dat")
PAUSE_HEIGHT = FINAL_HEIGHT - 40
@@ -159,9 +182,16 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info("Ensuring background validation completes")
self.wait_until(lambda: len(n1.getchainstates()['chainstates']) == 1)
- self.log.info("Ensuring wallet can be restored from backup")
- n1.restorewallet("w", "backup_w.dat")
+ self.log.info("Ensuring wallet can be restored from a backup that was created before the snapshot height")
+ n1.restorewallet("w2", "backup_w2.dat")
+ # Check balance of w2 wallet
+ assert_equal(n1.getbalance(), 340)
+
+ # Check balance of w wallet after node is synced
+ n1.loadwallet("w")
+ w = n1.get_wallet_rpc("w")
+ assert_equal(w.getbalance(), 34)
if __name__ == '__main__':
- AssumeutxoTest().main()
+ AssumeutxoTest(__file__).main()
diff --git a/test/functional/wallet_avoid_mixing_output_types.py b/test/functional/wallet_avoid_mixing_output_types.py
index 66fbf780e5..146b3df3f4 100755
--- a/test/functional/wallet_avoid_mixing_output_types.py
+++ b/test/functional/wallet_avoid_mixing_output_types.py
@@ -177,4 +177,4 @@ class AddressInputTypeGrouping(BitcoinTestFramework):
if __name__ == '__main__':
- AddressInputTypeGrouping().main()
+ AddressInputTypeGrouping(__file__).main()
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
index 4983bfda7f..bba79d0a25 100755
--- a/test/functional/wallet_avoidreuse.py
+++ b/test/functional/wallet_avoidreuse.py
@@ -381,4 +381,4 @@ class AvoidReuseTest(BitcoinTestFramework):
if __name__ == '__main__':
- AvoidReuseTest().main()
+ AvoidReuseTest(__file__).main()
diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py
index d03b08bcc4..83267f77e1 100755
--- a/test/functional/wallet_backup.py
+++ b/test/functional/wallet_backup.py
@@ -140,6 +140,25 @@ class WalletBackupTest(BitcoinTestFramework):
assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file)
assert wallet_file.exists()
+ def test_pruned_wallet_backup(self):
+ self.log.info("Test loading backup on a pruned node when the backup was created close to the prune height of the restoring node")
+ node = self.nodes[3]
+ self.restart_node(3, ["-prune=1", "-fastprune=1"])
+ # Ensure the chain tip is at height 214, because this test assume it is.
+ assert_equal(node.getchaintips()[0]["height"], 214)
+ # We need a few more blocks so we can actually get above an realistic
+ # minimal prune height
+ self.generate(node, 50, sync_fun=self.no_op)
+ # Backup created at block height 264
+ node.backupwallet(node.datadir_path / 'wallet_pruned.bak')
+ # Generate more blocks so we can actually prune the older blocks
+ self.generate(node, 300, sync_fun=self.no_op)
+ # This gives us an actual prune height roughly in the range of 220 - 240
+ node.pruneblockchain(250)
+ # The backup should be updated with the latest height (locator) for
+ # the backup to load successfully this close to the prune height
+ node.restorewallet(f'pruned', node.datadir_path / 'wallet_pruned.bak')
+
def run_test(self):
self.log.info("Generating initial blockchain")
self.generate(self.nodes[0], 1)
@@ -242,6 +261,8 @@ class WalletBackupTest(BitcoinTestFramework):
for sourcePath in sourcePaths:
assert_raises_rpc_error(-4, "backup failed", self.nodes[0].backupwallet, sourcePath)
+ self.test_pruned_wallet_backup()
+
if __name__ == '__main__':
- WalletBackupTest().main()
+ WalletBackupTest(__file__).main()
diff --git a/test/functional/wallet_backwards_compatibility.py b/test/functional/wallet_backwards_compatibility.py
index ab008a40cd..775786fbb1 100755
--- a/test/functional/wallet_backwards_compatibility.py
+++ b/test/functional/wallet_backwards_compatibility.py
@@ -33,7 +33,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
- self.num_nodes = 12
+ self.num_nodes = 11
# Add new version after each release:
self.extra_args = [
["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay
@@ -47,7 +47,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.19.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=127.0.0.1"], # v0.18.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=127.0.0.1"], # v0.17.2
- ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=127.0.0.1", "-wallet=wallet.dat"], # v0.16.3
]
self.wallet_names = [self.default_wallet_name]
@@ -68,7 +67,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
190100,
180100,
170200,
- 160300,
])
self.start_nodes()
@@ -133,18 +131,17 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
def run_test(self):
node_miner = self.nodes[0]
node_master = self.nodes[1]
- node_v21 = self.nodes[self.num_nodes - 6]
- node_v17 = self.nodes[self.num_nodes - 2]
- node_v16 = self.nodes[self.num_nodes - 1]
+ node_v21 = self.nodes[self.num_nodes - 5]
+ node_v17 = self.nodes[self.num_nodes - 1]
legacy_nodes = self.nodes[2:] # Nodes that support legacy wallets
- legacy_only_nodes = self.nodes[-5:] # Nodes that only support legacy wallets
- descriptors_nodes = self.nodes[2:-5] # Nodes that support descriptor wallets
+ legacy_only_nodes = self.nodes[-4:] # Nodes that only support legacy wallets
+ descriptors_nodes = self.nodes[2:-4] # Nodes that support descriptor wallets
self.generatetoaddress(node_miner, COINBASE_MATURITY + 1, node_miner.getnewaddress())
# Sanity check the test framework:
- res = node_v16.getblockchaininfo()
+ res = node_v17.getblockchaininfo()
assert_equal(res['blocks'], COINBASE_MATURITY + 1)
self.log.info("Test wallet backwards compatibility...")
@@ -215,9 +212,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# In descriptors wallet mode, run this test on the nodes that support descriptor wallets
# In legacy wallets mode, run this test on the nodes that support legacy wallets
for node in descriptors_nodes if self.options.descriptors else legacy_nodes:
- if self.major_version_less_than(node, 17):
- # loadwallet was introduced in v0.17.0
- continue
self.log.info(f"- {node.version}")
for wallet_name in ["w1", "w2", "w3"]:
if self.major_version_less_than(node, 18) and wallet_name == "w3":
@@ -290,15 +284,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core")
self.start_node(node_v17.index)
- # No wallet created in master can be opened in 0.16
- self.log.info("Test that wallets created in master are too new for 0.16")
- self.stop_node(node_v16.index)
- for wallet_name in ["w1", "w2", "w3"]:
- if self.options.descriptors:
- node_v16.assert_start_raises_init_error([f"-wallet={wallet_name}"], f"Error: {wallet_name} corrupt, salvage failed")
- else:
- node_v16.assert_start_raises_init_error([f"-wallet={wallet_name}"], f"Error: Error loading {wallet_name}: Wallet requires newer version of Bitcoin Core")
-
# When descriptors are enabled, w1 cannot be opened by 0.21 since it contains a taproot descriptor
if self.options.descriptors:
self.log.info("Test that 0.21 cannot open wallet containing tr() descriptors")
@@ -400,4 +385,4 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
assert_equal(info, addr_info)
if __name__ == '__main__':
- BackwardsCompatibilityTest().main()
+ BackwardsCompatibilityTest(__file__).main()
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 2c85773bf3..9da53402a4 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -339,4 +339,4 @@ class WalletTest(BitcoinTestFramework):
assert_equal(tx_info['lastprocessedblock']['hash'], prev_hash)
if __name__ == '__main__':
- WalletTest().main()
+ WalletTest(__file__).main()
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 1b2b8ec1f3..c968e4333a 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -818,4 +818,4 @@ class WalletTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletTest().main()
+ WalletTest(__file__).main()
diff --git a/test/functional/wallet_blank.py b/test/functional/wallet_blank.py
index e646d27005..76f9cb05ee 100755
--- a/test/functional/wallet_blank.py
+++ b/test/functional/wallet_blank.py
@@ -160,4 +160,4 @@ class WalletBlankTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletBlankTest().main()
+ WalletBlankTest(__file__).main()
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 6d45adc823..061e9f2caa 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -848,10 +848,10 @@ def test_bumpfee_with_feerate_ignores_walletincrementalrelayfee(self, rbf_node,
# less than (original fee + incrementalrelayfee)
assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 2.8})
- # You can fee bump as long as the new fee set from fee_rate is atleast (original fee + incrementalrelayfee)
+ # You can fee bump as long as the new fee set from fee_rate is at least (original fee + incrementalrelayfee)
rbf_node.bumpfee(tx["txid"], {"fee_rate": 3})
self.clear_mempool()
if __name__ == "__main__":
- BumpFeeTest().main()
+ BumpFeeTest(__file__).main()
diff --git a/test/functional/wallet_change_address.py b/test/functional/wallet_change_address.py
index f8bfe9eebf..07bab82697 100755
--- a/test/functional/wallet_change_address.py
+++ b/test/functional/wallet_change_address.py
@@ -105,4 +105,4 @@ class WalletChangeAddressTest(BitcoinTestFramework):
self.assert_change_pos(w1, tx, 0)
if __name__ == '__main__':
- WalletChangeAddressTest().main()
+ WalletChangeAddressTest(__file__).main()
diff --git a/test/functional/wallet_coinbase_category.py b/test/functional/wallet_coinbase_category.py
index c2cb0bf3b0..f09ed4913a 100755
--- a/test/functional/wallet_coinbase_category.py
+++ b/test/functional/wallet_coinbase_category.py
@@ -60,4 +60,4 @@ class CoinbaseCategoryTest(BitcoinTestFramework):
self.assert_category("orphan", address, txid, 100)
if __name__ == '__main__':
- CoinbaseCategoryTest().main()
+ CoinbaseCategoryTest(__file__).main()
diff --git a/test/functional/wallet_conflicts.py b/test/functional/wallet_conflicts.py
index e5739a6a59..7a950ffae6 100755
--- a/test/functional/wallet_conflicts.py
+++ b/test/functional/wallet_conflicts.py
@@ -9,7 +9,6 @@ Test that wallet correctly tracks transactions that have been conflicted by bloc
from decimal import Decimal
-from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -37,7 +36,6 @@ class TxConflicts(BitcoinTestFramework):
"""
self.test_block_conflicts()
- self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 7, self.nodes[2].getnewaddress())
self.test_mempool_conflict()
self.test_mempool_and_block_conflicts()
self.test_descendants_with_mempool_conflicts()
@@ -425,4 +423,4 @@ class TxConflicts(BitcoinTestFramework):
carol.unloadwallet()
if __name__ == '__main__':
- TxConflicts().main()
+ TxConflicts(__file__).main()
diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py
index fa3e920c25..6deb262c9a 100755
--- a/test/functional/wallet_create_tx.py
+++ b/test/functional/wallet_create_tx.py
@@ -114,19 +114,19 @@ class CreateTxWalletTest(BitcoinTestFramework):
self.log.info('Check wallet does not create transactions with version=3 yet')
wallet_rpc = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
- self.nodes[0].createwallet("v3")
- wallet_v3 = self.nodes[0].get_wallet_rpc("v3")
+ self.nodes[0].createwallet("version3")
+ wallet_v3 = self.nodes[0].get_wallet_rpc("version3")
tx_data = wallet_rpc.send(outputs=[{wallet_v3.getnewaddress(): 25}], options={"change_position": 0})
wallet_tx_data = wallet_rpc.gettransaction(tx_data["txid"])
tx_current_version = tx_from_hex(wallet_tx_data["hex"])
- # While v3 transactions are standard, the CURRENT_VERSION is 2.
+ # While version=3 transactions are standard, the CURRENT_VERSION is 2.
# This test can be removed if CURRENT_VERSION is changed, and replaced with tests that the
- # wallet handles v3 rules properly.
+ # wallet handles TRUC rules properly.
assert_equal(tx_current_version.version, 2)
wallet_v3.unloadwallet()
if __name__ == '__main__':
- CreateTxWalletTest().main()
+ CreateTxWalletTest(__file__).main()
diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py
index 8e07021e03..0232af1658 100755
--- a/test/functional/wallet_createwallet.py
+++ b/test/functional/wallet_createwallet.py
@@ -190,4 +190,4 @@ class CreateWalletTest(BitcoinTestFramework):
if __name__ == '__main__':
- CreateWalletTest().main()
+ CreateWalletTest(__file__).main()
diff --git a/test/functional/wallet_createwalletdescriptor.py b/test/functional/wallet_createwalletdescriptor.py
index 18e1703da3..e8cd914769 100755
--- a/test/functional/wallet_createwalletdescriptor.py
+++ b/test/functional/wallet_createwalletdescriptor.py
@@ -120,4 +120,4 @@ class WalletCreateDescriptorTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletCreateDescriptorTest().main()
+ WalletCreateDescriptorTest(__file__).main()
diff --git a/test/functional/wallet_crosschain.py b/test/functional/wallet_crosschain.py
index 4c5d7192ae..97db84c3e4 100755
--- a/test/functional/wallet_crosschain.py
+++ b/test/functional/wallet_crosschain.py
@@ -11,7 +11,7 @@ class WalletCrossChain(BitcoinTestFramework):
self.add_wallet_options(parser)
def set_test_params(self):
- self.num_nodes = 2
+ self.num_nodes = 3
self.setup_clean_chain = True
def skip_test_if_missing_module(self):
@@ -24,6 +24,12 @@ class WalletCrossChain(BitcoinTestFramework):
self.nodes[1].chain = 'testnet3'
self.nodes[1].extra_args = ['-maxconnections=0', '-prune=550'] # disable testnet sync
self.nodes[1].replace_in_config([('regtest=', 'testnet='), ('[regtest]', '[test]')])
+
+ # Switch node 2 to testnet4 before starting it.
+ self.nodes[2].chain = 'testnet4'
+ self.nodes[2].extra_args = ['-maxconnections=0', '-prune=550'] # disable testnet4 sync
+ self.nodes[2].replace_in_config([('regtest=', 'testnet4='), ('[regtest]', '[testnet4]')])
+
self.start_nodes()
def run_test(self):
@@ -39,19 +45,40 @@ class WalletCrossChain(BitcoinTestFramework):
self.nodes[1].createwallet(node1_wallet)
self.nodes[1].backupwallet(node1_wallet_backup)
self.nodes[1].unloadwallet(node1_wallet)
+ node2_wallet = self.nodes[2].datadir_path / 'node2_wallet'
+ node2_wallet_backup = self.nodes[0].datadir_path / 'node2_wallet.bak'
+ self.nodes[2].createwallet(node2_wallet)
+ self.nodes[2].backupwallet(node2_wallet_backup)
+ self.nodes[2].unloadwallet(node2_wallet)
self.log.info("Loading/restoring wallets into nodes with a different genesis block")
if self.options.descriptors:
assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].loadwallet, node1_wallet)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].loadwallet, node2_wallet)
assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].loadwallet, node0_wallet)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[2].loadwallet, node0_wallet)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].loadwallet, node2_wallet)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[2].loadwallet, node1_wallet)
assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].restorewallet, 'w', node1_wallet_backup)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].restorewallet, 'w', node2_wallet_backup)
assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].restorewallet, 'w', node0_wallet_backup)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[2].restorewallet, 'w', node0_wallet_backup)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].restorewallet, 'w', node2_wallet_backup)
+ assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[2].restorewallet, 'w', node1_wallet_backup)
else:
assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].loadwallet, node1_wallet)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].loadwallet, node2_wallet)
assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].loadwallet, node0_wallet)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[2].loadwallet, node0_wallet)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].loadwallet, node2_wallet)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[2].loadwallet, node1_wallet)
assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].restorewallet, 'w', node1_wallet_backup)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].restorewallet, 'w', node2_wallet_backup)
assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].restorewallet, 'w', node0_wallet_backup)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[2].restorewallet, 'w', node0_wallet_backup)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].restorewallet, 'w', node2_wallet_backup)
+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[2].restorewallet, 'w', node1_wallet_backup)
if not self.options.descriptors:
self.log.info("Override cross-chain wallet load protection")
@@ -62,4 +89,4 @@ class WalletCrossChain(BitcoinTestFramework):
if __name__ == '__main__':
- WalletCrossChain().main()
+ WalletCrossChain(__file__).main()
diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py
index cbd3898f92..5e0ee97892 100755
--- a/test/functional/wallet_descriptor.py
+++ b/test/functional/wallet_descriptor.py
@@ -282,4 +282,4 @@ class WalletDescriptorTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletDescriptorTest().main ()
+ WalletDescriptorTest(__file__).main()
diff --git a/test/functional/wallet_disable.py b/test/functional/wallet_disable.py
index 9c73f7dead..da6e5d408f 100755
--- a/test/functional/wallet_disable.py
+++ b/test/functional/wallet_disable.py
@@ -28,4 +28,4 @@ class DisableWalletTest (BitcoinTestFramework):
if __name__ == '__main__':
- DisableWalletTest().main()
+ DisableWalletTest(__file__).main()
diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py
index f50aae0c53..3a4f23a124 100755
--- a/test/functional/wallet_dump.py
+++ b/test/functional/wallet_dump.py
@@ -223,4 +223,4 @@ class WalletDumpTest(BitcoinTestFramework):
w3.dumpwallet(self.nodes[0].datadir_path / "w3.dump")
if __name__ == '__main__':
- WalletDumpTest().main()
+ WalletDumpTest(__file__).main()
diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py
index b30634010d..5a5fb3e5be 100755
--- a/test/functional/wallet_encryption.py
+++ b/test/functional/wallet_encryption.py
@@ -102,4 +102,4 @@ class WalletEncryptionTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletEncryptionTest().main()
+ WalletEncryptionTest(__file__).main()
diff --git a/test/functional/wallet_fallbackfee.py b/test/functional/wallet_fallbackfee.py
index f0740b72fd..1849a602ab 100755
--- a/test/functional/wallet_fallbackfee.py
+++ b/test/functional/wallet_fallbackfee.py
@@ -32,4 +32,4 @@ class WalletRBFTest(BitcoinTestFramework):
assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendmany("", {self.nodes[0].getnewaddress(): 1}))
if __name__ == '__main__':
- WalletRBFTest().main()
+ WalletRBFTest(__file__).main()
diff --git a/test/functional/wallet_fast_rescan.py b/test/functional/wallet_fast_rescan.py
index 1315bccafd..4ac441516e 100755
--- a/test/functional/wallet_fast_rescan.py
+++ b/test/functional/wallet_fast_rescan.py
@@ -99,4 +99,4 @@ class WalletFastRescanTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletFastRescanTest().main()
+ WalletFastRescanTest(__file__).main()
diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py
index 3c1b2deb1d..827f27b431 100755
--- a/test/functional/wallet_fundrawtransaction.py
+++ b/test/functional/wallet_fundrawtransaction.py
@@ -1322,15 +1322,15 @@ class RawTransactionsTest(BitcoinTestFramework):
outputs = []
for _ in range(1472):
outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1})
- txid = self.nodes[0].send(outputs=outputs)["txid"]
+ txid = self.nodes[0].send(outputs=outputs, change_position=0)["txid"]
self.generate(self.nodes[0], 1)
# 272 WU per input (273 when high-s); picking 1471 inputs will exceed the max standard tx weight.
rawtx = wallet.createrawtransaction([], [{wallet.getnewaddress(): 0.1 * 1471}])
- # 1) Try to fund transaction only using the preset inputs
+ # 1) Try to fund transaction only using the preset inputs (pick all 1472 inputs to cover the fee)
input_weights = []
- for i in range(1471):
+ for i in range(1, 1473): # skip first output as it is the parent tx change output
input_weights.append({"txid": txid, "vout": i, "weight": 273})
assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights)
@@ -1523,4 +1523,4 @@ class RawTransactionsTest(BitcoinTestFramework):
wallet.unloadwallet()
if __name__ == '__main__':
- RawTransactionsTest().main()
+ RawTransactionsTest(__file__).main()
diff --git a/test/functional/wallet_gethdkeys.py b/test/functional/wallet_gethdkeys.py
index f09b8c875a..b6edc29fe6 100755
--- a/test/functional/wallet_gethdkeys.py
+++ b/test/functional/wallet_gethdkeys.py
@@ -182,4 +182,4 @@ class WalletGetHDKeyTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletGetHDKeyTest().main()
+ WalletGetHDKeyTest(__file__).main()
diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py
index 26477131cf..8d6c96c0e0 100755
--- a/test/functional/wallet_groups.py
+++ b/test/functional/wallet_groups.py
@@ -182,4 +182,4 @@ class WalletGroupTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletGroupTest().main()
+ WalletGroupTest(__file__).main()
diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py
index 52161043ea..8f2a5fc78b 100755
--- a/test/functional/wallet_hd.py
+++ b/test/functional/wallet_hd.py
@@ -280,4 +280,4 @@ class WalletHDTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletHDTest().main()
+ WalletHDTest(__file__).main()
diff --git a/test/functional/wallet_implicitsegwit.py b/test/functional/wallet_implicitsegwit.py
index baa9bafb00..e5787c0304 100755
--- a/test/functional/wallet_implicitsegwit.py
+++ b/test/functional/wallet_implicitsegwit.py
@@ -65,4 +65,4 @@ class ImplicitSegwitTest(BitcoinTestFramework):
check_implicit_transactions(implicit_keys, self.nodes[0])
if __name__ == '__main__':
- ImplicitSegwitTest().main()
+ ImplicitSegwitTest(__file__).main()
diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py
index 2a9435b370..c5834c15d2 100755
--- a/test/functional/wallet_import_rescan.py
+++ b/test/functional/wallet_import_rescan.py
@@ -338,4 +338,4 @@ class ImportRescanTest(BitcoinTestFramework):
if __name__ == "__main__":
- ImportRescanTest().main()
+ ImportRescanTest(__file__).main()
diff --git a/test/functional/wallet_import_with_label.py b/test/functional/wallet_import_with_label.py
index 0a1fc31ebc..9d01dfa5b7 100755
--- a/test/functional/wallet_import_with_label.py
+++ b/test/functional/wallet_import_with_label.py
@@ -125,4 +125,4 @@ class ImportWithLabel(BitcoinTestFramework):
if __name__ == "__main__":
- ImportWithLabel().main()
+ ImportWithLabel(__file__).main()
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index f9d05a2fe4..84c07b6a28 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -16,6 +16,7 @@ variants.
and test the values returned."""
import concurrent.futures
+import time
from test_framework.authproxy import JSONRPCException
from test_framework.blocktools import COINBASE_MATURITY
@@ -708,5 +709,56 @@ class ImportDescriptorsTest(BitcoinTestFramework):
assert_equal(temp_wallet.getbalance(), encrypted_wallet.getbalance())
+ self.log.info("Multipath descriptors")
+ self.nodes[1].createwallet(wallet_name="multipath", descriptors=True, blank=True)
+ w_multipath = self.nodes[1].get_wallet_rpc("multipath")
+ self.nodes[1].createwallet(wallet_name="multipath_split", descriptors=True, blank=True)
+ w_multisplit = self.nodes[1].get_wallet_rpc("multipath_split")
+ timestamp = int(time.time())
+
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
+ "active": True,
+ "range": 10,
+ "timestamp": "now",
+ "label": "some label"},
+ success=False,
+ error_code=-8,
+ error_message="Multipath descriptors should not have a label",
+ wallet=w_multipath)
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
+ "active": True,
+ "range": 10,
+ "timestamp": timestamp,
+ "internal": True},
+ success=False,
+ error_code=-5,
+ error_message="Cannot have multipath descriptor while also specifying \'internal\'",
+ wallet=w_multipath)
+
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
+ "active": True,
+ "range": 10,
+ "timestamp": timestamp},
+ success=True,
+ wallet=w_multipath)
+
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/10/0/*)"),
+ "active": True,
+ "range": 10,
+ "timestamp": timestamp},
+ success=True,
+ wallet=w_multisplit)
+ self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/20/0/*)"),
+ "active": True,
+ "range": 10,
+ "internal": True,
+ "timestamp": timestamp},
+ success=True,
+ wallet=w_multisplit)
+ for _ in range(0, 10):
+ assert_equal(w_multipath.getnewaddress(address_type="bech32"), w_multisplit.getnewaddress(address_type="bech32"))
+ assert_equal(w_multipath.getrawchangeaddress(address_type="bech32"), w_multisplit.getrawchangeaddress(address_type="bech32"))
+ assert_equal(sorted(w_multipath.listdescriptors()["descriptors"], key=lambda x: x["desc"]), sorted(w_multisplit.listdescriptors()["descriptors"], key=lambda x: x["desc"]))
+
if __name__ == '__main__':
- ImportDescriptorsTest().main()
+ ImportDescriptorsTest(__file__).main()
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index 31013f6323..3ce794dc2f 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -896,6 +896,43 @@ class ImportMultiTest(BitcoinTestFramework):
)
assert result[0]['success']
+ self.log.info("Multipath descriptors")
+ self.nodes[1].createwallet(wallet_name="multipath", blank=True, disable_private_keys=True)
+ w_multipath = self.nodes[1].get_wallet_rpc("multipath")
+ self.nodes[1].createwallet(wallet_name="multipath_split", blank=True, disable_private_keys=True)
+ w_multisplit = self.nodes[1].get_wallet_rpc("multipath_split")
+
+ res = w_multipath.importmulti([{"desc": descsum_create(f"wpkh({xpub}/<10;20>/0/*)"),
+ "keypool": True,
+ "range": 10,
+ "timestamp": "now",
+ "internal": True}])
+ assert_equal(res[0]["success"], False)
+ assert_equal(res[0]["error"]["code"], -5)
+ assert_equal(res[0]["error"]["message"], "Cannot have multipath descriptor while also specifying 'internal'")
+
+ res = w_multipath.importmulti([{"desc": descsum_create(f"wpkh({xpub}/<10;20>/0/*)"),
+ "keypool": True,
+ "range": 10,
+ "timestamp": "now"}])
+ assert_equal(res[0]["success"], True)
+
+ res = w_multisplit.importmulti([{"desc": descsum_create(f"wpkh({xpub}/10/0/*)"),
+ "keypool": True,
+ "range": 10,
+ "timestamp": "now"}])
+ assert_equal(res[0]["success"], True)
+ res = w_multisplit.importmulti([{"desc": descsum_create(f"wpkh({xpub}/20/0/*)"),
+ "keypool": True,
+ "range": 10,
+ "internal": True,
+ "timestamp": timestamp}])
+ assert_equal(res[0]["success"], True)
+
+ for _ in range(0, 9):
+ assert_equal(w_multipath.getnewaddress(address_type="bech32"), w_multisplit.getnewaddress(address_type="bech32"))
+ assert_equal(w_multipath.getrawchangeaddress(address_type="bech32"), w_multisplit.getrawchangeaddress(address_type="bech32"))
+
if __name__ == '__main__':
- ImportMultiTest().main()
+ ImportMultiTest(__file__).main()
diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py
index b3ae22cc44..467fddeb59 100755
--- a/test/functional/wallet_importprunedfunds.py
+++ b/test/functional/wallet_importprunedfunds.py
@@ -143,4 +143,4 @@ class ImportPrunedFundsTest(BitcoinTestFramework):
if __name__ == '__main__':
- ImportPrunedFundsTest().main()
+ ImportPrunedFundsTest(__file__).main()
diff --git a/test/functional/wallet_inactive_hdchains.py b/test/functional/wallet_inactive_hdchains.py
index c6d22ab90b..3b0c09c02b 100755
--- a/test/functional/wallet_inactive_hdchains.py
+++ b/test/functional/wallet_inactive_hdchains.py
@@ -146,4 +146,4 @@ class InactiveHDChainsTest(BitcoinTestFramework):
if __name__ == '__main__':
- InactiveHDChainsTest().main()
+ InactiveHDChainsTest(__file__).main()
diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py
index 6ed8572347..d3b6ca1ed1 100755
--- a/test/functional/wallet_keypool.py
+++ b/test/functional/wallet_keypool.py
@@ -223,4 +223,4 @@ class KeyPoolTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, msg, w2.keypoolrefill, 100)
if __name__ == '__main__':
- KeyPoolTest().main()
+ KeyPoolTest(__file__).main()
diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py
index e1bd85d8a9..25028d87bf 100755
--- a/test/functional/wallet_keypool_topup.py
+++ b/test/functional/wallet_keypool_topup.py
@@ -98,4 +98,4 @@ class KeypoolRestoreTest(BitcoinTestFramework):
if __name__ == '__main__':
- KeypoolRestoreTest().main()
+ KeypoolRestoreTest(__file__).main()
diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py
index f074339a2b..307e10ae34 100755
--- a/test/functional/wallet_labels.py
+++ b/test/functional/wallet_labels.py
@@ -256,4 +256,4 @@ def change_label(node, address, old_label, new_label):
new_label.verify(node)
if __name__ == '__main__':
- WalletLabelsTest().main()
+ WalletLabelsTest(__file__).main()
diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py
index 712b881322..c9d6c1f190 100755
--- a/test/functional/wallet_listdescriptors.py
+++ b/test/functional/wallet_listdescriptors.py
@@ -136,4 +136,4 @@ class ListDescriptorsTest(BitcoinTestFramework):
if __name__ == '__main__':
- ListDescriptorsTest().main()
+ ListDescriptorsTest(__file__).main()
diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py
index d0f1336a5e..522c7732fe 100755
--- a/test/functional/wallet_listreceivedby.py
+++ b/test/functional/wallet_listreceivedby.py
@@ -263,4 +263,4 @@ class ReceivedByTest(BitcoinTestFramework):
if __name__ == '__main__':
- ReceivedByTest().main()
+ ReceivedByTest(__file__).main()
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index 15214539a9..d777212f96 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -505,4 +505,4 @@ class ListSinceBlockTest(BitcoinTestFramework):
if __name__ == '__main__':
- ListSinceBlockTest().main()
+ ListSinceBlockTest(__file__).main()
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
index c820eaa6f6..a9f2066dd1 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -330,4 +330,4 @@ class ListTransactionsTest(BitcoinTestFramework):
if __name__ == '__main__':
- ListTransactionsTest().main()
+ ListTransactionsTest(__file__).main()
diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py
index 890b6a5c1b..48ad8e4f2a 100755
--- a/test/functional/wallet_migration.py
+++ b/test/functional/wallet_migration.py
@@ -205,9 +205,13 @@ class WalletMigrationTest(BitcoinTestFramework):
self.assert_list_txs_equal(basic2.listtransactions(), basic2_txs)
# Now test migration on a descriptor wallet
- self.log.info("Test \"nothing to migrate\" when the user tries to migrate a wallet with no legacy data")
+ self.log.info("Test \"nothing to migrate\" when the user tries to migrate a loaded wallet with no legacy data")
assert_raises_rpc_error(-4, "Error: This wallet is already a descriptor wallet", basic2.migratewallet)
+ self.log.info("Test \"nothing to migrate\" when the user tries to migrate an unloaded wallet with no legacy data")
+ basic2.unloadwallet()
+ assert_raises_rpc_error(-4, "Error: This wallet is already a descriptor wallet", self.nodes[0].migratewallet, "basic2")
+
def test_multisig(self):
default = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
@@ -467,6 +471,12 @@ class WalletMigrationTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", wallet.migratewallet, None, "badpass")
assert_raises_rpc_error(-4, "The passphrase contains a null character", wallet.migratewallet, None, "pass\0with\0null")
+ # Check the wallet is still active post-migration failure.
+ # If not, it will throw an exception and abort the test.
+ wallet.walletpassphrase("pass", 99999)
+ wallet.getnewaddress()
+
+ # Verify we can properly migrate the encrypted wallet
self.migrate_wallet(wallet, passphrase="pass")
info = wallet.getwalletinfo()
@@ -538,10 +548,15 @@ class WalletMigrationTest(BitcoinTestFramework):
assert_equal(info["descriptors"], True)
assert_equal(info["format"], "sqlite")
+ walletdir_list = wallet.listwalletdir()
+ assert {"name": info["walletname"]} in walletdir_list["wallets"]
+
# Check backup existence and its non-empty wallet filename
- backup_path = self.nodes[0].wallets_path / f'default_wallet_{curr_time}.legacy.bak'
+ backup_filename = f"default_wallet_{curr_time}.legacy.bak"
+ backup_path = self.nodes[0].wallets_path / backup_filename
assert backup_path.exists()
assert_equal(str(backup_path), res['backup_path'])
+ assert {"name": backup_filename} not in walletdir_list["wallets"]
def test_direct_file(self):
self.log.info("Test migration of a wallet that is not in a wallet directory")
@@ -1022,4 +1037,4 @@ class WalletMigrationTest(BitcoinTestFramework):
self.test_blank()
if __name__ == '__main__':
- WalletMigrationTest().main()
+ WalletMigrationTest(__file__).main()
diff --git a/test/functional/wallet_miniscript.py b/test/functional/wallet_miniscript.py
index 67e1283902..064eac499b 100755
--- a/test/functional/wallet_miniscript.py
+++ b/test/functional/wallet_miniscript.py
@@ -401,4 +401,4 @@ class WalletMiniscriptTest(BitcoinTestFramework):
if __name__ == "__main__":
- WalletMiniscriptTest().main()
+ WalletMiniscriptTest(__file__).main()
diff --git a/test/functional/wallet_multisig_descriptor_psbt.py b/test/functional/wallet_multisig_descriptor_psbt.py
index 68bf45f7e3..2e0b0d1a41 100755
--- a/test/functional/wallet_multisig_descriptor_psbt.py
+++ b/test/functional/wallet_multisig_descriptor_psbt.py
@@ -7,7 +7,6 @@
This is meant to be documentation as much as functional tests, so it is kept as simple and readable as possible.
"""
-from test_framework.address import base58_to_byte
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
@@ -30,10 +29,12 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
self.skip_if_no_sqlite()
@staticmethod
- def _get_xpub(wallet):
+ def _get_xpub(wallet, internal):
"""Extract the wallet's xpubs using `listdescriptors` and pick the one from the `pkh` descriptor since it's least likely to be accidentally reused (legacy addresses)."""
- descriptor = next(filter(lambda d: d["desc"].startswith("pkh"), wallet.listdescriptors()["descriptors"]))
- return descriptor["desc"].split("]")[-1].split("/")[0]
+ pkh_descriptor = next(filter(lambda d: d["desc"].startswith("pkh(") and d["internal"] == internal, wallet.listdescriptors()["descriptors"]))
+ # Keep all key origin information (master key fingerprint and all derivation steps) for proper support of hardware devices
+ # See section 'Key origin identification' in 'doc/descriptors.md' for more details...
+ return pkh_descriptor["desc"].split("pkh(")[1].split(")")[0]
@staticmethod
def _check_psbt(psbt, to, value, multisig):
@@ -47,19 +48,13 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
amount += vout["value"]
assert_approx(amount, float(value), vspan=0.001)
- def participants_create_multisigs(self, xpubs):
+ def participants_create_multisigs(self, external_xpubs, internal_xpubs):
"""The multisig is created by importing the following descriptors. The resulting wallet is watch-only and every participant can do this."""
- # some simple validation
- assert_equal(len(xpubs), self.N)
- # a sanity-check/assertion, this will throw if the base58 checksum of any of the provided xpubs are invalid
- for xpub in xpubs:
- base58_to_byte(xpub)
-
for i, node in enumerate(self.nodes):
node.createwallet(wallet_name=f"{self.name}_{i}", blank=True, descriptors=True, disable_private_keys=True)
multisig = node.get_wallet_rpc(f"{self.name}_{i}")
- external = multisig.getdescriptorinfo(f"wsh(sortedmulti({self.M},{f'/0/*,'.join(xpubs)}/0/*))")
- internal = multisig.getdescriptorinfo(f"wsh(sortedmulti({self.M},{f'/1/*,'.join(xpubs)}/1/*))")
+ external = multisig.getdescriptorinfo(f"wsh(sortedmulti({self.M},{f','.join(external_xpubs)}))")
+ internal = multisig.getdescriptorinfo(f"wsh(sortedmulti({self.M},{f','.join(internal_xpubs)}))")
result = multisig.importdescriptors([
{ # receiving addresses (internal: False)
"desc": external["descriptor"],
@@ -93,10 +88,10 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
}
self.log.info("Generate and exchange xpubs...")
- xpubs = [self._get_xpub(signer) for signer in participants["signers"]]
+ external_xpubs, internal_xpubs = [[self._get_xpub(signer, internal) for signer in participants["signers"]] for internal in [False, True]]
self.log.info("Every participant imports the following descriptors to create the watch-only multisig...")
- participants["multisigs"] = list(self.participants_create_multisigs(xpubs))
+ participants["multisigs"] = list(self.participants_create_multisigs(external_xpubs, internal_xpubs))
self.log.info("Check that every participant's multisig generates the same addresses...")
for _ in range(10): # we check that the first 10 generated addresses are the same for all participant's multisigs
@@ -159,4 +154,4 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
if __name__ == "__main__":
- WalletMultisigDescriptorPSBTTest().main()
+ WalletMultisigDescriptorPSBTTest(__file__).main()
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index ee866ee59b..149b1246d8 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -229,7 +229,7 @@ class MultiWalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", wallet_bad.getwalletinfo)
# accessing wallet RPC without using wallet endpoint fails
- assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo)
+ assert_raises_rpc_error(-19, "Multiple wallets are loaded. Please select which wallet", node.getwalletinfo)
w1, w2, w3, w4, *_ = wallets
self.generatetoaddress(node, nblocks=COINBASE_MATURITY + 1, address=w1.getnewaddress(), sync_fun=self.no_op)
@@ -275,7 +275,7 @@ class MultiWalletTest(BitcoinTestFramework):
loadwallet_name = node.loadwallet(wallet_names[1])
assert_equal(loadwallet_name['name'], wallet_names[1])
assert_equal(node.listwallets(), wallet_names[0:2])
- assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo)
+ assert_raises_rpc_error(-19, "Multiple wallets are loaded. Please select which wallet", node.getwalletinfo)
w2 = node.get_wallet_rpc(wallet_names[1])
w2.getwalletinfo()
@@ -423,4 +423,4 @@ class MultiWalletTest(BitcoinTestFramework):
if __name__ == '__main__':
- MultiWalletTest().main()
+ MultiWalletTest(__file__).main()
diff --git a/test/functional/wallet_orphanedreward.py b/test/functional/wallet_orphanedreward.py
index 451f8abb0a..8a02781f26 100755
--- a/test/functional/wallet_orphanedreward.py
+++ b/test/functional/wallet_orphanedreward.py
@@ -73,4 +73,4 @@ class OrphanedBlockRewardTest(BitcoinTestFramework):
if __name__ == '__main__':
- OrphanedBlockRewardTest().main()
+ OrphanedBlockRewardTest(__file__).main()
diff --git a/test/functional/wallet_pruning.py b/test/functional/wallet_pruning.py
index 6e252b5406..9c34a24be9 100755
--- a/test/functional/wallet_pruning.py
+++ b/test/functional/wallet_pruning.py
@@ -155,4 +155,4 @@ class WalletPruningTest(BitcoinTestFramework):
self.test_wallet_import_pruned_with_missing_blocks(wallet_birthheight_1)
if __name__ == '__main__':
- WalletPruningTest().main()
+ WalletPruningTest(__file__).main()
diff --git a/test/functional/wallet_reindex.py b/test/functional/wallet_reindex.py
index 5388de4b71..6778f76efc 100755
--- a/test/functional/wallet_reindex.py
+++ b/test/functional/wallet_reindex.py
@@ -105,4 +105,4 @@ class WalletReindexTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletReindexTest().main()
+ WalletReindexTest(__file__).main()
diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py
index 4271f3e481..77cf34898b 100755
--- a/test/functional/wallet_reorgsrestore.py
+++ b/test/functional/wallet_reorgsrestore.py
@@ -101,4 +101,4 @@ class ReorgsRestoreTest(BitcoinTestFramework):
assert conflicting["blockhash"] != conflicted_after_reorg["blockhash"]
if __name__ == '__main__':
- ReorgsRestoreTest().main()
+ ReorgsRestoreTest(__file__).main()
diff --git a/test/functional/wallet_rescan_unconfirmed.py b/test/functional/wallet_rescan_unconfirmed.py
index ad9fa081c2..69ad522b5d 100755
--- a/test/functional/wallet_rescan_unconfirmed.py
+++ b/test/functional/wallet_rescan_unconfirmed.py
@@ -80,4 +80,4 @@ class WalletRescanUnconfirmed(BitcoinTestFramework):
assert_equal(w1.gettransaction(tx_child_unconfirmed_sweep["txid"])["confirmations"], 0)
if __name__ == '__main__':
- WalletRescanUnconfirmed().main()
+ WalletRescanUnconfirmed(__file__).main()
diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py
index f61e1edc1d..49c8ef1c5f 100755
--- a/test/functional/wallet_resendwallettransactions.py
+++ b/test/functional/wallet_resendwallettransactions.py
@@ -149,4 +149,4 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
if __name__ == '__main__':
- ResendWalletTransactionsTest().main()
+ ResendWalletTransactionsTest(__file__).main()
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index bbb0d658d9..2d0aad3b5d 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -612,4 +612,4 @@ class WalletSendTest(BitcoinTestFramework):
if __name__ == '__main__':
- WalletSendTest().main()
+ WalletSendTest(__file__).main()
diff --git a/test/functional/wallet_sendall.py b/test/functional/wallet_sendall.py
index 1d308c225d..b256b53b38 100755
--- a/test/functional/wallet_sendall.py
+++ b/test/functional/wallet_sendall.py
@@ -531,4 +531,4 @@ class SendallTest(BitcoinTestFramework):
self.sendall_fails_with_transaction_too_large()
if __name__ == '__main__':
- SendallTest().main()
+ SendallTest(__file__).main()
diff --git a/test/functional/wallet_sendmany.py b/test/functional/wallet_sendmany.py
index 5751993143..ad99100590 100755
--- a/test/functional/wallet_sendmany.py
+++ b/test/functional/wallet_sendmany.py
@@ -40,4 +40,4 @@ class SendmanyTest(BitcoinTestFramework):
if __name__ == '__main__':
- SendmanyTest().main()
+ SendmanyTest(__file__).main()
diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py
index abfc3c1ba1..52b4c390b8 100755
--- a/test/functional/wallet_signer.py
+++ b/test/functional/wallet_signer.py
@@ -263,4 +263,4 @@ class WalletSignerTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "GetExternalSigner: More than one external signer found", self.nodes[1].createwallet, wallet_name='multi_hww', disable_private_keys=True, descriptors=True, external_signer=True)
if __name__ == '__main__':
- WalletSignerTest().main()
+ WalletSignerTest(__file__).main()
diff --git a/test/functional/wallet_signmessagewithaddress.py b/test/functional/wallet_signmessagewithaddress.py
index 4a4b818bd1..170c883ca4 100755
--- a/test/functional/wallet_signmessagewithaddress.py
+++ b/test/functional/wallet_signmessagewithaddress.py
@@ -45,4 +45,4 @@ class SignMessagesWithAddressTest(BitcoinTestFramework):
if __name__ == '__main__':
- SignMessagesWithAddressTest().main()
+ SignMessagesWithAddressTest(__file__).main()
diff --git a/test/functional/wallet_signrawtransactionwithwallet.py b/test/functional/wallet_signrawtransactionwithwallet.py
index 612a2542e7..eb0e4097fe 100755
--- a/test/functional/wallet_signrawtransactionwithwallet.py
+++ b/test/functional/wallet_signrawtransactionwithwallet.py
@@ -310,4 +310,4 @@ class SignRawTransactionWithWalletTest(BitcoinTestFramework):
if __name__ == '__main__':
- SignRawTransactionWithWalletTest().main()
+ SignRawTransactionWithWalletTest(__file__).main()
diff --git a/test/functional/wallet_simulaterawtx.py b/test/functional/wallet_simulaterawtx.py
index 545aad892c..11b7a4c76e 100755
--- a/test/functional/wallet_simulaterawtx.py
+++ b/test/functional/wallet_simulaterawtx.py
@@ -129,4 +129,4 @@ class SimulateTxTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w2.simulaterawtransaction, [tx1, tx2])
if __name__ == '__main__':
- SimulateTxTest().main()
+ SimulateTxTest(__file__).main()
diff --git a/test/functional/wallet_spend_unconfirmed.py b/test/functional/wallet_spend_unconfirmed.py
index bfcdeaeaa8..4c73e0970b 100755
--- a/test/functional/wallet_spend_unconfirmed.py
+++ b/test/functional/wallet_spend_unconfirmed.py
@@ -505,4 +505,4 @@ class UnconfirmedInputTest(BitcoinTestFramework):
self.test_external_input_unconfirmed_low()
if __name__ == '__main__':
- UnconfirmedInputTest().main()
+ UnconfirmedInputTest(__file__).main()
diff --git a/test/functional/wallet_startup.py b/test/functional/wallet_startup.py
index 2cc4e312af..6feb00af8e 100755
--- a/test/functional/wallet_startup.py
+++ b/test/functional/wallet_startup.py
@@ -58,4 +58,4 @@ class WalletStartupTest(BitcoinTestFramework):
assert_equal(set(self.nodes[0].listwallets()), set(('w2', 'w3')))
if __name__ == '__main__':
- WalletStartupTest().main()
+ WalletStartupTest(__file__).main()
diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py
index a5d7445ce8..a88d84f4c6 100755
--- a/test/functional/wallet_taproot.py
+++ b/test/functional/wallet_taproot.py
@@ -507,4 +507,4 @@ class WalletTaprootTest(BitcoinTestFramework):
)
if __name__ == '__main__':
- WalletTaprootTest().main()
+ WalletTaprootTest(__file__).main()
diff --git a/test/functional/wallet_timelock.py b/test/functional/wallet_timelock.py
index 0a622979a4..6c88438330 100755
--- a/test/functional/wallet_timelock.py
+++ b/test/functional/wallet_timelock.py
@@ -50,4 +50,4 @@ class WalletLocktimeTest(BitcoinTestFramework):
if __name__ == "__main__":
- WalletLocktimeTest().main()
+ WalletLocktimeTest(__file__).main()
diff --git a/test/functional/wallet_transactiontime_rescan.py b/test/functional/wallet_transactiontime_rescan.py
index ea99992084..fb6dc74dc5 100755
--- a/test/functional/wallet_transactiontime_rescan.py
+++ b/test/functional/wallet_transactiontime_rescan.py
@@ -223,4 +223,4 @@ class TransactionTimeRescanTest(BitcoinTestFramework):
assert_equal(encrypted_wallet.getbalance(), temp_wallet.getbalance())
if __name__ == '__main__':
- TransactionTimeRescanTest().main()
+ TransactionTimeRescanTest(__file__).main()
diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py
index 1f3b6f2ce9..a2dd223aed 100755
--- a/test/functional/wallet_txn_clone.py
+++ b/test/functional/wallet_txn_clone.py
@@ -149,4 +149,4 @@ class TxnMallTest(BitcoinTestFramework):
if __name__ == '__main__':
- TxnMallTest().main()
+ TxnMallTest(__file__).main()
diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py
index 3cd0cd3207..3d6830f1f3 100755
--- a/test/functional/wallet_txn_doublespend.py
+++ b/test/functional/wallet_txn_doublespend.py
@@ -138,4 +138,4 @@ class TxnMallTest(BitcoinTestFramework):
if __name__ == '__main__':
- TxnMallTest().main()
+ TxnMallTest(__file__).main()
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index a4f2a9b74d..c909336a25 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -185,6 +185,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
self.restart_node(0)
copy_v16()
wallet = node_master.get_wallet_rpc(self.default_wallet_name)
+ assert_equal(wallet.getbalance(), v16_3_balance)
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
@@ -231,7 +232,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
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)
+ 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()
@@ -258,7 +259,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
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)
+ 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)
@@ -284,7 +285,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
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)
+ 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)
# The next addresses are HD and should be on different HD chains (the one remaining key in each pool should have been flushed)
@@ -301,8 +302,8 @@ class UpgradeWalletTest(BitcoinTestFramework):
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))
+ 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)
@@ -360,4 +361,4 @@ class UpgradeWalletTest(BitcoinTestFramework):
self.test_upgradewallet(disabled_wallet, previous_version=169900, expected_version=169900)
if __name__ == '__main__':
- UpgradeWalletTest().main()
+ UpgradeWalletTest(__file__).main()
diff --git a/test/functional/wallet_watchonly.py b/test/functional/wallet_watchonly.py
index b473f5d65c..298d50d452 100755
--- a/test/functional/wallet_watchonly.py
+++ b/test/functional/wallet_watchonly.py
@@ -111,4 +111,4 @@ class CreateWalletWatchonlyTest(BitcoinTestFramework):
if __name__ == '__main__':
- CreateWalletWatchonlyTest().main()
+ CreateWalletWatchonlyTest(__file__).main()
diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py
index c74246ef45..9917eca75a 100755
--- a/test/fuzz/test_runner.py
+++ b/test/fuzz/test_runner.py
@@ -23,10 +23,10 @@ def get_fuzz_env(*, target, source_dir):
'FUZZ': target,
'UBSAN_OPTIONS':
f'suppressions={source_dir}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1',
- 'UBSAN_SYMBOLIZER_PATH':symbolizer,
- "ASAN_OPTIONS": "detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1",
- 'ASAN_SYMBOLIZER_PATH':symbolizer,
- 'MSAN_SYMBOLIZER_PATH':symbolizer,
+ 'UBSAN_SYMBOLIZER_PATH': symbolizer,
+ "ASAN_OPTIONS": "detect_leaks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1",
+ 'ASAN_SYMBOLIZER_PATH': symbolizer,
+ 'MSAN_SYMBOLIZER_PATH': symbolizer,
}
if platform.system() == "Windows":
# On Windows, `env` option must include valid `SystemRoot`.
diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py
index 459693102b..1d397e721e 100755
--- a/test/get_previous_releases.py
+++ b/test/get_previous_releases.py
@@ -133,20 +133,9 @@ def download_binary(tag, args) -> int:
print('Fetching: {tarballUrl}'.format(tarballUrl=tarballUrl))
- header, status = subprocess.Popen(
- ['curl', '--head', tarballUrl], stdout=subprocess.PIPE).communicate()
- if re.search("404 Not Found", header.decode("utf-8")):
- print("Binary tag was not found")
- return 1
-
- curlCmds = [
- ['curl', '--remote-name', tarballUrl]
- ]
-
- for cmd in curlCmds:
- ret = subprocess.run(cmd).returncode
- if ret:
- return ret
+ ret = subprocess.run(['curl', '--fail', '--remote-name', tarballUrl]).returncode
+ if ret:
+ return ret
hasher = hashlib.sha256()
with open(tarball, "rb") as afile:
diff --git a/test/lint/README.md b/test/lint/README.md
index 04a836c4d2..8c1f0fedf0 100644
--- a/test/lint/README.md
+++ b/test/lint/README.md
@@ -45,13 +45,13 @@ or `--help`:
| Lint test | Dependency |
|-----------|:----------:|
-| [`lint-python.py`](/test/lint/lint-python.py) | [flake8](https://github.com/PyCQA/flake8)
| [`lint-python.py`](/test/lint/lint-python.py) | [lief](https://github.com/lief-project/LIEF)
| [`lint-python.py`](/test/lint/lint-python.py) | [mypy](https://github.com/python/mypy)
| [`lint-python.py`](/test/lint/lint-python.py) | [pyzmq](https://github.com/zeromq/pyzmq)
| [`lint-python-dead-code.py`](/test/lint/lint-python-dead-code.py) | [vulture](https://github.com/jendrikseipp/vulture)
| [`lint-shell.py`](/test/lint/lint-shell.py) | [ShellCheck](https://github.com/koalaman/shellcheck)
| [`lint-spelling.py`](/test/lint/lint-spelling.py) | [codespell](https://github.com/codespell-project/codespell)
+| `py_lint` | [ruff](https://github.com/astral-sh/ruff)
| markdown link check | [mlc](https://github.com/becheran/mlc)
In use versions and install instructions are available in the [CI setup](../../ci/lint/04_install.sh).
diff --git a/test/lint/lint-assertions.py b/test/lint/lint-assertions.py
index d9f86b22b8..5d01b13fd4 100755
--- a/test/lint/lint-assertions.py
+++ b/test/lint/lint-assertions.py
@@ -27,8 +27,9 @@ def main():
# checks should be used over assert. See: src/util/check.h
# src/rpc/server.cpp is excluded from this check since it's mostly meta-code.
exit_code = git_grep([
- "-nE",
- r"\<(A|a)ss(ume|ert) *\(.*\);",
+ "--line-number",
+ "--extended-regexp",
+ r"\<(A|a)ss(ume|ert)\(",
"--",
"src/rpc/",
"src/wallet/rpc*",
@@ -38,8 +39,9 @@ def main():
# The `BOOST_ASSERT` macro requires to `#include boost/assert.hpp`,
# which is an unnecessary Boost dependency.
exit_code |= git_grep([
- "-E",
- r"BOOST_ASSERT *\(.*\);",
+ "--line-number",
+ "--extended-regexp",
+ r"BOOST_ASSERT\(",
"--",
"*.cpp",
"*.h",
diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py
index 09d858e131..86a17fb0f8 100755
--- a/test/lint/lint-format-strings.py
+++ b/test/lint/lint-format-strings.py
@@ -16,28 +16,8 @@ import re
import sys
FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS = [
- 'FatalErrorf,0',
- 'fprintf,1',
'tfm::format,1', # Assuming tfm::::format(std::ostream&, ...
- 'LogConnectFailure,1',
- 'LogError,0',
- 'LogWarning,0',
- 'LogInfo,0',
- 'LogDebug,1',
- 'LogTrace,1',
- 'LogPrint,1',
- 'LogPrintf,0',
- 'LogPrintfCategory,1',
- 'LogPrintLevel,2',
- 'printf,0',
- 'snprintf,2',
- 'sprintf,1',
'strprintf,0',
- 'vfprintf,1',
- 'vprintf,1',
- 'vsnprintf,1',
- 'vsprintf,1',
- 'WalletLogPrintf,0',
]
RUN_LINT_FILE = 'test/lint/run-lint-format-strings.py'
@@ -82,7 +62,7 @@ def main():
matching_files_filtered = []
for matching_file in matching_files:
- if not re.search('^src/(leveldb|secp256k1|minisketch|tinyformat|test/fuzz/strprintf.cpp)|contrib/devtools/bitcoin-tidy/example_logprintf.cpp', matching_file):
+ if not re.search('^src/(leveldb|secp256k1|minisketch|tinyformat|test/fuzz/strprintf.cpp)', matching_file):
matching_files_filtered.append(matching_file)
matching_files_filtered.sort()
diff --git a/test/lint/lint-python-mutable-default-parameters.py b/test/lint/lint-python-mutable-default-parameters.py
deleted file mode 100755
index 820595ea34..0000000000
--- a/test/lint/lint-python-mutable-default-parameters.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (c) 2019-2022 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-"""
-Detect when a mutable list or dict is used as a default parameter value in a Python function.
-"""
-
-import subprocess
-import sys
-
-
-def main():
- command = [
- "git",
- "grep",
- "-E",
- r"^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)",
- "--",
- "*.py",
- ]
- output = subprocess.run(command, stdout=subprocess.PIPE, text=True)
- if len(output.stdout) > 0:
- error_msg = (
- "A mutable list or dict seems to be used as default parameter value:\n\n"
- f"{output.stdout}\n"
- f"{example()}"
- )
- print(error_msg)
- sys.exit(1)
- else:
- sys.exit(0)
-
-
-def example():
- return """This is how mutable list and dict default parameter values behave:
-
->>> def f(i, j=[], k={}):
-... j.append(i)
-... k[i] = True
-... return j, k
-...
->>> f(1)
-([1], {1: True})
->>> f(1)
-([1, 1], {1: True})
->>> f(2)
-([1, 1, 2], {1: True, 2: True})
-
-The intended behaviour was likely:
-
->>> def f(i, j=None, k=None):
-... if j is None:
-... j = []
-... if k is None:
-... k = {}
-... j.append(i)
-... k[i] = True
-... return j, k
-...
->>> f(1)
-([1], {1: True})
->>> f(1)
-([1], {1: True})
->>> f(2)
-([2], {2: True})"""
-
-
-if __name__ == "__main__":
- main()
diff --git a/test/lint/lint-python.py b/test/lint/lint-python.py
index eabd13322e..e2dbe25b88 100755
--- a/test/lint/lint-python.py
+++ b/test/lint/lint-python.py
@@ -5,13 +5,12 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
-Check for specified flake8 and mypy warnings in python files.
+Check for specified mypy warnings in python files.
"""
import os
from pathlib import Path
import subprocess
-import sys
from importlib.metadata import metadata, PackageNotFoundError
@@ -19,89 +18,12 @@ from importlib.metadata import metadata, PackageNotFoundError
cache_dir = Path(__file__).parent.parent / ".mypy_cache"
os.environ["MYPY_CACHE_DIR"] = str(cache_dir)
-DEPS = ['flake8', 'lief', 'mypy', 'pyzmq']
-
-# All .py files, except those in src/ (to exclude subtrees there)
-FLAKE_FILES_ARGS = ['git', 'ls-files', '*.py', ':!:src/*.py']
+DEPS = ['lief', 'mypy', 'pyzmq']
# Only .py files in test/functional and contrib/devtools have type annotations
# enforced.
MYPY_FILES_ARGS = ['git', 'ls-files', 'test/functional/*.py', 'contrib/devtools/*.py']
-ENABLED = (
- 'E101,' # indentation contains mixed spaces and tabs
- 'E112,' # expected an indented block
- 'E113,' # unexpected indentation
- 'E115,' # expected an indented block (comment)
- 'E116,' # unexpected indentation (comment)
- 'E125,' # continuation line with same indent as next logical line
- 'E129,' # visually indented line with same indent as next logical line
- 'E131,' # continuation line unaligned for hanging indent
- 'E133,' # closing bracket is missing indentation
- 'E223,' # tab before operator
- 'E224,' # tab after operator
- 'E242,' # tab after ','
- 'E266,' # too many leading '#' for block comment
- 'E271,' # multiple spaces after keyword
- 'E272,' # multiple spaces before keyword
- 'E273,' # tab after keyword
- 'E274,' # tab before keyword
- 'E275,' # missing whitespace after keyword
- 'E304,' # blank lines found after function decorator
- 'E306,' # expected 1 blank line before a nested definition
- 'E401,' # multiple imports on one line
- 'E402,' # module level import not at top of file
- 'E502,' # the backslash is redundant between brackets
- 'E701,' # multiple statements on one line (colon)
- 'E702,' # multiple statements on one line (semicolon)
- 'E703,' # statement ends with a semicolon
- 'E711,' # comparison to None should be 'if cond is None:'
- 'E714,' # test for object identity should be "is not"
- 'E721,' # do not compare types, use "isinstance()"
- 'E722,' # do not use bare 'except'
- 'E742,' # do not define classes named "l", "O", or "I"
- 'E743,' # do not define functions named "l", "O", or "I"
- 'E901,' # SyntaxError: invalid syntax
- 'E902,' # TokenError: EOF in multi-line string
- 'F401,' # module imported but unused
- 'F402,' # import module from line N shadowed by loop variable
- 'F403,' # 'from foo_module import *' used; unable to detect undefined names
- 'F404,' # future import(s) name after other statements
- 'F405,' # foo_function may be undefined, or defined from star imports: bar_module
- 'F406,' # "from module import *" only allowed at module level
- 'F407,' # an undefined __future__ feature name was imported
- 'F601,' # dictionary key name repeated with different values
- 'F602,' # dictionary key variable name repeated with different values
- 'F621,' # too many expressions in an assignment with star-unpacking
- 'F622,' # two or more starred expressions in an assignment (a, *b, *c = d)
- 'F631,' # assertion test is a tuple, which are always True
- 'F632,' # use ==/!= to compare str, bytes, and int literals
- 'F701,' # a break statement outside of a while or for loop
- 'F702,' # a continue statement outside of a while or for loop
- 'F703,' # a continue statement in a finally block in a loop
- 'F704,' # a yield or yield from statement outside of a function
- 'F705,' # a return statement with arguments inside a generator
- 'F706,' # a return statement outside of a function/method
- 'F707,' # an except: block as not the last exception handler
- 'F811,' # redefinition of unused name from line N
- 'F812,' # list comprehension redefines 'foo' from line N
- 'F821,' # undefined name 'Foo'
- 'F822,' # undefined name name in __all__
- 'F823,' # local variable name … referenced before assignment
- 'F831,' # duplicate argument name in function definition
- 'F841,' # local variable 'foo' is assigned to but never used
- 'W191,' # indentation contains tabs
- 'W291,' # trailing whitespace
- 'W292,' # no newline at end of file
- 'W293,' # blank line contains whitespace
- 'W601,' # .has_key() is deprecated, use "in"
- 'W602,' # deprecated form of raising exception
- 'W603,' # "<>" is deprecated, use "!="
- 'W604,' # backticks are deprecated, use "repr()"
- 'W605,' # invalid escape sequence "x"
- 'W606,' # 'async' and 'await' are reserved keywords starting with Python 3.7
-)
-
def check_dependencies():
for dep in DEPS:
@@ -115,20 +37,6 @@ def check_dependencies():
def main():
check_dependencies()
- if len(sys.argv) > 1:
- flake8_files = sys.argv[1:]
- else:
- flake8_files = subprocess.check_output(FLAKE_FILES_ARGS).decode("utf-8").splitlines()
-
- flake8_args = ['flake8', '--ignore=B,C,E,F,I,N,W', f'--select={ENABLED}'] + flake8_files
- flake8_env = os.environ.copy()
- flake8_env["PYTHONWARNINGS"] = "ignore"
-
- try:
- subprocess.check_call(flake8_args, env=flake8_env)
- except subprocess.CalledProcessError:
- exit(1)
-
mypy_files = subprocess.check_output(MYPY_FILES_ARGS).decode("utf-8").splitlines()
mypy_args = ['mypy', '--show-error-codes'] + mypy_files
diff --git a/test/lint/lint-spelling.py b/test/lint/lint-spelling.py
index 3e578b218f..945288a3dd 100755
--- a/test/lint/lint-spelling.py
+++ b/test/lint/lint-spelling.py
@@ -14,7 +14,7 @@ from subprocess import check_output, STDOUT, CalledProcessError
from lint_ignore_dirs import SHARED_EXCLUDED_SUBTREES
IGNORE_WORDS_FILE = 'test/lint/spelling.ignore-words.txt'
-FILES_ARGS = ['git', 'ls-files', '--', ":(exclude)build-aux/m4/", ":(exclude)contrib/seeds/*.txt", ":(exclude)depends/", ":(exclude)doc/release-notes/", ":(exclude)src/qt/locale/", ":(exclude)src/qt/*.qrc", ":(exclude)contrib/guix/patches"]
+FILES_ARGS = ['git', 'ls-files', '--', ":(exclude)contrib/seeds/*.txt", ":(exclude)depends/", ":(exclude)doc/release-notes/", ":(exclude)src/qt/locale/", ":(exclude)src/qt/*.qrc", ":(exclude)contrib/guix/patches"]
FILES_ARGS += [f":(exclude){dir}" for dir in SHARED_EXCLUDED_SUBTREES]
diff --git a/test/lint/run-lint-format-strings.py b/test/lint/run-lint-format-strings.py
index 244bf5956f..d3c0ac92e5 100755
--- a/test/lint/run-lint-format-strings.py
+++ b/test/lint/run-lint-format-strings.py
@@ -13,17 +13,8 @@ import re
import sys
FALSE_POSITIVES = [
- ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"),
- ("src/index/base.cpp", "FatalErrorf(const char* fmt, const Args&... args)"),
- ("src/index/base.h", "FatalErrorf(const char* fmt, const Args&... args)"),
- ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"),
("src/clientversion.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"),
("src/test/translation_tests.cpp", "strprintf(format, arg)"),
- ("src/validationinterface.cpp", "LogPrint(BCLog::VALIDATION, fmt \"\\n\", __VA_ARGS__)"),
- ("src/wallet/wallet.h", "WalletLogPrintf(const char* fmt, Params... parameters)"),
- ("src/wallet/wallet.h", "LogPrintf((\"%s \" + std::string{fmt}).c_str(), GetDisplayName(), parameters...)"),
- ("src/wallet/scriptpubkeyman.h", "WalletLogPrintf(const char* fmt, Params... parameters)"),
- ("src/wallet/scriptpubkeyman.h", "LogPrintf((\"%s \" + std::string{fmt}).c_str(), m_storage.GetDisplayName(), parameters...)"),
]
diff --git a/test/lint/spelling.ignore-words.txt b/test/lint/spelling.ignore-words.txt
index cb0e0c4d31..ccf2e6964b 100644
--- a/test/lint/spelling.ignore-words.txt
+++ b/test/lint/spelling.ignore-words.txt
@@ -1,22 +1,25 @@
afile
+amountIn
asend
-ba
blockin
bu
cachable
clen
crypted
debbugs
+deques
fo
fpr
+hashIn
hights
-inflight
+incomin
invokable
-keypair
lief
mor
nd
nin
+outIn
+re-use
requestor
ser
siz
@@ -24,5 +27,5 @@ stap
unparseable
unser
useable
-warmup
-wit
+viewIn
+wit \ No newline at end of file
diff --git a/test/lint/test_runner/src/main.rs b/test/lint/test_runner/src/main.rs
index 9c35898c1f..42c880052e 100644
--- a/test/lint/test_runner/src/main.rs
+++ b/test/lint/test_runner/src/main.rs
@@ -5,9 +5,12 @@
use std::env;
use std::fs;
use std::io::ErrorKind;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
use std::process::{Command, ExitCode, Stdio};
+/// A possible error returned by any of the linters.
+///
+/// The error string should explain the failure type and list all violations.
type LintError = String;
type LintResult = Result<(), LintError>;
type LintFn = fn() -> LintResult;
@@ -26,7 +29,7 @@ fn get_linter_list() -> Vec<&'static Linter> {
lint_fn: lint_doc
},
&Linter {
- description: "Check that no symbol from bitcoin-config.h is used without the header being included",
+ description: "Check that no symbol from bitcoin-build-config.h is used without the header being included",
name: "includes_build_config",
lint_fn: lint_includes_build_config
},
@@ -36,11 +39,21 @@ fn get_linter_list() -> Vec<&'static Linter> {
lint_fn: lint_markdown
},
&Linter {
+ description: "Lint Python code",
+ name: "py_lint",
+ lint_fn: lint_py_lint,
+ },
+ &Linter {
description: "Check that std::filesystem is not used directly",
name: "std_filesystem",
lint_fn: lint_std_filesystem
},
&Linter {
+ description: "Check that release note snippets are in the right folder",
+ name: "doc_release_note_snippets",
+ lint_fn: lint_doc_release_note_snippets
+ },
+ &Linter {
description: "Check that subtrees are pure subtrees",
name: "subtree",
lint_fn: lint_subtree
@@ -120,20 +133,27 @@ fn parse_lint_args(args: &[String]) -> Vec<&'static Linter> {
}
/// Return the git command
+///
+/// Lint functions should use this command, so that only files tracked by git are considered and
+/// temporary and untracked files are ignored. For example, instead of 'grep', 'git grep' should be
+/// used.
fn git() -> Command {
let mut git = Command::new("git");
git.arg("--no-pager");
git
}
-/// Return stdout
+/// Return stdout on success and a LintError on failure, when invalid UTF8 was detected or the
+/// command did not succeed.
fn check_output(cmd: &mut std::process::Command) -> Result<String, LintError> {
let out = cmd.output().expect("command error");
if !out.status.success() {
return Err(String::from_utf8_lossy(&out.stderr).to_string());
}
Ok(String::from_utf8(out.stdout)
- .map_err(|e| format!("{e}"))?
+ .map_err(|e| {
+ format!("All path names, source code, messages, and output must be valid UTF8!\n{e}")
+ })?
.trim()
.to_string())
}
@@ -180,6 +200,73 @@ fn lint_subtree() -> LintResult {
}
}
+fn lint_py_lint() -> LintResult {
+ let bin_name = "ruff";
+ let checks = format!(
+ "--select={}",
+ [
+ "B006", // mutable-argument-default
+ "B008", // function-call-in-default-argument
+ "E101", // indentation contains mixed spaces and tabs
+ "E401", // multiple imports on one line
+ "E402", // module level import not at top of file
+ "E701", // multiple statements on one line (colon)
+ "E702", // multiple statements on one line (semicolon)
+ "E703", // statement ends with a semicolon
+ "E711", // comparison to None should be 'if cond is None:'
+ "E714", // test for object identity should be "is not"
+ "E721", // do not compare types, use "isinstance()"
+ "E722", // do not use bare 'except'
+ "E742", // do not define classes named "l", "O", or "I"
+ "E743", // do not define functions named "l", "O", or "I"
+ "F401", // module imported but unused
+ "F402", // import module from line N shadowed by loop variable
+ "F403", // 'from foo_module import *' used; unable to detect undefined names
+ "F404", // future import(s) name after other statements
+ "F405", // foo_function may be undefined, or defined from star imports: bar_module
+ "F406", // "from module import *" only allowed at module level
+ "F407", // an undefined __future__ feature name was imported
+ "F601", // dictionary key name repeated with different values
+ "F602", // dictionary key variable name repeated with different values
+ "F621", // too many expressions in an assignment with star-unpacking
+ "F631", // assertion test is a tuple, which are always True
+ "F632", // use ==/!= to compare str, bytes, and int literals
+ "F811", // redefinition of unused name from line N
+ "F821", // undefined name 'Foo'
+ "F822", // undefined name name in __all__
+ "F823", // local variable name … referenced before assignment
+ "F841", // local variable 'foo' is assigned to but never used
+ "W191", // indentation contains tabs
+ "W291", // trailing whitespace
+ "W292", // no newline at end of file
+ "W293", // blank line contains whitespace
+ "W605", // invalid escape sequence "x"
+ ]
+ .join(",")
+ );
+ let files = check_output(
+ git()
+ .args(["ls-files", "--", "*.py"])
+ .args(get_pathspecs_exclude_subtrees()),
+ )?;
+
+ let mut cmd = Command::new(bin_name);
+ cmd.args(["check", &checks]).args(files.lines());
+
+ match cmd.status() {
+ Ok(status) if status.success() => Ok(()),
+ Ok(_) => Err(format!("`{}` found errors!", bin_name)),
+ Err(e) if e.kind() == ErrorKind::NotFound => {
+ println!(
+ "`{}` was not found in $PATH, skipping those checks.",
+ bin_name
+ );
+ Ok(())
+ }
+ Err(e) => Err(format!("Error running `{}`: {}", bin_name, e)),
+ }
+}
+
fn lint_std_filesystem() -> LintResult {
let found = git()
.args([
@@ -204,6 +291,30 @@ fs:: namespace, which has unsafe filesystem functions marked as deleted.
}
}
+fn lint_doc_release_note_snippets() -> LintResult {
+ let non_release_notes = check_output(git().args([
+ "ls-files",
+ "--",
+ "doc/release-notes/",
+ ":(exclude)doc/release-notes/*.*.md", // Assume that at least one dot implies a proper release note
+ ]))?;
+ if non_release_notes.is_empty() {
+ Ok(())
+ } else {
+ Err(format!(
+ r#"
+{}
+^^^
+Release note snippets and other docs must be put into the doc/ folder directly.
+
+The doc/release-notes/ folder is for archived release notes of previous releases only. Snippets are
+expected to follow the naming "/doc/release-notes-<PR number>.md".
+ "#,
+ non_release_notes
+ ))
+ }
+}
+
/// Return the pathspecs for whitespace related excludes
fn get_pathspecs_exclude_whitespace() -> Vec<String> {
let mut list = get_pathspecs_exclude_subtrees();
@@ -284,20 +395,14 @@ Please add any false positives, such as subtrees, or externally sourced files to
}
fn lint_includes_build_config() -> LintResult {
- let config_path = "./src/config/bitcoin-config.h.in";
- if !Path::new(config_path).is_file() {
- assert!(Command::new("./autogen.sh")
- .status()
- .expect("command error")
- .success());
- }
+ let config_path = "./cmake/bitcoin-build-config.h.in";
let defines_regex = format!(
r"^\s*(?!//).*({})",
- check_output(Command::new("grep").args(["undef ", "--", config_path]))
+ check_output(Command::new("grep").args(["define", "--", config_path]))
.expect("grep failed")
.lines()
.map(|line| {
- line.split("undef ")
+ line.split_whitespace()
.nth(1)
.unwrap_or_else(|| panic!("Could not extract name in line: {line}"))
})
@@ -324,7 +429,7 @@ fn lint_includes_build_config() -> LintResult {
])
.args(get_pathspecs_exclude_subtrees())
.args([
- // These are exceptions which don't use bitcoin-config.h, rather the Makefile.am adds
+ // These are exceptions which don't use bitcoin-build-config.h, rather CMakeLists.txt adds
// these cppflags manually.
":(exclude)src/crypto/sha256_arm_shani.cpp",
":(exclude)src/crypto/sha256_avx2.cpp",
@@ -342,9 +447,9 @@ fn lint_includes_build_config() -> LintResult {
"--files-with-matches"
},
if mode {
- "^#include <config/bitcoin-config.h> // IWYU pragma: keep$"
+ "^#include <bitcoin-build-config.h> // IWYU pragma: keep$"
} else {
- "#include <config/bitcoin-config.h>" // Catch redundant includes with and without the IWYU pragma
+ "#include <bitcoin-build-config.h>" // Catch redundant includes with and without the IWYU pragma
},
"--",
])
@@ -358,11 +463,11 @@ fn lint_includes_build_config() -> LintResult {
return Err(format!(
r#"
^^^
-One or more files use a symbol declared in the bitcoin-config.h header. However, they are not
+One or more files use a symbol declared in the bitcoin-build-config.h header. However, they are not
including the header. This is problematic, because the header may or may not be indirectly
included. If the indirect include were to be intentionally or accidentally removed, the build could
still succeed, but silently be buggy. For example, a slower fallback algorithm could be picked,
-even though bitcoin-config.h indicates that a faster feature is available and should be used.
+even though bitcoin-build-config.h indicates that a faster feature is available and should be used.
If you are unsure which symbol is used, you can find it with this command:
git grep --perl-regexp '{}' -- file_name
@@ -370,7 +475,7 @@ git grep --perl-regexp '{}' -- file_name
Make sure to include it with the IWYU pragma. Otherwise, IWYU may falsely instruct to remove the
include again.
-#include <config/bitcoin-config.h> // IWYU pragma: keep
+#include <bitcoin-build-config.h> // IWYU pragma: keep
"#,
defines_regex
));
@@ -379,7 +484,7 @@ include again.
if redundant {
return Err(r#"
^^^
-None of the files use a symbol declared in the bitcoin-config.h header. However, they are including
+None of the files use a symbol declared in the bitcoin-build-config.h header. However, they are including
the header. Consider removing the unused include.
"#
.to_string());
@@ -410,6 +515,7 @@ fn lint_markdown() -> LintResult {
"--offline",
"--ignore-path",
md_ignore_path_str.as_str(),
+ "--gitignore",
"--root-dir",
".",
])
@@ -419,11 +525,6 @@ fn lint_markdown() -> LintResult {
Ok(output) if output.status.success() => Ok(()),
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
- let filtered_stderr: String = stderr // Filter out this annoying trailing line
- .lines()
- .filter(|&line| line != "The following links could not be resolved:")
- .collect::<Vec<&str>>()
- .join("\n");
Err(format!(
r#"
One or more markdown links are broken.
@@ -433,7 +534,7 @@ Relative links are preferred (but not required) as jumping to file works nativel
Markdown link errors found:
{}
"#,
- filtered_stderr
+ stderr
))
}
Err(e) if e.kind() == ErrorKind::NotFound => {
diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan
index 9818d73fdf..94bd14e6c3 100644
--- a/test/sanitizer_suppressions/ubsan
+++ b/test/sanitizer_suppressions/ubsan
@@ -51,19 +51,18 @@ unsigned-integer-overflow:CCoinsViewCache::Uncache
unsigned-integer-overflow:CompressAmount
unsigned-integer-overflow:DecompressAmount
unsigned-integer-overflow:crypto/
-unsigned-integer-overflow:getchaintxstats*
unsigned-integer-overflow:MurmurHash3
unsigned-integer-overflow:CBlockPolicyEstimator::processBlockTx
unsigned-integer-overflow:TxConfirmStats::EstimateMedianVal
unsigned-integer-overflow:prevector.h
unsigned-integer-overflow:EvalScript
-unsigned-integer-overflow:xoroshiro128plusplus.h
+unsigned-integer-overflow:InsecureRandomContext::rand64
+unsigned-integer-overflow:InsecureRandomContext::SplitMix64
unsigned-integer-overflow:bitset_detail::PopCount
implicit-integer-sign-change:CBlockPolicyEstimator::processBlockTx
implicit-integer-sign-change:SetStdinEcho
implicit-integer-sign-change:compressor.h
implicit-integer-sign-change:crypto/
-implicit-integer-sign-change:getchaintxstats*
implicit-integer-sign-change:TxConfirmStats::removeTx
implicit-integer-sign-change:prevector.h
implicit-integer-sign-change:verify_flags
@@ -75,4 +74,6 @@ shift-base:arith_uint256.cpp
shift-base:crypto/
shift-base:streams.h
shift-base:FormatHDKeypath
-shift-base:xoroshiro128plusplus.h
+shift-base:InsecureRandomContext::rand64
+shift-base:RandomMixin<*>::randbits
+shift-base:RandomMixin<*>::randbits<*>
diff --git a/test/util/test_runner.py b/test/util/test_runner.py
index 1cd368f6f4..e4a77d53d6 100755
--- a/test/util/test_runner.py
+++ b/test/util/test_runner.py
@@ -5,7 +5,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test framework for bitcoin utils.
-Runs automatically during `make check`.
+Runs automatically during `ctest --test-dir build/`.
Can also be run manually."""