aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md6
-rw-r--r--test/functional/data/rpc_psbt.json21
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py4
-rwxr-xr-xtest/functional/feature_bind_extra.py13
-rwxr-xr-xtest/functional/feature_blockfilterindex_prune.py78
-rwxr-xr-xtest/functional/feature_coinstatsindex.py16
-rwxr-xr-xtest/functional/feature_config_args.py5
-rwxr-xr-xtest/functional/feature_csv_activation.py8
-rwxr-xr-xtest/functional/feature_dbcrash.py61
-rwxr-xr-xtest/functional/feature_fee_estimation.py4
-rwxr-xr-xtest/functional/feature_index_prune.py155
-rwxr-xr-xtest/functional/feature_init.py46
-rwxr-xr-xtest/functional/feature_maxuploadtarget.py2
-rwxr-xr-xtest/functional/feature_minchainwork.py29
-rwxr-xr-xtest/functional/feature_nulldummy.py65
-rwxr-xr-xtest/functional/feature_proxy.py18
-rwxr-xr-xtest/functional/feature_pruning.py7
-rwxr-xr-xtest/functional/feature_rbf.py515
-rwxr-xr-xtest/functional/feature_signet.py8
-rwxr-xr-xtest/functional/feature_taproot.py110
-rwxr-xr-xtest/functional/feature_versionbits_warning.py3
-rwxr-xr-xtest/functional/interface_rest.py11
-rwxr-xr-xtest/functional/interface_usdt_coinselection.py208
-rwxr-xr-xtest/functional/interface_usdt_utxocache.py3
-rwxr-xr-xtest/functional/mempool_accept.py4
-rwxr-xr-xtest/functional/mempool_expiry.py8
-rwxr-xr-xtest/functional/mempool_limit.py22
-rwxr-xr-xtest/functional/mempool_packages.py6
-rwxr-xr-xtest/functional/mempool_persist.py14
-rwxr-xr-xtest/functional/mempool_reorg.py9
-rwxr-xr-xtest/functional/mempool_spend_coinbase.py2
-rwxr-xr-xtest/functional/mempool_unbroadcast.py2
-rwxr-xr-xtest/functional/mempool_updatefromblock.py19
-rwxr-xr-xtest/functional/mining_prioritisetransaction.py48
-rwxr-xr-xtest/functional/p2p_addr_relay.py66
-rwxr-xr-xtest/functional/p2p_block_sync.py37
-rwxr-xr-xtest/functional/p2p_blockfilters.py11
-rwxr-xr-xtest/functional/p2p_blocksonly.py16
-rwxr-xr-xtest/functional/p2p_compactblocks.py168
-rwxr-xr-xtest/functional/p2p_compactblocks_blocksonly.py6
-rwxr-xr-xtest/functional/p2p_getaddr_caching.py64
-rwxr-xr-xtest/functional/p2p_invalid_tx.py68
-rwxr-xr-xtest/functional/p2p_message_capture.py6
-rwxr-xr-xtest/functional/p2p_permissions.py1
-rwxr-xr-xtest/functional/p2p_segwit.py6
-rwxr-xr-xtest/functional/p2p_timeouts.py5
-rwxr-xr-xtest/functional/p2p_unrequested_blocks.py15
-rwxr-xr-xtest/functional/rpc_createmultisig.py17
-rwxr-xr-xtest/functional/rpc_dumptxoutset.py6
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py25
-rwxr-xr-xtest/functional/rpc_getblockfrompeer.py13
-rwxr-xr-xtest/functional/rpc_mempool_entry_fee_fields_deprecation.py67
-rwxr-xr-xtest/functional/rpc_mempool_info.py100
-rwxr-xr-xtest/functional/rpc_misc.py2
-rwxr-xr-xtest/functional/rpc_net.py4
-rwxr-xr-xtest/functional/rpc_packages.py133
-rwxr-xr-xtest/functional/rpc_psbt.py60
-rwxr-xr-xtest/functional/rpc_rawtransaction.py165
-rwxr-xr-xtest/functional/rpc_signrawtransactionwithkey.py140
-rwxr-xr-xtest/functional/rpc_users.py3
-rw-r--r--test/functional/test_framework/address.py7
-rw-r--r--test/functional/test_framework/blocktools.py24
-rw-r--r--test/functional/test_framework/key.py4
-rwxr-xr-xtest/functional/test_framework/messages.py6
-rwxr-xr-xtest/functional/test_framework/p2p.py6
-rwxr-xr-xtest/functional/test_framework/script_util.py5
-rwxr-xr-xtest/functional/test_framework/test_framework.py16
-rwxr-xr-xtest/functional/test_framework/test_node.py8
-rw-r--r--test/functional/test_framework/util.py90
-rw-r--r--test/functional/test_framework/wallet.py177
-rwxr-xr-xtest/functional/test_runner.py29
-rwxr-xr-xtest/functional/wallet_basic.py2
-rwxr-xr-xtest/functional/wallet_coinbase_category.py1
-rwxr-xr-xtest/functional/wallet_createwallet.py2
-rwxr-xr-xtest/functional/wallet_crosschain.py60
-rwxr-xr-xtest/functional/wallet_descriptor.py18
-rwxr-xr-xtest/functional/wallet_importdescriptors.py2
-rwxr-xr-xtest/functional/wallet_listreceivedby.py46
-rwxr-xr-xtest/functional/wallet_send.py17
-rwxr-xr-xtest/functional/wallet_signrawtransactionwithwallet.py (renamed from test/functional/rpc_signrawtransaction.py)151
-rwxr-xr-xtest/functional/wallet_taproot.py37
-rwxr-xr-xtest/get_previous_releases.py18
-rw-r--r--test/lint/README.md3
-rwxr-xr-xtest/lint/all-lint.py22
-rwxr-xr-xtest/lint/extended-lint-all.sh26
-rwxr-xr-xtest/lint/extended-lint-cppcheck.sh88
-rwxr-xr-xtest/lint/lint-all.sh30
-rwxr-xr-xtest/lint/lint-assertions.py52
-rwxr-xr-xtest/lint/lint-assertions.sh34
-rwxr-xr-xtest/lint/lint-circular-dependencies.py79
-rwxr-xr-xtest/lint/lint-circular-dependencies.sh65
-rwxr-xr-xtest/lint/lint-files.py79
-rwxr-xr-xtest/lint/lint-format-strings.py100
-rwxr-xr-xtest/lint/lint-format-strings.sh44
-rwxr-xr-xtest/lint/lint-include-guards.py99
-rwxr-xr-xtest/lint/lint-include-guards.sh30
-rwxr-xr-xtest/lint/lint-includes.py7
-rwxr-xr-xtest/lint/lint-locale-dependence.py260
-rwxr-xr-xtest/lint/lint-locale-dependence.sh241
-rwxr-xr-xtest/lint/lint-logs.py4
-rwxr-xr-xtest/lint/lint-python-utf8-encoding.py73
-rwxr-xr-xtest/lint/lint-python-utf8-encoding.sh28
-rwxr-xr-xtest/lint/lint-shell-locale.py67
-rwxr-xr-xtest/lint/lint-shell-locale.sh25
-rwxr-xr-xtest/lint/lint-shell.py93
-rwxr-xr-xtest/lint/lint-shell.sh33
-rwxr-xr-xtest/lint/lint-spelling.py2
-rwxr-xr-xtest/lint/lint-tests.py87
-rwxr-xr-xtest/lint/lint-tests.sh35
-rwxr-xr-xtest/lint/lint-whitespace.py1
-rw-r--r--test/lint/spelling.ignore-words.txt2
-rw-r--r--test/sanitizer_suppressions/ubsan4
-rwxr-xr-xtest/util/test_runner.py12
113 files changed, 3198 insertions, 1987 deletions
diff --git a/test/README.md b/test/README.md
index e5a184d23c..6ca7cc0016 100644
--- a/test/README.md
+++ b/test/README.md
@@ -98,7 +98,7 @@ test/functional/test_runner.py --extended
In order to run backwards compatibility tests, download the previous node binaries:
```
-test/get_previous_releases.py -b v22.0 v0.21.0 v0.20.1 v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2 v0.14.3
+test/get_previous_releases.py -b v23.0 v22.0 v0.21.0 v0.20.1 v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2 v0.14.3
```
By default, up to 4 tests will be run in parallel by test_runner. To specify
@@ -309,7 +309,7 @@ Use the `-v` option for verbose output.
| [`lint-python.py`](lint/lint-python.py) | [mypy](https://github.com/python/mypy)
| [`lint-python.py`](lint/lint-python.py) | [pyzmq](https://github.com/zeromq/pyzmq)
| [`lint-python-dead-code.py`](lint/lint-python-dead-code.py) | [vulture](https://github.com/jendrikseipp/vulture)
-| [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck)
+| [`lint-shell.py`](lint/lint-shell.py) | [ShellCheck](https://github.com/koalaman/shellcheck)
| [`lint-spelling.py`](lint/lint-spelling.py) | [codespell](https://github.com/codespell-project/codespell)
In use versions and install instructions are available in the [CI setup](../ci/lint/04_install.sh).
@@ -327,7 +327,7 @@ test/lint/lint-files.py
You can run all the shell-based lint tests by running:
```
-test/lint/lint-all.sh
+test/lint/all-lint.py
```
# Writing functional tests
diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json
index 8672400a92..430a1802a8 100644
--- a/test/functional/data/rpc_psbt.json
+++ b/test/functional/data/rpc_psbt.json
@@ -27,7 +27,18 @@
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQQAAQQBagA=",
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQUAAQUBUQA=",
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQcAAQcBUQA=",
- "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA="
+ "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA=",
+ "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARchAv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyAAAA",
+ "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARM/Fzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1AAAA",
+ "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARNCFzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1FwGqAAAA",
+ "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXIhYC/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIZAHcrLadWAACAAQAAgAAAAIABAAAAAAAAAAAAAA==",
+ "cHNidP8BAH0CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Aoh7AQAAAAAAFgAUI4KHHH6EIaAAk/dU2RKB5nWHS59gawQqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAABBSEC/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIA",
+ "cHNidP8BAH0CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Aoh7AQAAAAAAFgAUI4KHHH6EIaAAk/dU2RKB5nWHS59gawQqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAiBwL+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAAA==",
+ "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJCFAIssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20s2XDhX1P8DIL5UP1WD/qRm3YXK+AXNoqJkTrwdPQAsJQIl1aqNznMxonsD886NgvjLMC1mxbpOh6LtGBXJrLKej/3BsQXZkljKyzGjh+RK4pXjjcZzncQiFx6lm9JvNQ8sAAA==",
+ "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"
],
"valid" : [
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA",
@@ -43,7 +54,13 @@
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAFQoYn3yLGjhv/o7tkbODDHp7zR53jAIBAhUK8pG6UBXfNIyAhT+luw95RvXJ4bMBAQAAAA==",
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAIQtL9RIvNEVUxTveLruM0rfj0WAK1jHDhaXXzOI8d4VFmgEBIQuhKHH+4hD7hhkpHq6hlFgcvSUx5LI3WdIl9oBpI/YyIgIBAgAAAA==",
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAFQwVzEnhkcvFINkZRGAKXLd69qoykQIBAhUMxRtmvO1eRJEAG9cCZpdw3M9ECYIBAQAAAA==",
- "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAIQ12pWrO2RXSUT3NhMLDeLLoqlzWMrW3HKLyrFsOOmSb2wIBAiENnBLP3ATHRYTXh6w9I3chMsGFJLx6so3sQhm4/FtCX3ABAQAAAA=="
+ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAIQ12pWrO2RXSUT3NhMLDeLLoqlzWMrW3HKLyrFsOOmSb2wIBAiENnBLP3ATHRYTXh6w9I3chMsGFJLx6so3sQhm4/FtCX3ABAQAAAA==",
+ "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgAiAgNrdyptt02HU8mKgnlY3mx4qzMSEJ830+AwRIQkLs5z2Bh3Ky2nVAAAgAEAAIAAAACAAAAAAAAAAAAA",
+ "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1cBE0C7U+yRe62dkGrxuocYHEi4as5aritTYFpyXKdGJWMUdvxvW67a9PLuD0d/NvWPOXDVuCc7fkl7l68uPxJcl680IRb+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAARcg/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIAIgIDa3cqbbdNh1PJioJ5WN5seKszEhCfN9PgMESEJC7Oc9gYdystp1QAAIABAACAAAAAgAAAAAAAAAAAAA==",
+ "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSARJNp67JLM0GyVRWJkf0N7E4uVchqEvivyJ2u92rPmcSEHESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEZAHcrLadWAACAAQAAgAAAAIAAAAAABQAAAAA=",
+ "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMBCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA",
+ "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgCoy9yG3hzhwPnK6yLW33ztNoP+Qj4F0eQCqHk0HW9vUAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAEGbwLAIiBzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAqwCwCIgYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWmsAcAiIET6pJoDON5IjI3//s37bzKfOAvVZu8gyN9tgT6rHEJzrCEHRPqkmgM43kiMjf/+zftvMp84C9Vm7yDI322BPqscQnM5AfBreYuSoQ7ZqdC7/Trxc6U7FhfaOkFZygCCFs2Fay4Odystp1YAAIABAACAAQAAgAAAAAADAAAAIQdQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAUAfEYeXSEHYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWk5ARis5AmIl4Xg6nDO67jhyokqenjq7eDy4pbPQ1lhqPTKdystp1YAAIABAACAAgAAgAAAAAADAAAAIQdzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAjkBKaW0kVCQFi11mv0/4Pk/ozJgVtC0CIy5M8rngmy42Cx3Ky2nVgAAgAEAAIADAACAAAAAAAMAAAAA",
+ "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwlAv4GNl1fW/+tTi6BX+0wfxOD17xhudlvrVkeR4Cr1/T1eJVHU404z2G8na4LJnHmu0/A5Wgge/NLMLGXdfmk9eUEUQyCwvxbwEbU+p75hWSSqfyfl0prSDqEVXYSGdsO60bIRXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+EDh8atvq/omsjbyGDNxncHUKKt2jYD5H5mI2KvvR7+4Y7sfKlKfdowV8AzjTsKDzcB+iPhCi+KPbvZAQ8MpEYEaQRT6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqW99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwQOwfA3kgZGHIM0IoVCMyZwirAx8NpKJT7kWq+luMkgNNi2BUkPjNE+APmJmJuX4hX6o28S3uNpPS2szzeBwXV/ZiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMBCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA"
],
"creator" : [
{
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index a7fb3184a6..59a12193fd 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -34,11 +34,12 @@ from test_framework.util import (
class BackwardsCompatibilityTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
- self.num_nodes = 9
+ self.num_nodes = 10
# 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
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to receive coins, swap wallets, etc
+ ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v23.0
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v22.0
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.21.0
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.20.1
@@ -57,6 +58,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[
None,
None,
+ 230000,
220000,
210000,
200100,
diff --git a/test/functional/feature_bind_extra.py b/test/functional/feature_bind_extra.py
index 6802da8d48..5de9ff203c 100755
--- a/test/functional/feature_bind_extra.py
+++ b/test/functional/feature_bind_extra.py
@@ -18,12 +18,12 @@ from test_framework.test_framework import (
SkipTest,
)
from test_framework.util import (
- PORT_MIN,
- PORT_RANGE,
assert_equal,
+ p2p_port,
rpc_port,
)
+
class BindExtraTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
@@ -33,11 +33,6 @@ class BindExtraTest(BitcoinTestFramework):
self.num_nodes = 2
def setup_network(self):
- # Override setup_network() because we want to put the result of
- # p2p_port() in self.extra_args[], before the nodes are started.
- # p2p_port() is not usable in set_test_params() because PortSeed.n is
- # not set at that time.
-
# Due to OS-specific network stats queries, we only run on Linux.
self.log.info("Checking for Linux")
if not sys.platform.startswith('linux'):
@@ -45,8 +40,8 @@ class BindExtraTest(BitcoinTestFramework):
loopback_ipv4 = addr_to_hex("127.0.0.1")
- # Start custom ports after p2p and rpc ports.
- port = PORT_MIN + 2 * PORT_RANGE
+ # Start custom ports by reusing unused p2p ports
+ port = p2p_port(self.num_nodes)
# Array of tuples [command line arguments, expected bind addresses].
self.expected = []
diff --git a/test/functional/feature_blockfilterindex_prune.py b/test/functional/feature_blockfilterindex_prune.py
deleted file mode 100755
index c983ceda6f..0000000000
--- a/test/functional/feature_blockfilterindex_prune.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2020-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 blockfilterindex in conjunction with prune."""
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import (
- assert_equal,
- assert_greater_than,
- assert_raises_rpc_error,
-)
-
-
-class FeatureBlockfilterindexPruneTest(BitcoinTestFramework):
- def set_test_params(self):
- self.num_nodes = 1
- self.extra_args = [["-fastprune", "-prune=1", "-blockfilterindex=1"]]
-
- def sync_index(self, height):
- expected = {'basic block filter index': {'synced': True, 'best_block_height': height}}
- self.wait_until(lambda: self.nodes[0].getindexinfo() == expected)
-
- def run_test(self):
- self.log.info("check if we can access a blockfilter when pruning is enabled but no blocks are actually pruned")
- self.sync_index(height=200)
- assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0)
- self.generate(self.nodes[0], 500)
- self.sync_index(height=700)
-
- self.log.info("prune some blocks")
- pruneheight = self.nodes[0].pruneblockchain(400)
- # the prune heights used here and below are magic numbers that are determined by the
- # thresholds at which block files wrap, so they depend on disk serialization and default block file size.
- assert_equal(pruneheight, 249)
-
- self.log.info("check if we can access the tips blockfilter when we have pruned some blocks")
- assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0)
-
- self.log.info("check if we can access the blockfilter of a pruned block")
- assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getblockhash(2))['filter']), 0)
-
- # mine and sync index up to a height that will later be the pruneheight
- self.generate(self.nodes[0], 51)
- self.sync_index(height=751)
-
- self.log.info("start node without blockfilterindex")
- self.restart_node(0, extra_args=["-fastprune", "-prune=1"])
-
- self.log.info("make sure accessing the blockfilters throws an error")
- assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[0].getblockfilter, self.nodes[0].getblockhash(2))
- self.generate(self.nodes[0], 749)
-
- self.log.info("prune exactly up to the blockfilterindexes best block while blockfilters are disabled")
- pruneheight_2 = self.nodes[0].pruneblockchain(1000)
- assert_equal(pruneheight_2, 751)
- self.restart_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"])
- self.log.info("make sure that we can continue with the partially synced index after having pruned up to the index height")
- self.sync_index(height=1500)
-
- self.log.info("prune below the blockfilterindexes best block while blockfilters are disabled")
- self.restart_node(0, extra_args=["-fastprune", "-prune=1"])
- self.generate(self.nodes[0], 1000)
- pruneheight_3 = self.nodes[0].pruneblockchain(2000)
- assert_greater_than(pruneheight_3, pruneheight_2)
- self.stop_node(0)
-
- self.log.info("make sure we get an init error when starting the node again with block filters")
- self.nodes[0].assert_start_raises_init_error(
- extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"],
- expected_msg="Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)",
- )
-
- self.log.info("make sure the node starts again with the -reindex arg")
- self.start_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex", "-reindex"])
-
-
-if __name__ == '__main__':
- FeatureBlockfilterindexPruneTest().main()
diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py
index f865661894..2e21638f80 100755
--- a/test/functional/feature_coinstatsindex.py
+++ b/test/functional/feature_coinstatsindex.py
@@ -223,6 +223,22 @@ class CoinStatsIndexTest(BitcoinTestFramework):
res10 = index_node.gettxoutsetinfo('muhash')
assert(res8['txouts'] < res10['txouts'])
+ self.log.info("Test that the index works with -reindex")
+
+ self.restart_node(1, extra_args=["-coinstatsindex", "-reindex"])
+ res11 = index_node.gettxoutsetinfo('muhash')
+ assert_equal(res11, res10)
+
+ self.log.info("Test that -reindex-chainstate is disallowed with coinstatsindex")
+
+ self.stop_node(1)
+ self.nodes[1].assert_start_raises_init_error(
+ expected_msg='Error: -reindex-chainstate option is not compatible with -coinstatsindex. '
+ 'Please temporarily disable coinstatsindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.',
+ extra_args=['-coinstatsindex', '-reindex-chainstate'],
+ )
+ self.restart_node(1, extra_args=["-coinstatsindex"])
+
def _test_use_index_option(self):
self.log.info("Test use_index option for nodes running the index")
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index eea5fa24ee..6c51a5ac31 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -85,7 +85,7 @@ class ConfArgsTest(BitcoinTestFramework):
def test_invalid_command_line_options(self):
self.nodes[0].assert_start_raises_init_error(
- expected_msg='Error: No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.',
+ expected_msg='Error: Error parsing command line arguments: Can not set -proxy with no value. Please specify value with -proxy=value.',
extra_args=['-proxy'],
)
@@ -247,7 +247,8 @@ class ConfArgsTest(BitcoinTestFramework):
conf_file = os.path.join(default_data_dir, "bitcoin.conf")
# datadir needs to be set before [chain] section
- conf_file_contents = open(conf_file, encoding='utf8').read()
+ with open(conf_file, encoding='utf8') as f:
+ conf_file_contents = f.read()
with open(conf_file, 'w', encoding='utf8') as f:
f.write(f"datadir={new_data_dir}\n")
f.write(conf_file_contents)
diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py
index 6470c1c5eb..bff95c3b94 100755
--- a/test/functional/feature_csv_activation.py
+++ b/test/functional/feature_csv_activation.py
@@ -112,6 +112,7 @@ class BIP68_112_113Test(BitcoinTestFramework):
tx.nVersion = txversion
self.miniwallet.sign_tx(tx)
tx.vin[0].scriptSig = CScript([-1, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))
+ tx.rehash()
return tx
def create_bip112emptystack(self, input, txversion):
@@ -119,6 +120,7 @@ class BIP68_112_113Test(BitcoinTestFramework):
tx.nVersion = txversion
self.miniwallet.sign_tx(tx)
tx.vin[0].scriptSig = CScript([OP_CHECKSEQUENCEVERIFY] + list(CScript(tx.vin[0].scriptSig)))
+ tx.rehash()
return tx
def send_generic_input_tx(self, coinbases):
@@ -136,7 +138,6 @@ class BIP68_112_113Test(BitcoinTestFramework):
tx.nVersion = txversion
tx.vin[0].nSequence = locktime + locktime_delta
self.miniwallet.sign_tx(tx)
- tx.rehash()
txs.append({'tx': tx, 'sdf': sdf, 'stf': stf})
return txs
@@ -339,20 +340,16 @@ class BIP68_112_113Test(BitcoinTestFramework):
# BIP 113 tests should now fail regardless of version number if nLockTime isn't satisfied by new rules
bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
self.miniwallet.sign_tx(bip113tx_v1)
- bip113tx_v1.rehash()
bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
self.miniwallet.sign_tx(bip113tx_v2)
- bip113tx_v2.rehash()
for bip113tx in [bip113tx_v1, bip113tx_v2]:
self.send_blocks([self.create_test_block([bip113tx])], success=False, reject_reason='bad-txns-nonfinal')
# BIP 113 tests should now pass if the locktime is < MTP
bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 - 1 # < MTP of prior block
self.miniwallet.sign_tx(bip113tx_v1)
- bip113tx_v1.rehash()
bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 - 1 # < MTP of prior block
self.miniwallet.sign_tx(bip113tx_v2)
- bip113tx_v2.rehash()
for bip113tx in [bip113tx_v1, bip113tx_v2]:
self.send_blocks([self.create_test_block([bip113tx])])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
@@ -477,7 +474,6 @@ class BIP68_112_113Test(BitcoinTestFramework):
for tx in [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if not tx['sdf'] and tx['stf']]:
tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME | SEQ_TYPE_FLAG
self.miniwallet.sign_tx(tx)
- tx.rehash()
time_txs.append(tx)
self.send_blocks([self.create_test_block(time_txs)])
diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py
index 3e60efbb3c..f606f26e70 100755
--- a/test/functional/feature_dbcrash.py
+++ b/test/functional/feature_dbcrash.py
@@ -30,17 +30,17 @@ import http.client
import random
import time
+from test_framework.blocktools import COINBASE_MATURITY
from test_framework.messages import (
COIN,
- COutPoint,
- CTransaction,
- CTxIn,
- CTxOut,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- create_confirmed_utxos,
+)
+from test_framework.wallet import (
+ MiniWallet,
+ getnewdestination,
)
@@ -62,17 +62,13 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args
# Node3 is a normal node with default args, except will mine full blocks
- # and non-standard txs (e.g. txs with "dust" outputs)
- self.node3_args = ["-blockmaxweight=4000000", "-acceptnonstdtxn"]
+ # and txs with "dust" outputs
+ self.node3_args = ["-blockmaxweight=4000000", "-dustrelayfee=0"]
self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
def setup_network(self):
self.add_nodes(self.num_nodes, extra_args=self.extra_args)
self.start_nodes()
- self.import_deterministic_coinbase_privkeys()
# Leave them unconnected, we'll use submitblock directly in this test
def restart_node(self, node_index, expected_tip):
@@ -190,34 +186,36 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
num_transactions = 0
random.shuffle(utxo_list)
while len(utxo_list) >= 2 and num_transactions < count:
- tx = CTransaction()
- input_amount = 0
- for _ in range(2):
- utxo = utxo_list.pop()
- tx.vin.append(CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout'])))
- input_amount += int(utxo['amount'] * COIN)
- output_amount = (input_amount - FEE) // 3
-
- if output_amount <= 0:
+ utxos_to_spend = [utxo_list.pop() for _ in range(2)]
+ input_amount = int(sum([utxo['value'] for utxo in utxos_to_spend]) * COIN)
+ if input_amount < FEE:
# Sanity check -- if we chose inputs that are too small, skip
continue
- for _ in range(3):
- tx.vout.append(CTxOut(output_amount, bytes.fromhex(utxo['scriptPubKey'])))
-
- # Sign and send the transaction to get into the mempool
- tx_signed_hex = node.signrawtransactionwithwallet(tx.serialize().hex())['hex']
- node.sendrawtransaction(tx_signed_hex)
+ self.wallet.send_self_transfer_multi(
+ from_node=node,
+ utxos_to_spend=utxos_to_spend,
+ num_outputs=3,
+ fee_per_output=FEE // 3,
+ )
num_transactions += 1
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[3])
+ self.wallet.rescan_utxos()
+ initial_height = self.nodes[3].getblockcount()
+ self.generate(self.nodes[3], COINBASE_MATURITY, sync_fun=self.no_op)
+
# Track test coverage statistics
self.restart_counts = [0, 0, 0] # Track the restarts for nodes 0-2
self.crashed_on_restart = 0 # Track count of crashes during recovery
# Start by creating a lot of utxos on node3
- initial_height = self.nodes[3].getblockcount()
- utxo_list = create_confirmed_utxos(self, self.nodes[3].getnetworkinfo()['relayfee'], self.nodes[3], 5000, sync_fun=self.no_op)
+ utxo_list = []
+ for _ in range(5):
+ utxo_list.extend(self.wallet.send_self_transfer_multi(from_node=self.nodes[3], num_outputs=1000)['new_utxos'])
+ self.generate(self.nodes[3], 1, sync_fun=self.no_op)
+ assert_equal(len(self.nodes[3].getrawmempool()), 0)
self.log.info(f"Prepped {len(utxo_list)} utxo entries")
# Sync these blocks with the other nodes
@@ -257,13 +255,14 @@ class ChainstateWriteCrashTest(BitcoinTestFramework):
self.nodes[3],
nblocks=min(10, current_height + 1 - self.nodes[3].getblockcount()),
# new address to avoid mining a block that has just been invalidated
- address=self.nodes[3].getnewaddress(),
+ address=getnewdestination()[2],
sync_fun=self.no_op,
))
self.log.debug(f"Syncing {len(block_hashes)} new blocks...")
self.sync_node3blocks(block_hashes)
- utxo_list = self.nodes[3].listunspent()
- self.log.debug(f"Node3 utxo count: {len(utxo_list)}")
+ self.wallet.rescan_utxos()
+ utxo_list = self.wallet.get_utxos()
+ self.log.debug(f"MiniWallet utxo count: {len(utxo_list)}")
# Check that the utxo hashes agree with node3
# Useful side effect: each utxo cache gets flushed here, so that we
diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py
index 422612a78e..b0cbcf4edf 100755
--- a/test/functional/feature_fee_estimation.py
+++ b/test/functional/feature_fee_estimation.py
@@ -51,9 +51,9 @@ def small_txpuzzle_randfee(
if total_in <= amount + fee:
raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}")
tx = wallet.create_self_transfer_multi(
- from_node=from_node,
utxos_to_spend=utxos_to_spend,
- fee_per_output=0)
+ fee_per_output=0,
+ )["tx"]
tx.vout[0].nValue = int((total_in - amount - fee) * COIN)
tx.vout.append(deepcopy(tx.vout[0]))
tx.vout[1].nValue = int(amount * COIN)
diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py
new file mode 100755
index 0000000000..bc85e43a57
--- /dev/null
+++ b/test/functional/feature_index_prune.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020-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 indices in conjunction with prune."""
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ assert_raises_rpc_error,
+)
+
+
+class FeatureIndexPruneTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 4
+ self.extra_args = [
+ ["-fastprune", "-prune=1", "-blockfilterindex=1"],
+ ["-fastprune", "-prune=1", "-coinstatsindex=1"],
+ ["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"],
+ []
+ ]
+
+ def sync_index(self, height):
+ expected_filter = {
+ 'basic block filter index': {'synced': True, 'best_block_height': height},
+ }
+ self.wait_until(lambda: self.nodes[0].getindexinfo() == expected_filter)
+
+ expected_stats = {
+ 'coinstatsindex': {'synced': True, 'best_block_height': height}
+ }
+ self.wait_until(lambda: self.nodes[1].getindexinfo() == expected_stats)
+
+ expected = {**expected_filter, **expected_stats}
+ self.wait_until(lambda: self.nodes[2].getindexinfo() == expected)
+
+ def reconnect_nodes(self):
+ self.connect_nodes(0,1)
+ self.connect_nodes(0,2)
+ self.connect_nodes(0,3)
+
+ def mine_batches(self, blocks):
+ n = blocks // 250
+ for _ in range(n):
+ self.generate(self.nodes[0], 250)
+ self.generate(self.nodes[0], blocks % 250)
+ self.sync_blocks()
+
+ def restart_without_indices(self):
+ for i in range(3):
+ self.restart_node(i, extra_args=["-fastprune", "-prune=1"])
+ self.reconnect_nodes()
+
+ def run_test(self):
+ filter_nodes = [self.nodes[0], self.nodes[2]]
+ stats_nodes = [self.nodes[1], self.nodes[2]]
+
+ self.log.info("check if we can access blockfilters and coinstats when pruning is enabled but no blocks are actually pruned")
+ self.sync_index(height=200)
+ tip = self.nodes[0].getbestblockhash()
+ for node in filter_nodes:
+ assert_greater_than(len(node.getblockfilter(tip)['filter']), 0)
+ for node in stats_nodes:
+ assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash'])
+
+ self.mine_batches(500)
+ self.sync_index(height=700)
+
+ self.log.info("prune some blocks")
+ for node in self.nodes[:2]:
+ with node.assert_debug_log(['limited pruning to height 689']):
+ pruneheight_new = node.pruneblockchain(400)
+ # the prune heights used here and below are magic numbers that are determined by the
+ # thresholds at which block files wrap, so they depend on disk serialization and default block file size.
+ assert_equal(pruneheight_new, 248)
+
+ self.log.info("check if we can access the tips blockfilter and coinstats when we have pruned some blocks")
+ tip = self.nodes[0].getbestblockhash()
+ for node in filter_nodes:
+ assert_greater_than(len(node.getblockfilter(tip)['filter']), 0)
+ for node in stats_nodes:
+ assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash'])
+
+ self.log.info("check if we can access the blockfilter and coinstats of a pruned block")
+ height_hash = self.nodes[0].getblockhash(2)
+ for node in filter_nodes:
+ assert_greater_than(len(node.getblockfilter(height_hash)['filter']), 0)
+ for node in stats_nodes:
+ assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=height_hash)['muhash'])
+
+ # mine and sync index up to a height that will later be the pruneheight
+ self.generate(self.nodes[0], 51)
+ self.sync_index(height=751)
+
+ self.restart_without_indices()
+
+ self.log.info("make sure trying to access the indices throws errors")
+ for node in filter_nodes:
+ msg = "Index is not enabled for filtertype basic"
+ assert_raises_rpc_error(-1, msg, node.getblockfilter, height_hash)
+ for node in stats_nodes:
+ msg = "Querying specific block heights requires coinstatsindex"
+ assert_raises_rpc_error(-8, msg, node.gettxoutsetinfo, "muhash", height_hash)
+
+ self.mine_batches(749)
+
+ self.log.info("prune exactly up to the indices best blocks while the indices are disabled")
+ for i in range(3):
+ pruneheight_2 = self.nodes[i].pruneblockchain(1000)
+ assert_equal(pruneheight_2, 750)
+ # Restart the nodes again with the indices activated
+ self.restart_node(i, extra_args=self.extra_args[i])
+
+ self.log.info("make sure that we can continue with the partially synced indices after having pruned up to the index height")
+ self.sync_index(height=1500)
+
+ self.log.info("prune further than the indices best blocks while the indices are disabled")
+ self.restart_without_indices()
+ self.mine_batches(1000)
+
+ for i in range(3):
+ pruneheight_3 = self.nodes[i].pruneblockchain(2000)
+ assert_greater_than(pruneheight_3, pruneheight_2)
+ self.stop_node(i)
+
+ self.log.info("make sure we get an init error when starting the nodes again with the indices")
+ filter_msg = "Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"
+ stats_msg = "Error: coinstatsindex best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"
+ for i, msg in enumerate([filter_msg, stats_msg, filter_msg]):
+ self.nodes[i].assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg)
+
+ self.log.info("make sure the nodes start again with the indices and an additional -reindex arg")
+ for i in range(3):
+ restart_args = self.extra_args[i]+["-reindex"]
+ self.restart_node(i, extra_args=restart_args)
+ # The nodes need to be reconnected to the non-pruning node upon restart, otherwise they will be stuck
+ self.connect_nodes(i, 3)
+
+ self.sync_blocks(timeout=300)
+
+ for node in self.nodes[:2]:
+ with node.assert_debug_log(['limited pruning to height 2489']):
+ pruneheight_new = node.pruneblockchain(2500)
+ assert_equal(pruneheight_new, 2005)
+
+ self.log.info("ensure that prune locks don't prevent indices from failing in a reorg scenario")
+ with self.nodes[0].assert_debug_log(['basic block filter index prune lock moved back to 2480']):
+ self.nodes[3].invalidateblock(self.nodes[0].getblockhash(2480))
+ self.generate(self.nodes[3], 30)
+ self.sync_blocks()
+
+
+if __name__ == '__main__':
+ FeatureIndexPruneTest().main()
diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py
index d0cb1e10e2..13c7326519 100755
--- a/test/functional/feature_init.py
+++ b/test/functional/feature_init.py
@@ -49,33 +49,33 @@ class InitStressTest(BitcoinTestFramework):
assert_equal(200, node.getblockcount())
lines_to_terminate_after = [
- 'Validating signatures for all blocks',
- 'scheduler thread start',
- 'Starting HTTP server',
- 'Loading P2P addresses',
- 'Loading banlist',
- 'Loading block index',
- 'Switching active chainstate',
- 'Checking all blk files are present',
- 'Loaded best chain:',
- 'init message: Verifying blocks',
- 'init message: Starting network threads',
- 'net thread start',
- 'addcon thread start',
- 'loadblk thread start',
- 'txindex thread start',
- 'block filter index thread start',
- 'coinstatsindex thread start',
- 'msghand thread start',
- 'net thread start',
- 'addcon thread start',
+ b'Validating signatures for all blocks',
+ b'scheduler thread start',
+ b'Starting HTTP server',
+ b'Loading P2P addresses',
+ b'Loading banlist',
+ b'Loading block index',
+ b'Switching active chainstate',
+ b'Checking all blk files are present',
+ b'Loaded best chain:',
+ b'init message: Verifying blocks',
+ b'init message: Starting network threads',
+ b'net thread start',
+ b'addcon thread start',
+ b'loadblk thread start',
+ b'txindex thread start',
+ b'block filter index thread start',
+ b'coinstatsindex thread start',
+ b'msghand thread start',
+ b'net thread start',
+ b'addcon thread start',
]
if self.is_wallet_compiled():
- lines_to_terminate_after.append('Verifying wallet')
+ lines_to_terminate_after.append(b'Verifying wallet')
for terminate_line in lines_to_terminate_after:
- self.log.info(f"Starting node and will exit after line '{terminate_line}'")
- with node.wait_for_debug_log([terminate_line], ignore_case=True):
+ self.log.info(f"Starting node and will exit after line {terminate_line}")
+ with node.wait_for_debug_log([terminate_line]):
node.start(extra_args=['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1'])
self.log.debug("Terminating node after terminate line was found")
sigterm_node()
diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py
index 0b9d651226..3ea412002a 100755
--- a/test/functional/feature_maxuploadtarget.py
+++ b/test/functional/feature_maxuploadtarget.py
@@ -46,7 +46,7 @@ class MaxUploadTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [[
"-maxuploadtarget=800M",
- "-acceptnonstdtxn=1",
+ "-datacarriersize=100000",
]]
self.supports_cli = False
diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py
index 489a729cfc..9d0ad5ef9d 100755
--- a/test/functional/feature_minchainwork.py
+++ b/test/functional/feature_minchainwork.py
@@ -17,6 +17,7 @@ only succeeds past a given node once its nMinimumChainWork has been exceeded.
import time
+from test_framework.p2p import P2PInterface, msg_getheaders
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
@@ -41,6 +42,9 @@ class MinimumChainWorkTest(BitcoinTestFramework):
for i in range(self.num_nodes-1):
self.connect_nodes(i+1, i)
+ # Set clock of node2 2 days ahead, to keep it in IBD during this test.
+ self.nodes[2].setmocktime(int(time.time()) + 48*60*60)
+
def run_test(self):
# Start building a chain on node0. node2 shouldn't be able to sync until node1's
# minchainwork is exceeded
@@ -71,6 +75,15 @@ class MinimumChainWorkTest(BitcoinTestFramework):
assert self.nodes[1].getbestblockhash() != self.nodes[0].getbestblockhash()
assert_equal(self.nodes[2].getblockcount(), starting_blockcount)
+ self.log.info("Check that getheaders requests to node2 are ignored")
+ peer = self.nodes[2].add_p2p_connection(P2PInterface())
+ msg = msg_getheaders()
+ msg.locator.vHave = [int(self.nodes[2].getbestblockhash(), 16)]
+ msg.hashstop = 0
+ peer.send_and_ping(msg)
+ time.sleep(5)
+ assert ("headers" not in peer.last_message or len(peer.last_message["headers"].headers) == 0)
+
self.log.info("Generating one more block")
self.generate(self.nodes[0], 1)
@@ -85,5 +98,21 @@ class MinimumChainWorkTest(BitcoinTestFramework):
self.sync_all()
self.log.info(f"Blockcounts: {[n.getblockcount() for n in self.nodes]}")
+ self.log.info("Test that getheaders requests to node2 are not ignored")
+ peer.send_and_ping(msg)
+ assert "headers" in peer.last_message
+
+ # Verify that node2 is in fact still in IBD (otherwise this test may
+ # not be exercising the logic we want!)
+ assert_equal(self.nodes[2].getblockchaininfo()['initialblockdownload'], True)
+
+ self.log.info("Test -minimumchainwork with a non-hex value")
+ 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',
+ )
+
+
if __name__ == '__main__':
MinimumChainWorkTest().main()
diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py
index 7a84098a83..9bfb79057e 100755
--- a/test/functional/feature_nulldummy.py
+++ b/test/functional/feature_nulldummy.py
@@ -19,9 +19,11 @@ from test_framework.blocktools import (
NORMAL_GBT_REQUEST_PARAMS,
add_witness_commitment,
create_block,
- create_transaction,
)
-from test_framework.messages import CTransaction
+from test_framework.messages import (
+ CTransaction,
+ tx_from_hex,
+)
from test_framework.script import (
OP_0,
OP_TRUE,
@@ -31,6 +33,9 @@ from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
+from test_framework.wallet import getnewdestination
+from test_framework.key import ECKey
+from test_framework.wallet_util import bytes_to_wif
NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)"
@@ -55,22 +60,26 @@ class NULLDUMMYTest(BitcoinTestFramework):
'-par=1', # Use only one script thread to get the exact reject reason for testing
]]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ def create_transaction(self, *, txid, input_details=None, addr, amount, privkey):
+ input = {"txid": txid, "vout": 0}
+ output = {addr: amount}
+ rawtx = self.nodes[0].createrawtransaction([input], output)
+ # Details only needed for scripthash or witness spends
+ input = None if not input_details else [{**input, **input_details}]
+ signedtx = self.nodes[0].signrawtransactionwithkey(rawtx, [privkey], input)
+ return tx_from_hex(signedtx["hex"])
def run_test(self):
- self.nodes[0].createwallet(wallet_name='wmulti', disable_private_keys=True)
- wmulti = self.nodes[0].get_wallet_rpc('wmulti')
- w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
- self.address = w0.getnewaddress()
- self.pubkey = w0.getaddressinfo(self.address)['pubkey']
- self.ms_address = wmulti.addmultisigaddress(1, [self.pubkey])['address']
- self.wit_address = w0.getnewaddress(address_type='p2sh-segwit')
- self.wit_ms_address = wmulti.addmultisigaddress(1, [self.pubkey], '', 'p2sh-segwit')['address']
- if not self.options.descriptors:
- # Legacy wallets need to import these so that they are watched by the wallet. This is unnecessary (and does not need to be tested) for descriptor wallets
- wmulti.importaddress(self.ms_address)
- wmulti.importaddress(self.wit_ms_address)
+ eckey = ECKey()
+ eckey.generate()
+ self.privkey = bytes_to_wif(eckey.get_bytes())
+ self.pubkey = eckey.get_pubkey().get_bytes().hex()
+ cms = self.nodes[0].createmultisig(1, [self.pubkey])
+ wms = self.nodes[0].createmultisig(1, [self.pubkey], 'p2sh-segwit')
+ self.ms_address = cms["address"]
+ ms_unlock_details = {"scriptPubKey": self.nodes[0].validateaddress(self.ms_address)["scriptPubKey"],
+ "redeemScript": cms["redeemScript"]}
+ self.wit_ms_address = wms['address']
self.coinbase_blocks = self.generate(self.nodes[0], 2) # block height = 2
coinbase_txid = []
@@ -82,16 +91,23 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.lastblocktime = int(time.time()) + self.lastblockheight
self.log.info(f"Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [{COINBASE_MATURITY + 3}]")
- test1txs = [create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, amount=49)]
+ test1txs = [self.create_transaction(txid=coinbase_txid[0], addr=self.ms_address, amount=49,
+ privkey=self.nodes[0].get_deterministic_priv_key().key)]
txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize_with_witness().hex(), 0)
- test1txs.append(create_transaction(self.nodes[0], txid1, self.ms_address, amount=48))
+ test1txs.append(self.create_transaction(txid=txid1, input_details=ms_unlock_details,
+ addr=self.ms_address, amount=48,
+ privkey=self.privkey))
txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize_with_witness().hex(), 0)
- test1txs.append(create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, amount=49))
+ test1txs.append(self.create_transaction(txid=coinbase_txid[1],
+ addr=self.wit_ms_address, amount=49,
+ privkey=self.nodes[0].get_deterministic_priv_key().key))
txid3 = self.nodes[0].sendrawtransaction(test1txs[2].serialize_with_witness().hex(), 0)
self.block_submit(self.nodes[0], test1txs, accept=True)
self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation")
- test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, amount=47)
+ test2tx = self.create_transaction(txid=txid2, input_details=ms_unlock_details,
+ addr=self.ms_address, amount=47,
+ privkey=self.privkey)
invalidate_nulldummy_tx(test2tx)
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), 0)
@@ -99,14 +115,19 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.block_submit(self.nodes[0], [test2tx], accept=True)
self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation")
- test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46)
+ test4tx = self.create_transaction(txid=test2tx.hash, input_details=ms_unlock_details,
+ addr=getnewdestination()[2], amount=46,
+ privkey=self.privkey)
test6txs = [CTransaction(test4tx)]
invalidate_nulldummy_tx(test4tx)
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), 0)
self.block_submit(self.nodes[0], [test4tx], accept=False)
self.log.info("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation")
- test5tx = create_transaction(self.nodes[0], txid3, self.wit_address, amount=48)
+ test5tx = self.create_transaction(txid=txid3, input_details={"scriptPubKey": test1txs[2].vout[0].scriptPubKey.hex(),
+ "amount": 49, "witnessScript": wms["redeemScript"]},
+ addr=getnewdestination(address_type='p2sh-segwit')[2], amount=48,
+ privkey=self.privkey)
test6txs.append(CTransaction(test5tx))
test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01'
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test5tx.serialize_with_witness().hex(), 0)
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index 8541c3ed88..dd3cdc96ca 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -36,22 +36,19 @@ addnode connect to a CJDNS address
- Test passing invalid -i2psam
- Test passing -onlynet=onion without -proxy or -onion
- Test passing -onlynet=onion with -onion=0 and with -noonion
+- Test passing unknown -onlynet
"""
import socket
-import os
from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
- PORT_MIN,
- PORT_RANGE,
assert_equal,
+ p2p_port,
)
from test_framework.netutil import test_ipv6_local
-RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports
-
# Networks returned by RPC getpeerinfo.
NET_UNROUTABLE = "not_publicly_routable"
NET_IPV4 = "ipv4"
@@ -74,19 +71,19 @@ class ProxyTest(BitcoinTestFramework):
# Create two proxies on different ports
# ... one unauthenticated
self.conf1 = Socks5Configuration()
- self.conf1.addr = ('127.0.0.1', RANGE_BEGIN + (os.getpid() % 1000))
+ self.conf1.addr = ('127.0.0.1', p2p_port(self.num_nodes))
self.conf1.unauth = True
self.conf1.auth = False
# ... one supporting authenticated and unauthenticated (Tor)
self.conf2 = Socks5Configuration()
- self.conf2.addr = ('127.0.0.1', RANGE_BEGIN + 1000 + (os.getpid() % 1000))
+ self.conf2.addr = ('127.0.0.1', p2p_port(self.num_nodes + 1))
self.conf2.unauth = True
self.conf2.auth = True
if self.have_ipv6:
# ... one on IPv6 with similar configuration
self.conf3 = Socks5Configuration()
self.conf3.af = socket.AF_INET6
- self.conf3.addr = ('::1', RANGE_BEGIN + 2000 + (os.getpid() % 1000))
+ self.conf3.addr = ('::1', p2p_port(self.num_nodes + 2))
self.conf3.unauth = True
self.conf3.auth = True
else:
@@ -349,6 +346,11 @@ class ProxyTest(BitcoinTestFramework):
self.nodes[1].extra_args = ["-onlynet=onion", arg]
self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+ self.log.info("Test passing unknown network to -onlynet raises expected init error")
+ self.nodes[1].extra_args = ["-onlynet=abc"]
+ msg = "Error: Unknown network specified in -onlynet: 'abc'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
if __name__ == '__main__':
ProxyTest().main()
diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py
index bf19384279..7dbeccbc09 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -125,6 +125,7 @@ class PruneTest(BitcoinTestFramework):
self.sync_blocks(self.nodes[0:5])
def test_invalid_command_line_options(self):
+ self.stop_node(0)
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Prune cannot be configured with a negative value.',
extra_args=['-prune=-1'],
@@ -138,10 +139,6 @@ class PruneTest(BitcoinTestFramework):
extra_args=['-prune=550', '-txindex'],
)
self.nodes[0].assert_start_raises_init_error(
- expected_msg='Error: Prune mode is incompatible with -coinstatsindex.',
- extra_args=['-prune=550', '-coinstatsindex'],
- )
- self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.',
extra_args=['-prune=550', '-reindex-chainstate'],
)
@@ -294,7 +291,7 @@ class PruneTest(BitcoinTestFramework):
def prune(index):
ret = node.pruneblockchain(height=height(index))
- assert_equal(ret, node.getblockchaininfo()['pruneheight'])
+ assert_equal(ret + 1, node.getblockchaininfo()['pruneheight'])
def has_block(index):
return os.path.isfile(os.path.join(self.nodes[node_number].datadir, self.chain, "blocks", f"blk{index:05}.dat"))
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index f0ed914461..1a8362d94d 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -4,44 +4,36 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the RBF code."""
-from copy import deepcopy
from decimal import Decimal
from test_framework.messages import (
BIP125_SEQUENCE_NUMBER,
COIN,
- COutPoint,
- CTransaction,
- CTxIn,
- CTxOut,
SEQUENCE_FINAL,
)
-from test_framework.script import CScript, OP_DROP
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
-from test_framework.script_util import (
- DUMMY_P2WPKH_SCRIPT,
- DUMMY_2_P2WPKH_SCRIPT,
-)
from test_framework.wallet import MiniWallet
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
MAX_REPLACEMENT_LIMIT = 100
class ReplaceByFeeTest(BitcoinTestFramework):
def set_test_params(self):
- self.num_nodes = 1
+ self.num_nodes = 2
self.extra_args = [
[
- "-acceptnonstdtxn=1",
"-maxorphantx=1000",
"-limitancestorcount=50",
"-limitancestorsize=101",
"-limitdescendantcount=200",
"-limitdescendantsize=101",
],
+ # second node has default mempool parameters
+ [
+ ],
]
self.supports_cli = False
@@ -73,6 +65,9 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test too many replacements...")
self.test_too_many_replacements()
+ self.log.info("Running test too many replacements using default mempool params...")
+ self.test_too_many_replacements_with_default_mempool_params()
+
self.log.info("Running test opt-in...")
self.test_opt_in()
@@ -88,17 +83,19 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test replacement relay fee...")
self.test_replacement_relay_fee()
+ self.log.info("Running test full replace by fee...")
+ self.test_fullrbf()
+
self.log.info("Passed")
- def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
+ def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None):
"""Create a txout with a given amount and scriptPubKey
- confirmed - txouts created will be confirmed in the blockchain;
+ confirmed - txout created will be confirmed in the blockchain;
unconfirmed otherwise.
"""
- txid, n = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey, amount=amount)
+ txid, n = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey or self.wallet.get_scriptPubKey(), amount=amount)
- # If requested, ensure txouts are confirmed.
if confirmed:
mempool_size = len(node.getrawmempool())
while mempool_size > 0:
@@ -109,30 +106,24 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert new_size < mempool_size
mempool_size = new_size
- return COutPoint(int(txid, 16), n)
+ return self.wallet.get_utxo(txid=txid, vout=n)
def test_simple_doublespend(self):
"""Simple doublespend"""
# we use MiniWallet to create a transaction template with inputs correctly set,
# and modify the output (amount, scriptPubKey) according to our needs
- tx_template = self.wallet.create_self_transfer()['tx']
-
- tx1a = deepcopy(tx_template)
- tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
+ tx = self.wallet.create_self_transfer()["tx"]
+ tx1a_txid = self.nodes[0].sendrawtransaction(tx.serialize().hex())
# Should fail because we haven't changed the fee
- tx1b = deepcopy(tx_template)
- tx1b.vout = [CTxOut(1 * COIN, DUMMY_2_P2WPKH_SCRIPT)]
- tx1b_hex = tx1b.serialize().hex()
+ tx.vout[0].scriptPubKey[-1] ^= 1
# This will raise an exception due to insufficient fee
- assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
+ assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex(), 0)
# Extra 0.1 BTC fee
- tx1b.vout[0].nValue -= int(0.1 * COIN)
- tx1b_hex = tx1b.serialize().hex()
+ tx.vout[0].nValue -= int(0.1 * COIN)
+ tx1b_hex = tx.serialize().hex()
# Works when enabled
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
@@ -154,28 +145,28 @@ class ReplaceByFeeTest(BitcoinTestFramework):
chain_txids = []
while remaining_value > 1 * COIN:
remaining_value -= int(0.1 * COIN)
- tx = CTransaction()
- tx.vin = [CTxIn(prevout, nSequence=0)]
- tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))]
- tx_hex = tx.serialize().hex()
- txid = self.nodes[0].sendrawtransaction(tx_hex, 0)
- chain_txids.append(txid)
- prevout = COutPoint(int(txid, 16), 0)
+ prevout = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=prevout,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
+ chain_txids.append(prevout["txid"])
# Whether the double-spend is allowed is evaluated by including all
# child fees - 4 BTC - so this attempt is rejected.
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(initial_nValue - 3 * COIN, DUMMY_P2WPKH_SCRIPT)]
+ dbl_tx = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=Decimal("3"),
+ )["tx"]
dbl_tx_hex = dbl_tx.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
# Accepted with sufficient fee
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(int(0.1 * COIN), DUMMY_P2WPKH_SCRIPT)]
+ dbl_tx.vout[0].nValue = int(0.1 * COIN)
dbl_tx_hex = dbl_tx.serialize().hex()
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
@@ -199,22 +190,19 @@ class ReplaceByFeeTest(BitcoinTestFramework):
if txout_value < fee:
return
- vout = [CTxOut(txout_value, CScript([i+1]))
- for i in range(tree_width)]
- tx = CTransaction()
- tx.vin = [CTxIn(prevout, nSequence=0)]
- tx.vout = vout
- tx_hex = tx.serialize().hex()
+ tx = self.wallet.send_self_transfer_multi(
+ utxos_to_spend=[prevout],
+ from_node=self.nodes[0],
+ sequence=0,
+ num_outputs=tree_width,
+ amount_per_output=txout_value,
+ )
- assert len(tx.serialize()) < 100000
- txid = self.nodes[0].sendrawtransaction(tx_hex, 0)
- yield tx
+ yield tx["txid"]
_total_txs[0] += 1
- txid = int(txid, 16)
-
- for i, txout in enumerate(tx.vout):
- for x in branch(COutPoint(txid, i), txout_value,
+ for utxo in tx["new_utxos"]:
+ for x in branch(utxo, txout_value,
max_txs,
tree_width=tree_width, fee=fee,
_total_txs=_total_txs):
@@ -226,25 +214,26 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert_equal(len(tree_txs), n)
# Attempt double-spend, will fail because too little fee paid
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(initial_nValue - fee * n, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = dbl_tx.serialize().hex()
+ dbl_tx_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=(Decimal(fee) / COIN) * n,
+ )["hex"]
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
# 0.1 BTC fee is enough
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(initial_nValue - fee * n - int(0.1 * COIN), DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = dbl_tx.serialize().hex()
+ dbl_tx_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=(Decimal(fee) / COIN) * n + Decimal("0.1"),
+ )["hex"]
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
mempool = self.nodes[0].getrawmempool()
- for tx in tree_txs:
- tx.rehash()
- assert tx.hash not in mempool
+ for txid in tree_txs:
+ assert txid not in mempool
# Try again, but with more total transactions than the "max txs
# double-spent at once" anti-DoS limit.
@@ -254,33 +243,36 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
- dbl_tx = CTransaction()
- dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- dbl_tx.vout = [CTxOut(initial_nValue - 2 * fee * n, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = dbl_tx.serialize().hex()
+ dbl_tx_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=2 * (Decimal(fee) / COIN) * n,
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
- for tx in tree_txs:
- tx.rehash()
- self.nodes[0].getrawtransaction(tx.hash)
+ for txid in tree_txs:
+ self.nodes[0].getrawtransaction(txid)
def test_replacement_feeperkb(self):
"""Replacement requires fee-per-KB to be higher"""
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
- tx1a = CTransaction()
- tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- self.nodes[0].sendrawtransaction(tx1a_hex, 0)
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )
# Higher fee, but the fee per KB is much lower, so the replacement is
# rejected.
- tx1b = CTransaction()
- tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 999000]))]
- tx1b_hex = tx1b.serialize().hex()
+ tx1b_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx0_outpoint],
+ sequence=0,
+ num_outputs=100,
+ amount_per_output=1000,
+ )["hex"]
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
@@ -290,37 +282,36 @@ class ReplaceByFeeTest(BitcoinTestFramework):
utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN))
utxo2 = self.make_utxo(self.nodes[0], 3 * COIN)
- tx1a = CTransaction()
- tx1a.vin = [CTxIn(utxo1, nSequence=0)]
- tx1a.vout = [CTxOut(int(1.1 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
-
- tx1a_txid = int(tx1a_txid, 16)
+ tx1a_utxo = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=utxo1,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
# Direct spend an output of the transaction we're replacing.
- tx2 = CTransaction()
- tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0)]
- tx2.vin.append(CTxIn(COutPoint(tx1a_txid, 0), nSequence=0))
- tx2.vout = tx1a.vout
- tx2_hex = tx2.serialize().hex()
+ tx2_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[utxo1, utxo2, tx1a_utxo],
+ sequence=0,
+ amount_per_output=int(COIN * tx1a_utxo["value"]),
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
# Spend tx1a's output to test the indirect case.
- tx1b = CTransaction()
- tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
- tx1b.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1b_hex = tx1b.serialize().hex()
- tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
- tx1b_txid = int(tx1b_txid, 16)
+ tx1b_utxo = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx1a_utxo,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
- tx2 = CTransaction()
- tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0),
- CTxIn(COutPoint(tx1b_txid, 0))]
- tx2.vout = tx1a.vout
- tx2_hex = tx2.serialize().hex()
+ tx2_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[utxo1, utxo2, tx1b_utxo],
+ sequence=0,
+ amount_per_output=int(COIN * tx1a_utxo["value"]),
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
@@ -328,18 +319,20 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_new_unconfirmed_inputs(self):
"""Replacements that add new unconfirmed inputs are rejected"""
confirmed_utxo = self.make_utxo(self.nodes[0], int(1.1 * COIN))
- unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), False)
+ unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), confirmed=False)
- tx1 = CTransaction()
- tx1.vin = [CTxIn(confirmed_utxo)]
- tx1.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1_hex = tx1.serialize().hex()
- self.nodes[0].sendrawtransaction(tx1_hex, 0)
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=confirmed_utxo,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )
- tx2 = CTransaction()
- tx2.vin = [CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)]
- tx2.vout = tx1.vout
- tx2_hex = tx2.serialize().hex()
+ tx2_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[confirmed_utxo, unconfirmed_utxo],
+ sequence=0,
+ amount_per_output=1 * COIN,
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "replacement-adds-unconfirmed", self.nodes[0].sendrawtransaction, tx2_hex, 0)
@@ -355,67 +348,149 @@ class ReplaceByFeeTest(BitcoinTestFramework):
fee = int(0.0001 * COIN)
split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1))
- outputs = []
- for _ in range(MAX_REPLACEMENT_LIMIT + 1):
- outputs.append(CTxOut(split_value, CScript([1])))
-
- splitting_tx = CTransaction()
- splitting_tx.vin = [CTxIn(utxo, nSequence=0)]
- splitting_tx.vout = outputs
- splitting_tx_hex = splitting_tx.serialize().hex()
-
- txid = self.nodes[0].sendrawtransaction(splitting_tx_hex, 0)
- txid = int(txid, 16)
+ splitting_tx_utxos = self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ utxos_to_spend=[utxo],
+ sequence=0,
+ num_outputs=MAX_REPLACEMENT_LIMIT + 1,
+ amount_per_output=split_value,
+ )["new_utxos"]
# Now spend each of those outputs individually
- for i in range(MAX_REPLACEMENT_LIMIT + 1):
- tx_i = CTransaction()
- tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)]
- tx_i.vout = [CTxOut(split_value - fee, DUMMY_P2WPKH_SCRIPT)]
- tx_i_hex = tx_i.serialize().hex()
- self.nodes[0].sendrawtransaction(tx_i_hex, 0)
+ for utxo in splitting_tx_utxos:
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=utxo,
+ sequence=0,
+ fee=Decimal(fee) / COIN,
+ )
# Now create doublespend of the whole lot; should fail.
# Need a big enough fee to cover all spending transactions and have
# a higher fee rate
double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1)
- inputs = []
- for i in range(MAX_REPLACEMENT_LIMIT + 1):
- inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
- double_tx = CTransaction()
- double_tx.vin = inputs
- double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
+ double_tx = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=splitting_tx_utxos,
+ sequence=0,
+ amount_per_output=double_spend_value,
+ )["tx"]
double_tx_hex = double_tx.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, double_tx_hex, 0)
# If we remove an input, it should pass
- double_tx = CTransaction()
- double_tx.vin = inputs[0:-1]
- double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
+ double_tx.vin.pop()
double_tx_hex = double_tx.serialize().hex()
self.nodes[0].sendrawtransaction(double_tx_hex, 0)
+ def test_too_many_replacements_with_default_mempool_params(self):
+ """
+ Test rule 5 of BIP125 (do not allow replacements that cause more than 100
+ evictions) without having to rely on non-default mempool parameters.
+
+ In order to do this, create a number of "root" UTXOs, and then hang
+ enough transactions off of each root UTXO to exceed the MAX_REPLACEMENT_LIMIT.
+ Then create a conflicting RBF replacement transaction.
+ """
+ normal_node = self.nodes[1]
+ wallet = MiniWallet(normal_node)
+ wallet.rescan_utxos()
+ # Clear mempools to avoid cross-node sync failure.
+ for node in self.nodes:
+ self.generate(node, 1)
+
+ # This has to be chosen so that the total number of transactions can exceed
+ # MAX_REPLACEMENT_LIMIT without having any one tx graph run into the descendant
+ # limit; 10 works.
+ num_tx_graphs = 10
+
+ # (Number of transactions per graph, BIP125 rule 5 failure expected)
+ cases = [
+ # Test the base case of evicting fewer than MAX_REPLACEMENT_LIMIT
+ # transactions.
+ ((MAX_REPLACEMENT_LIMIT // num_tx_graphs) - 1, False),
+
+ # Test hitting the rule 5 eviction limit.
+ (MAX_REPLACEMENT_LIMIT // num_tx_graphs, True),
+ ]
+
+ for (txs_per_graph, failure_expected) in cases:
+ self.log.debug(f"txs_per_graph: {txs_per_graph}, failure: {failure_expected}")
+ # "Root" utxos of each txn graph that we will attempt to double-spend with
+ # an RBF replacement.
+ root_utxos = []
+
+ # For each root UTXO, create a package that contains the spend of that
+ # UTXO and `txs_per_graph` children tx.
+ for graph_num in range(num_tx_graphs):
+ root_utxos.append(wallet.get_utxo())
+
+ optin_parent_tx = wallet.send_self_transfer_multi(
+ from_node=normal_node,
+ sequence=BIP125_SEQUENCE_NUMBER,
+ utxos_to_spend=[root_utxos[graph_num]],
+ num_outputs=txs_per_graph,
+ )
+ assert_equal(True, normal_node.getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
+ new_utxos = optin_parent_tx['new_utxos']
+
+ for utxo in new_utxos:
+ # Create spends for each output from the "root" of this graph.
+ child_tx = wallet.send_self_transfer(
+ from_node=normal_node,
+ utxo_to_spend=utxo,
+ )
+
+ assert normal_node.getmempoolentry(child_tx['txid'])
+
+ num_txs_invalidated = len(root_utxos) + (num_tx_graphs * txs_per_graph)
+
+ if failure_expected:
+ assert num_txs_invalidated > MAX_REPLACEMENT_LIMIT
+ else:
+ assert num_txs_invalidated <= MAX_REPLACEMENT_LIMIT
+
+ # Now attempt to submit a tx that double-spends all the root tx inputs, which
+ # would invalidate `num_txs_invalidated` transactions.
+ tx_hex = wallet.create_self_transfer_multi(
+ utxos_to_spend=root_utxos,
+ fee_per_output=10_000_000, # absurdly high feerate
+ )["hex"]
+
+ if failure_expected:
+ assert_raises_rpc_error(
+ -26, "too many potential replacements", normal_node.sendrawtransaction, tx_hex, 0)
+ else:
+ txid = normal_node.sendrawtransaction(tx_hex, 0)
+ assert normal_node.getmempoolentry(txid)
+
+ # Clear the mempool once finished, and rescan the other nodes' wallet
+ # to account for the spends we've made on `normal_node`.
+ self.generate(normal_node, 1)
+ self.wallet.rescan_utxos()
+
def test_opt_in(self):
"""Replacing should only work if orig tx opted in"""
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a non-opting in transaction
- tx1a = CTransaction()
- tx1a.vin = [CTxIn(tx0_outpoint, nSequence=SEQUENCE_FINAL)]
- tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
+ tx1a_utxo = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx0_outpoint,
+ sequence=SEQUENCE_FINAL,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
# This transaction isn't shown as replaceable
- assert_equal(self.nodes[0].getmempoolentry(tx1a_txid)['bip125-replaceable'], False)
+ assert_equal(self.nodes[0].getmempoolentry(tx1a_utxo["txid"])['bip125-replaceable'], False)
# Shouldn't be able to double-spend
- tx1b = CTransaction()
- tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx1b_hex = tx1b.serialize().hex()
+ tx1b_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=Decimal("0.2"),
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
@@ -423,17 +498,19 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a different non-opting in transaction
- tx2a = CTransaction()
- tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)]
- tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx2a_hex = tx2a.serialize().hex()
- tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, 0)
+ tx2a_utxo = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx1_outpoint,
+ sequence=0xfffffffe,
+ fee=Decimal("0.1"),
+ )["new_utxo"]
# Still shouldn't be able to double-spend
- tx2b = CTransaction()
- tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
- tx2b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx2b_hex = tx2b.serialize().hex()
+ tx2b_hex = self.wallet.create_self_transfer(
+ utxo_to_spend=tx1_outpoint,
+ sequence=0,
+ fee=Decimal("0.2"),
+ )["hex"]
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
@@ -442,34 +519,31 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# opt-in on one of the inputs
# Transaction should be replaceable on either input
- tx1a_txid = int(tx1a_txid, 16)
- tx2a_txid = int(tx2a_txid, 16)
-
- tx3a = CTransaction()
- tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=SEQUENCE_FINAL),
- CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)]
- tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))]
- tx3a_hex = tx3a.serialize().hex()
-
- tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0)
+ tx3a_txid = self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ utxos_to_spend=[tx1a_utxo, tx2a_utxo],
+ sequence=[SEQUENCE_FINAL, 0xfffffffd],
+ fee_per_output=int(0.1 * COIN),
+ )["txid"]
# This transaction is shown as replaceable
assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], True)
- tx3b = CTransaction()
- tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
- tx3b.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx3b_hex = tx3b.serialize().hex()
-
- tx3c = CTransaction()
- tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)]
- tx3c.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx3c_hex = tx3c.serialize().hex()
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx1a_utxo,
+ sequence=0,
+ fee=Decimal("0.4"),
+ )
- self.nodes[0].sendrawtransaction(tx3b_hex, 0)
# If tx3b was accepted, tx3c won't look like a replacement,
# but make sure it is accepted anyway
- self.nodes[0].sendrawtransaction(tx3c_hex, 0)
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx2a_utxo,
+ sequence=0,
+ fee=Decimal("0.4"),
+ )
def test_prioritised_transactions(self):
# Ensure that fee deltas used via prioritisetransaction are
@@ -478,17 +552,20 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# 1. Check that feeperkb uses modified fees
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
- tx1a = CTransaction()
- tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = tx1a.serialize().hex()
- tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
+ tx1a_txid = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx0_outpoint,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )["txid"]
# Higher fee, but the actual fee per KB is much lower.
- tx1b = CTransaction()
- tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))]
- tx1b_hex = tx1b.serialize().hex()
+ tx1b_hex = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[tx0_outpoint],
+ sequence=0,
+ num_outputs=100,
+ amount_per_output=int(0.00001 * COIN),
+ )["hex"]
# Verify tx1b cannot replace tx1a.
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
@@ -504,27 +581,29 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# 2. Check that absolute fee checks use modified fee.
tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
- tx2a = CTransaction()
- tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
- tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx2a_hex = tx2a.serialize().hex()
- self.nodes[0].sendrawtransaction(tx2a_hex, 0)
+ # tx2a
+ self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=tx1_outpoint,
+ sequence=0,
+ fee=Decimal("0.1"),
+ )
# Lower fee, but we'll prioritise it
- tx2b = CTransaction()
- tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
- tx2b.vout = [CTxOut(int(1.01 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx2b.rehash()
- tx2b_hex = tx2b.serialize().hex()
+ tx2b = self.wallet.create_self_transfer(
+ utxo_to_spend=tx1_outpoint,
+ sequence=0,
+ fee=Decimal("0.09"),
+ )
# Verify tx2b cannot replace tx2a.
- assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
+ assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b["hex"], 0)
# Now prioritise tx2b to have a higher modified fee
- self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1 * COIN))
+ self.nodes[0].prioritisetransaction(txid=tx2b["txid"], fee_delta=int(0.1 * COIN))
# tx2b should now be accepted
- tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, 0)
+ tx2b_txid = self.nodes[0].sendrawtransaction(tx2b["hex"], 0)
assert tx2b_txid in self.nodes[0].getrawmempool()
@@ -590,7 +669,6 @@ class ReplaceByFeeTest(BitcoinTestFramework):
utxo_to_spend=parent_utxo,
sequence=SEQUENCE_FINAL,
fee_rate=Decimal('0.02'),
- mempool_valid=False,
)
# Broadcast replacement child tx
@@ -619,8 +697,37 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# Higher fee, higher feerate, different txid, but the replacement does not provide a relay
# fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB.
+ assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.00001"))
tx.vout[0].nValue -= 1
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
+ def test_fullrbf(self):
+
+ confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN))
+ self.restart_node(0, extra_args=["-mempoolfullrbf=1"])
+ assert self.nodes[0].getmempoolinfo()["fullrbf"]
+
+ # Create an explicitly opt-out transaction
+ optout_tx = self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=confirmed_utxo,
+ sequence=BIP125_SEQUENCE_NUMBER + 1,
+ fee_rate=Decimal('0.01'),
+ )
+ assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable'])
+
+ conflicting_tx = self.wallet.create_self_transfer(
+ utxo_to_spend=confirmed_utxo,
+ sequence=SEQUENCE_FINAL,
+ fee_rate=Decimal('0.02'),
+ )
+
+ # Send the replacement transaction, conflicting with the optout_tx.
+ self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0)
+
+ # Optout_tx is not anymore in the mempool.
+ assert optout_tx['txid'] not in self.nodes[0].getrawmempool()
+ assert conflicting_tx['txid'] in self.nodes[0].getrawmempool()
+
if __name__ == '__main__':
ReplaceByFeeTest().main()
diff --git a/test/functional/feature_signet.py b/test/functional/feature_signet.py
index 6578caee3f..4c1e48af6d 100755
--- a/test/functional/feature_signet.py
+++ b/test/functional/feature_signet.py
@@ -39,6 +39,14 @@ class SignetBasicTest(BitcoinTestFramework):
shared_args3, shared_args3,
]
+ def setup_network(self):
+ self.setup_nodes()
+
+ # Setup the three signets, which are incompatible with each other
+ self.connect_nodes(0, 1)
+ self.connect_nodes(2, 3)
+ self.connect_nodes(4, 5)
+
def run_test(self):
self.log.info("basic tests using OP_TRUE challenge")
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index c3925dbb00..0e44038196 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -10,7 +10,6 @@ from test_framework.blocktools import (
create_block,
add_witness_commitment,
MAX_BLOCK_SIGOPS_WEIGHT,
- NORMAL_GBT_REQUEST_PARAMS,
WITNESS_SCALE_FACTOR,
)
from test_framework.messages import (
@@ -96,10 +95,9 @@ from test_framework.util import assert_raises_rpc_error, assert_equal
from test_framework.key import generate_privkey, compute_xonly_pubkey, sign_schnorr, tweak_add_privkey, ECKey
from test_framework.address import (
hash160,
- program_to_witness
+ program_to_witness,
)
from collections import OrderedDict, namedtuple
-from enum import Enum
from io import BytesIO
import json
import hashlib
@@ -458,7 +456,7 @@ def spend(tx, idx, utxos, **kwargs):
# Each spender is a tuple of:
# - A scriptPubKey which is to be spent from (CScript)
# - A comment describing the test (string)
-# - Whether the spending (on itself) is expected to be standard (Enum.Standard)
+# - Whether the spending (on itself) is expected to be standard (bool)
# - A tx-signing lambda returning (scriptsig, witness_stack), taking as inputs:
# - A transaction to sign (CTransaction)
# - An input position (int)
@@ -470,14 +468,9 @@ def spend(tx, idx, utxos, **kwargs):
# - Whether this test demands being placed in a txin with no corresponding txout (for testing SIGHASH_SINGLE behavior)
Spender = namedtuple("Spender", "script,comment,is_standard,sat_function,err_msg,sigops_weight,no_fail,need_vin_vout_mismatch")
-# The full node versions that treat the tx standard.
-# ALL means any version
-# V23 means the major version 23.0 and any later version
-# NONE means no version
-Standard = Enum('Standard', 'ALL V23 NONE')
-def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=Standard.ALL, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs):
+def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=True, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs):
"""Helper for constructing Spender objects using the context signing framework.
* tap: a TaprootInfo object (see taproot_construct), for Taproot spends (cannot be combined with pkh, witv0, or script)
@@ -487,18 +480,13 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=
* p2sh: whether the output is P2SH wrapper (this is supported even for Taproot, where it makes the output unencumbered)
* spk_mutate_pre_psh: a callable to be applied to the script (before potentially P2SH-wrapping it)
* failure: a dict of entries to override in the context when intentionally failing to spend (if None, no_fail will be set)
- * standard: whether the (valid version of) spending is expected to be standard (True is mapped to Standard.ALL, False is mapped to Standard.NONE)
+ * standard: whether the (valid version of) spending is expected to be standard
* err_msg: a string with an expected error message for failure (or None, if not cared about)
* sigops_weight: the pre-taproot sigops weight consumed by a successful spend
* need_vin_vout_mismatch: whether this test requires being tested in a transaction input that has no corresponding
transaction output.
"""
- if standard == True:
- standard = Standard.ALL
- elif standard == False:
- standard = Standard.NONE
-
conf = dict()
# Compute scriptPubKey and set useful defaults based on the inputs.
@@ -1168,24 +1156,20 @@ def spenders_taproot_active():
return spenders
-def spenders_taproot_inactive():
- """Spenders for testing that pre-activation Taproot rules don't apply."""
+
+def spenders_taproot_nonstandard():
+ """Spenders for testing that post-activation Taproot rules may be nonstandard."""
spenders = []
sec = generate_privkey()
pub, _ = compute_xonly_pubkey(sec)
scripts = [
- ("pk", CScript([pub, OP_CHECKSIG])),
("future_leaf", CScript([pub, OP_CHECKSIG]), 0xc2),
("op_success", CScript([pub, OP_CHECKSIG, OP_0, OP_IF, CScriptOp(0x50), OP_ENDIF])),
]
tap = taproot_construct(pub, scripts)
- # Test that valid spending is standard.
- add_spender(spenders, "inactive/keypath_valid", key=sec, tap=tap, standard=Standard.V23)
- add_spender(spenders, "inactive/scriptpath_valid", key=sec, tap=tap, leaf="pk", standard=Standard.V23, inputs=[getter("sign")])
-
# Test that features like annex, leaf versions, or OP_SUCCESS are valid but non-standard
add_spender(spenders, "inactive/scriptpath_valid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")])
add_spender(spenders, "inactive/scriptpath_invalid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash))
@@ -1214,7 +1198,7 @@ def dump_json_test(tx, input_utxos, idx, success, failure):
# The "final" field indicates that a spend should be always valid, even with more validation flags enabled
# than the listed ones. Use standardness as a proxy for this (which gives a conservative underestimate).
- if spender.is_standard == Standard.ALL:
+ if spender.is_standard:
fields.append(("final", True))
def dump_witness(wit):
@@ -1241,31 +1225,14 @@ class TaprootTest(BitcoinTestFramework):
def add_options(self, parser):
parser.add_argument("--dumptests", dest="dump_tests", default=False, action="store_true",
help="Dump generated test cases to directory set by TEST_DUMP_DIR environment variable")
- parser.add_argument("--previous_release", dest="previous_release", default=False, action="store_true",
- help="Use a previous release as taproot-inactive node")
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
- if self.options.previous_release:
- self.skip_if_no_previous_releases()
def set_test_params(self):
- self.num_nodes = 2
+ self.num_nodes = 1
self.setup_clean_chain = True
- # Node 0 has Taproot inactive, Node 1 active.
- self.extra_args = [["-par=1"], ["-par=1"]]
- if self.options.previous_release:
- self.wallet_names = [None, self.default_wallet_name]
- else:
- self.extra_args[0].append("-vbparams=taproot:1:1")
-
- def setup_nodes(self):
- self.add_nodes(self.num_nodes, self.extra_args, versions=[
- 200100 if self.options.previous_release else None,
- None,
- ])
- self.start_nodes()
- self.import_deterministic_coinbase_privkeys()
+ self.extra_args = [["-par=1"]]
def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False):
@@ -1479,11 +1446,10 @@ class TaprootTest(BitcoinTestFramework):
for i in range(len(input_utxos)):
tx.vin[i].scriptSig = input_data[i][i != fail_input][0]
tx.wit.vtxinwit[i].scriptWitness.stack = input_data[i][i != fail_input][1]
- taproot_spend_policy = Standard.V23 if node.version is None else Standard.ALL
# Submit to mempool to check standardness
is_standard_tx = (
fail_input is None # Must be valid to be standard
- and (all(utxo.spender.is_standard == Standard.ALL or utxo.spender.is_standard == taproot_spend_policy for utxo in input_utxos)) # All inputs must be standard
+ and (all(utxo.spender.is_standard for utxo in input_utxos)) # All inputs must be standard
and tx.nVersion >= 1 # The tx version must be standard
and tx.nVersion <= 2)
tx.rehash()
@@ -1510,7 +1476,7 @@ class TaprootTest(BitcoinTestFramework):
self.log.info("Unit test scenario...")
# Deterministically mine coins to OP_TRUE in block 1
- assert self.nodes[1].getblockcount() == 0
+ assert_equal(self.nodes[0].getblockcount(), 0)
coinbase = CTransaction()
coinbase.nVersion = 1
coinbase.vin = [CTxIn(COutPoint(0, 0xffffffff), CScript([OP_1, OP_1]), SEQUENCE_FINAL)]
@@ -1519,12 +1485,12 @@ class TaprootTest(BitcoinTestFramework):
coinbase.rehash()
assert coinbase.hash == "f60c73405d499a956d3162e3483c395526ef78286458a4cb17b125aa92e49b20"
# Mine it
- block = create_block(hashprev=int(self.nodes[1].getbestblockhash(), 16), coinbase=coinbase)
+ block = create_block(hashprev=int(self.nodes[0].getbestblockhash(), 16), coinbase=coinbase)
block.rehash()
block.solve()
- self.nodes[1].submitblock(block.serialize().hex())
- assert self.nodes[1].getblockcount() == 1
- self.generate(self.nodes[1], COINBASE_MATURITY)
+ self.nodes[0].submitblock(block.serialize().hex())
+ assert_equal(self.nodes[0].getblockcount(), 1)
+ self.generate(self.nodes[0], COINBASE_MATURITY)
SEED = 317
VALID_LEAF_VERS = list(range(0xc0, 0x100, 2)) + [0x66, 0x7e, 0x80, 0x84, 0x96, 0x98, 0xba, 0xbc, 0xbe]
@@ -1613,8 +1579,8 @@ class TaprootTest(BitcoinTestFramework):
spend_info[spk]['prevout'] = COutPoint(tx.sha256, i & 1)
spend_info[spk]['utxo'] = CTxOut(val, spk)
# Mine those transactions
- self.init_blockinfo(self.nodes[1])
- self.block_submit(self.nodes[1], txn, "Crediting txn", None, sigops_weight=10, accept=True)
+ self.init_blockinfo(self.nodes[0])
+ self.block_submit(self.nodes[0], txn, "Crediting txn", None, sigops_weight=10, accept=True)
# scriptPubKey computation
tests = {"version": 1}
@@ -1726,53 +1692,21 @@ class TaprootTest(BitcoinTestFramework):
keypath_tests.append(tx_test)
assert_equal(hashlib.sha256(tx.serialize()).hexdigest(), "24bab662cb55a7f3bae29b559f651674c62bcc1cd442d44715c0133939107b38")
# Mine the spending transaction
- self.block_submit(self.nodes[1], [tx], "Spending txn", None, sigops_weight=10000, accept=True, witness=True)
+ self.block_submit(self.nodes[0], [tx], "Spending txn", None, sigops_weight=10000, accept=True, witness=True)
if GEN_TEST_VECTORS:
print(json.dumps(tests, indent=4, sort_keys=False))
-
def run_test(self):
self.gen_test_vectors()
- # Post-taproot activation tests go first (pre-taproot tests' blocks are invalid post-taproot).
self.log.info("Post-activation tests...")
- self.test_spenders(self.nodes[1], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3])
-
- # Re-connect nodes in case they have been disconnected
- self.disconnect_nodes(0, 1)
- self.connect_nodes(0, 1)
-
- # Transfer value of the largest 500 coins to pre-taproot node.
- addr = self.nodes[0].getnewaddress()
-
- unsp = self.nodes[1].listunspent()
- unsp = sorted(unsp, key=lambda i: i['amount'], reverse=True)
- unsp = unsp[:500]
-
- rawtx = self.nodes[1].createrawtransaction(
- inputs=[{
- 'txid': i['txid'],
- 'vout': i['vout']
- } for i in unsp],
- outputs={addr: sum(i['amount'] for i in unsp)}
- )
- rawtx = self.nodes[1].signrawtransactionwithwallet(rawtx)['hex']
-
- # Mine a block with the transaction
- block = create_block(tmpl=self.nodes[1].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS), txlist=[rawtx])
- add_witness_commitment(block)
- block.solve()
- assert_equal(None, self.nodes[1].submitblock(block.serialize().hex()))
- self.sync_blocks()
-
- # Pre-taproot activation tests.
- self.log.info("Pre-activation tests...")
+ self.test_spenders(self.nodes[0], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3])
# Run each test twice; once in isolation, and once combined with others. Testing in isolation
# means that the standardness is verified in every test (as combined transactions are only standard
# when all their inputs are standard).
- self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[1])
- self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[2, 3])
+ self.test_spenders(self.nodes[0], spenders_taproot_nonstandard(), input_counts=[1])
+ self.test_spenders(self.nodes[0], spenders_taproot_nonstandard(), input_counts=[2, 3])
if __name__ == '__main__':
diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py
index e83dd7f446..1572463308 100755
--- a/test/functional/feature_versionbits_warning.py
+++ b/test/functional/feature_versionbits_warning.py
@@ -58,7 +58,8 @@ class VersionBitsWarningTest(BitcoinTestFramework):
def versionbits_in_alert_file(self):
"""Test that the versionbits warning has been written to the alert file."""
- alert_text = open(self.alert_filename, 'r', encoding='utf8').read()
+ with open(self.alert_filename, 'r', encoding='utf8') as f:
+ alert_text = f.read()
return VB_PATTERN.search(alert_text) is not None
def run_test(self):
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index 95dc40cb52..f36bbda3af 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -219,7 +219,7 @@ class RESTTest (BitcoinTestFramework):
self.generate(self.nodes[0], 1) # generate block to not affect upcoming tests
- self.log.info("Test the /block, /blockhashbyheight and /headers URIs")
+ self.log.info("Test the /block, /blockhashbyheight, /headers, and /blockfilterheaders URIs")
bb_hash = self.nodes[0].getbestblockhash()
# Check result if block does not exists
@@ -300,6 +300,12 @@ class RESTTest (BitcoinTestFramework):
assert_equal(first_filter_header, rpc_blockfilter['header'])
assert_equal(json_obj['filter'], rpc_blockfilter['filter'])
+ # Test blockfilterheaders with an invalid hash and filtertype
+ resp = self.test_rest_request(f"/blockfilterheaders/{INVALID_PARAM}/{bb_hash}", ret_type=RetType.OBJ, status=400)
+ assert_equal(resp.read().decode('utf-8').rstrip(), f"Unknown filtertype {INVALID_PARAM}")
+ resp = self.test_rest_request(f"/blockfilterheaders/basic/{INVALID_PARAM}", ret_type=RetType.OBJ, status=400)
+ assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid hash: {INVALID_PARAM}")
+
# Test number parsing
for num in ['5a', '-5', '0', '2001', '99999999999999999999999999999999999']:
assert_equal(
@@ -324,6 +330,9 @@ class RESTTest (BitcoinTestFramework):
# the size of the memory pool should be greater than 3x ~100 bytes
assert_greater_than(json_obj['bytes'], 300)
+ mempool_info = self.nodes[0].getmempoolinfo()
+ assert_equal(json_obj, mempool_info)
+
# Check that there are our submitted transactions in the TX memory pool
json_obj = self.test_rest_request("/mempool/contents")
raw_mempool_verbose = self.nodes[0].getrawmempool(verbose=True)
diff --git a/test/functional/interface_usdt_coinselection.py b/test/functional/interface_usdt_coinselection.py
new file mode 100755
index 0000000000..ef32feda99
--- /dev/null
+++ b/test/functional/interface_usdt_coinselection.py
@@ -0,0 +1,208 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+""" Tests the coin_selection:* tracepoint API interface.
+ See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-coin_selection
+"""
+
+# Test will be skipped if we don't have bcc installed
+try:
+ from bcc import BPF, USDT # type: ignore[import]
+except ImportError:
+ pass
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ assert_raises_rpc_error,
+)
+
+coinselection_tracepoints_program = """
+#include <uapi/linux/ptrace.h>
+
+#define WALLET_NAME_LENGTH 16
+#define ALGO_NAME_LENGTH 16
+
+struct event_data
+{
+ u8 type;
+ char wallet_name[WALLET_NAME_LENGTH];
+
+ // selected coins event
+ char algo[ALGO_NAME_LENGTH];
+ s64 target;
+ s64 waste;
+ s64 selected_value;
+
+ // create tx event
+ bool success;
+ s64 fee;
+ s32 change_pos;
+
+ // aps create tx event
+ bool use_aps;
+};
+
+BPF_QUEUE(coin_selection_events, struct event_data, 1024);
+
+int trace_selected_coins(struct pt_regs *ctx) {
+ struct event_data data;
+ __builtin_memset(&data, 0, sizeof(data));
+ data.type = 1;
+ bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH);
+ bpf_usdt_readarg_p(2, ctx, &data.algo, ALGO_NAME_LENGTH);
+ bpf_usdt_readarg(3, ctx, &data.target);
+ bpf_usdt_readarg(4, ctx, &data.waste);
+ bpf_usdt_readarg(5, ctx, &data.selected_value);
+ coin_selection_events.push(&data, 0);
+ return 0;
+}
+
+int trace_normal_create_tx(struct pt_regs *ctx) {
+ struct event_data data;
+ __builtin_memset(&data, 0, sizeof(data));
+ data.type = 2;
+ bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH);
+ bpf_usdt_readarg(2, ctx, &data.success);
+ bpf_usdt_readarg(3, ctx, &data.fee);
+ bpf_usdt_readarg(4, ctx, &data.change_pos);
+ coin_selection_events.push(&data, 0);
+ return 0;
+}
+
+int trace_attempt_aps(struct pt_regs *ctx) {
+ struct event_data data;
+ __builtin_memset(&data, 0, sizeof(data));
+ data.type = 3;
+ bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH);
+ coin_selection_events.push(&data, 0);
+ return 0;
+}
+
+int trace_aps_create_tx(struct pt_regs *ctx) {
+ struct event_data data;
+ __builtin_memset(&data, 0, sizeof(data));
+ data.type = 4;
+ bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH);
+ bpf_usdt_readarg(2, ctx, &data.use_aps);
+ bpf_usdt_readarg(3, ctx, &data.success);
+ bpf_usdt_readarg(4, ctx, &data.fee);
+ bpf_usdt_readarg(5, ctx, &data.change_pos);
+ coin_selection_events.push(&data, 0);
+ return 0;
+}
+"""
+
+
+class CoinSelectionTracepointTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_platform_not_linux()
+ self.skip_if_no_bitcoind_tracepoints()
+ self.skip_if_no_python_bcc()
+ self.skip_if_no_bpf_permissions()
+ self.skip_if_no_wallet()
+
+ def get_tracepoints(self, expected_types):
+ events = []
+ try:
+ for i in range(0, len(expected_types) + 1):
+ event = self.bpf["coin_selection_events"].pop()
+ assert_equal(event.wallet_name.decode(), self.default_wallet_name)
+ assert_equal(event.type, expected_types[i])
+ events.append(event)
+ else:
+ # If the loop exits successfully instead of throwing a KeyError, then we have had
+ # more events than expected. There should be no more than len(expected_types) events.
+ assert False
+ except KeyError:
+ assert_equal(len(events), len(expected_types))
+ return events
+
+
+ def determine_selection_from_usdt(self, events):
+ success = None
+ use_aps = None
+ algo = None
+ waste = None
+ change_pos = None
+
+ is_aps = False
+ sc_events = []
+ for event in events:
+ if event.type == 1:
+ if not is_aps:
+ algo = event.algo.decode()
+ waste = event.waste
+ sc_events.append(event)
+ elif event.type == 2:
+ success = event.success
+ if not is_aps:
+ change_pos = event.change_pos
+ elif event.type == 3:
+ is_aps = True
+ elif event.type == 4:
+ assert is_aps
+ if event.use_aps:
+ use_aps = True
+ assert_equal(len(sc_events), 2)
+ algo = sc_events[1].algo.decode()
+ waste = sc_events[1].waste
+ change_pos = event.change_pos
+ return success, use_aps, algo, waste, change_pos
+
+ def run_test(self):
+ self.log.info("hook into the coin_selection tracepoints")
+ ctx = USDT(pid=self.nodes[0].process.pid)
+ ctx.enable_probe(probe="coin_selection:selected_coins", fn_name="trace_selected_coins")
+ ctx.enable_probe(probe="coin_selection:normal_create_tx_internal", fn_name="trace_normal_create_tx")
+ ctx.enable_probe(probe="coin_selection:attempting_aps_create_tx", fn_name="trace_attempt_aps")
+ ctx.enable_probe(probe="coin_selection:aps_create_tx_internal", fn_name="trace_aps_create_tx")
+ self.bpf = BPF(text=coinselection_tracepoints_program, usdt_contexts=[ctx], debug=0)
+
+ self.log.info("Prepare wallets")
+ self.generate(self.nodes[0], 101)
+ wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+
+ self.log.info("Sending a transaction should result in all tracepoints")
+ # We should have 5 tracepoints in the order:
+ # 1. selected_coins (type 1)
+ # 2. normal_create_tx_internal (type 2)
+ # 3. attempting_aps_create_tx (type 3)
+ # 4. selected_coins (type 1)
+ # 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)
+ assert_equal(success, True)
+ assert_greater_than(change_pos, -1)
+
+ self.log.info("Failing to fund results in 1 tracepoint")
+ # We should have 1 tracepoints in the order
+ # 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)
+ assert_equal(success, False)
+
+ self.log.info("Explicitly enabling APS results in 2 tracepoints")
+ # We should have 2 tracepoints in the order
+ # 1. selected_coins (type 1)
+ # 2. normal_create_tx_internal (type 2)
+ 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)
+ assert_equal(success, True)
+ assert_equal(use_aps, None)
+
+ self.bpf.cleanup()
+
+
+if __name__ == '__main__':
+ CoinSelectionTracepointTest().main()
diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py
index 0c7f351e66..f48ff9699d 100755
--- a/test/functional/interface_usdt_utxocache.py
+++ b/test/functional/interface_usdt_utxocache.py
@@ -169,8 +169,7 @@ class UTXOCacheTracepointTest(BitcoinTestFramework):
# Create a transaction and invalidate it by changing the txid of the previous
# output to the coinbase txid of the block at height 1.
- invalid_tx = self.wallet.create_self_transfer(
- from_node=self.nodes[0])["tx"]
+ invalid_tx = self.wallet.create_self_transfer()["tx"]
invalid_tx.vin[0].prevout.hash = int(block_1_coinbase_txid, 16)
self.log.info("hooking into the utxocache:uncache tracepoint")
diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py
index d3961e753d..85464b8d0d 100755
--- a/test/functional/mempool_accept.py
+++ b/test/functional/mempool_accept.py
@@ -76,7 +76,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
tx.vout[0].nValue = int(0.3 * COIN)
tx.vout[1].nValue = int(49 * COIN)
raw_tx_in_block = tx.serialize().hex()
- txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block, maxfeerate=0)
+ txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block)
self.generate(node, 1)
self.mempool_size = 0
self.check_mempool_result(
@@ -166,7 +166,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
tx.vin[1].prevout = COutPoint(hash=int(txid_1, 16), n=0)
tx.vout[0].nValue = int(0.1 * COIN)
raw_tx_spend_both = tx.serialize().hex()
- txid_spend_both = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_spend_both, maxfeerate=0)
+ txid_spend_both = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_spend_both)
self.generate(node, 1)
self.mempool_size = 0
# Now see if we can add the coins back to the utxo set by sending the exact txs again
diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py
index f301b29c25..47ae0c762b 100755
--- a/test/functional/mempool_expiry.py
+++ b/test/functional/mempool_expiry.py
@@ -5,7 +5,7 @@
"""Tests that a mempool transaction expires after a given timeout and that its
children are removed as well.
-Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY and a user
+Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY_HOURS and a user
definable expiry timeout via the '-mempoolexpiry=<n>' command line argument
(<n> is the timeout in hours) are tested.
"""
@@ -20,7 +20,7 @@ from test_framework.util import (
)
from test_framework.wallet import MiniWallet
-DEFAULT_MEMPOOL_EXPIRY = 336 # hours
+DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours
CUSTOM_MEMPOOL_EXPIRY = 10 # hours
@@ -98,8 +98,8 @@ class MempoolExpiryTest(BitcoinTestFramework):
def run_test(self):
self.log.info('Test default mempool expiry timeout of %d hours.' %
- DEFAULT_MEMPOOL_EXPIRY)
- self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY)
+ DEFAULT_MEMPOOL_EXPIRY_HOURS)
+ self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY_HOURS)
self.log.info('Test custom mempool expiry timeout of %d hours.' %
CUSTOM_MEMPOOL_EXPIRY)
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
index 3619e05761..7080662b49 100755
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -7,12 +7,12 @@
from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
-from test_framework.messages import COIN
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
+ create_lots_of_big_transactions,
gen_return_txouts,
)
from test_framework.wallet import MiniWallet
@@ -23,22 +23,12 @@ class MempoolLimitTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
- "-acceptnonstdtxn=1",
+ "-datacarriersize=100000",
"-maxmempool=5",
"-spendzeroconfchange=0",
]]
self.supports_cli = False
- def send_large_txs(self, node, miniwallet, txouts, fee, tx_batch_size):
- for _ in range(tx_batch_size):
- tx = miniwallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx']
- for txout in txouts:
- tx.vout.append(txout)
- tx.vout[0].nValue -= int(fee * COIN)
- res = node.testmempoolaccept([tx.serialize().hex()])[0]
- assert_equal(res['fees']['base'], fee)
- miniwallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex())
-
def run_test(self):
txouts = gen_return_txouts()
node = self.nodes[0]
@@ -71,7 +61,7 @@ class MempoolLimitTest(BitcoinTestFramework):
self.log.info("Fill up the mempool with txs with higher fee rate")
for batch_of_txid in range(num_of_batches):
fee = (batch_of_txid + 1) * base_fee
- self.send_large_txs(node, miniwallet, txouts, fee, tx_batch_size)
+ create_lots_of_big_transactions(miniwallet, node, fee, tx_batch_size, txouts)
self.log.info('The tx should be evicted by now')
# The number of transactions created should be greater than the ones present in the mempool
@@ -85,7 +75,11 @@ class MempoolLimitTest(BitcoinTestFramework):
# Deliberately try to create a tx with a fee less than the minimum mempool fee to assert that it does not get added to the mempool
self.log.info('Create a mempool tx that will not pass mempoolminfee')
- assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee, mempool_valid=False)
+ assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee)
+
+ self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error')
+ self.stop_node(0)
+ self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB")
if __name__ == '__main__':
diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py
index 068fdc0b65..a2a2caf324 100755
--- a/test/functional/mempool_packages.py
+++ b/test/functional/mempool_packages.py
@@ -100,6 +100,12 @@ class MempoolPackagesTest(BitcoinTestFramework):
entry = self.nodes[0].getmempoolentry(x)
assert_equal(entry, mempool[x])
+ # Check that gettxspendingprevout is consistent with getrawmempool
+ witnesstx = self.nodes[0].gettransaction(txid=x, verbose=True)['decoded']
+ for tx_in in witnesstx["vin"]:
+ spending_result = self.nodes[0].gettxspendingprevout([ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"]} ])
+ assert_equal(spending_result, [ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"], 'spendingtxid' : x} ])
+
# Check that the descendant calculations are correct
assert_equal(entry['descendantcount'], descendant_count)
descendant_fees += entry['fees']['base']
diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py
index 8c9379b90b..58f4c91343 100755
--- a/test/functional/mempool_persist.py
+++ b/test/functional/mempool_persist.py
@@ -105,6 +105,11 @@ class MempoolPersistTest(BitcoinTestFramework):
assert_equal(len(self.nodes[0].p2ps), 0)
self.mini_wallet.send_self_transfer(from_node=self.nodes[0])
+ # Test persistence of prioritisation for transactions not in the mempool.
+ # Create a tx and prioritise but don't submit until after the restart.
+ tx_prioritised_not_submitted = self.mini_wallet.create_self_transfer()
+ self.nodes[0].prioritisetransaction(txid=tx_prioritised_not_submitted['txid'], fee_delta=9999)
+
self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.")
self.stop_nodes()
# Give this node a head-start, so we can be "extra-sure" that it didn't load anything later
@@ -125,6 +130,9 @@ class MempoolPersistTest(BitcoinTestFramework):
self.log.debug('Verify all fields are loaded correctly')
assert_equal(last_entry, self.nodes[0].getmempoolentry(txid=last_txid))
+ self.nodes[0].sendrawtransaction(tx_prioritised_not_submitted['hex'])
+ entry_prioritised_before_restart = self.nodes[0].getmempoolentry(txid=tx_prioritised_not_submitted['txid'])
+ assert_equal(entry_prioritised_before_restart['fees']['base'] + Decimal('0.00009999'), entry_prioritised_before_restart['fees']['modified'])
# Verify accounting of mempool transactions after restart is correct
if self.is_sqlite_compiled():
@@ -143,7 +151,7 @@ class MempoolPersistTest(BitcoinTestFramework):
self.stop_nodes()
self.start_node(0)
assert self.nodes[0].getmempoolinfo()["loaded"]
- assert_equal(len(self.nodes[0].getrawmempool()), 6)
+ assert_equal(len(self.nodes[0].getrawmempool()), 7)
mempooldat0 = os.path.join(self.nodes[0].datadir, self.chain, 'mempool.dat')
mempooldat1 = os.path.join(self.nodes[1].datadir, self.chain, 'mempool.dat')
@@ -153,12 +161,12 @@ class MempoolPersistTest(BitcoinTestFramework):
assert os.path.isfile(mempooldat0)
assert_equal(result0['filename'], mempooldat0)
- self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 6 transactions")
+ self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 7 transactions")
os.rename(mempooldat0, mempooldat1)
self.stop_nodes()
self.start_node(1, extra_args=["-persistmempool"])
assert self.nodes[1].getmempoolinfo()["loaded"]
- assert_equal(len(self.nodes[1].getrawmempool()), 6)
+ assert_equal(len(self.nodes[1].getrawmempool()), 7)
self.log.debug("Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails")
# to test the exception we are creating a tmp folder called mempool.dat.new
diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py
index 91f2d0051a..47ff520713 100755
--- a/test/functional/mempool_reorg.py
+++ b/test/functional/mempool_reorg.py
@@ -53,8 +53,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
utxo = wallet.get_utxo(txid=coinbase_txids[0])
timelock_tx = wallet.create_self_transfer(
utxo_to_spend=utxo,
- mempool_valid=False,
- locktime=self.nodes[0].getblockcount() + 2
+ locktime=self.nodes[0].getblockcount() + 2,
)['hex']
self.log.info("Check that the time-locked transaction is too immature to spend")
@@ -69,10 +68,8 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx)
self.log.info("Create spend_2_1 and spend_3_1")
- spend_2_utxo = wallet.get_utxo(txid=spend_2['txid'])
- spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2_utxo)
- spend_3_utxo = wallet.get_utxo(txid=spend_3['txid'])
- spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3_utxo)
+ spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2["new_utxo"])
+ spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3["new_utxo"])
self.log.info("Broadcast and mine spend_3_1")
spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex'])
diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py
index 9c43ddaf6f..3585871350 100755
--- a/test/functional/mempool_spend_coinbase.py
+++ b/test/functional/mempool_spend_coinbase.py
@@ -40,7 +40,7 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework):
spend_mature_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_mature)["txid"]
# other coinbase should be too immature to spend
- immature_tx = wallet.create_self_transfer(utxo_to_spend=utxo_immature, mempool_valid=False)
+ immature_tx = wallet.create_self_transfer(utxo_to_spend=utxo_immature)
assert_raises_rpc_error(-26,
"bad-txns-premature-spend-of-coinbase",
lambda: self.nodes[0].sendrawtransaction(immature_tx['hex']))
diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py
index 37ef4a9157..7587adc257 100755
--- a/test/functional/mempool_unbroadcast.py
+++ b/test/functional/mempool_unbroadcast.py
@@ -40,7 +40,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
wallet_tx_hsh = node.sendtoaddress(addr, 0.0001)
# generate a txn using sendrawtransaction
- txFS = self.wallet.create_self_transfer(from_node=node)
+ txFS = self.wallet.create_self_transfer()
rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
# check transactions are in unbroadcast using rpc
diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py
index 51de582ce0..f97c2223a6 100755
--- a/test/functional/mempool_updatefromblock.py
+++ b/test/functional/mempool_updatefromblock.py
@@ -12,6 +12,9 @@ import time
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
+from test_framework.address import key_to_p2pkh
+from test_framework.wallet_util import bytes_to_wif
+from test_framework.key import ECKey
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
@@ -19,8 +22,13 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100']]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ def get_new_address(self):
+ key = ECKey()
+ key.generate()
+ pubkey = key.get_pubkey().get_bytes()
+ address = key_to_p2pkh(pubkey)
+ self.priv_keys.append(bytes_to_wif(key.get_bytes()))
+ return address
def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', end_address='', fee=Decimal(0.00100000)):
"""Create an acyclic tournament (a type of directed graph) of transactions and use it for testing.
@@ -38,11 +46,12 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory)
"""
+ self.priv_keys = [self.nodes[0].get_deterministic_priv_key().key]
if not start_input_txid:
start_input_txid = self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0]
if not end_address:
- end_address = self.nodes[0].getnewaddress()
+ end_address = self.get_new_address()
first_block_hash = ''
tx_id = []
@@ -74,7 +83,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
output_value = ((inputs_value - fee) / Decimal(n_outputs)).quantize(Decimal('0.00000001'))
outputs = {}
for _ in range(n_outputs):
- outputs[self.nodes[0].getnewaddress()] = output_value
+ outputs[self.get_new_address()] = output_value
else:
output_value = (inputs_value - fee).quantize(Decimal('0.00000001'))
outputs = {end_address: output_value}
@@ -84,7 +93,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
# Create a new transaction.
unsigned_raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
- signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(unsigned_raw_tx)
+ signed_raw_tx = self.nodes[0].signrawtransactionwithkey(unsigned_raw_tx, self.priv_keys)
tx_id.append(self.nodes[0].sendrawtransaction(signed_raw_tx['hex']))
tx_size.append(self.nodes[0].getmempoolentry(tx_id[-1])['vsize'])
diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py
index a15fbe5a24..64e66ac30a 100755
--- a/test/functional/mining_prioritisetransaction.py
+++ b/test/functional/mining_prioritisetransaction.py
@@ -7,29 +7,31 @@
from decimal import Decimal
import time
-from test_framework.blocktools import COINBASE_MATURITY
-from test_framework.messages import COIN, MAX_BLOCK_WEIGHT
+from test_framework.messages import (
+ COIN,
+ MAX_BLOCK_WEIGHT,
+)
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, assert_raises_rpc_error, create_confirmed_utxos, create_lots_of_big_transactions, gen_return_txouts
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+ create_lots_of_big_transactions,
+ gen_return_txouts,
+)
from test_framework.wallet import MiniWallet
class PrioritiseTransactionTest(BitcoinTestFramework):
def set_test_params(self):
- self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
"-printpriority=1",
- "-acceptnonstdtxn=1",
+ "-datacarriersize=100000",
]] * self.num_nodes
self.supports_cli = False
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
def test_diamond(self):
self.log.info("Test diamond-shape package with priority")
- self.generate(self.wallet, COINBASE_MATURITY + 1)
mock_time = int(time.time())
self.nodes[0].setmocktime(mock_time)
@@ -104,6 +106,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
def run_test(self):
self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.rescan_utxos()
# Test `prioritisetransaction` required parameters
assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction)
@@ -131,7 +134,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
self.relayfee = self.nodes[0].getnetworkinfo()['relayfee']
utxo_count = 90
- utxos = create_confirmed_utxos(self, self.relayfee, self.nodes[0], utxo_count)
+ utxos = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=utxo_count)['new_utxos']
+ self.generate(self.wallet, 1)
+ assert_equal(len(self.nodes[0].getrawmempool()), 0)
+
base_fee = self.relayfee*100 # our transactions are smaller than 100kb
txids = []
@@ -141,7 +147,13 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
txids.append([])
start_range = i * range_size
end_range = start_range + range_size
- txids[i] = create_lots_of_big_transactions(self.nodes[0], self.txouts, utxos[start_range:end_range], end_range - start_range, (i+1)*base_fee)
+ txids[i] = create_lots_of_big_transactions(
+ self.wallet,
+ self.nodes[0],
+ (i+1) * base_fee,
+ end_range - start_range,
+ self.txouts,
+ utxos[start_range:end_range])
# Make sure that the size of each group of transactions exceeds
# MAX_BLOCK_WEIGHT // 4 -- otherwise the test needs to be revised to
@@ -200,17 +212,9 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
assert x not in mempool
# Create a free transaction. Should be rejected.
- utxo_list = self.nodes[0].listunspent()
- assert len(utxo_list) > 0
- utxo = utxo_list[0]
-
- inputs = []
- outputs = {}
- inputs.append({"txid" : utxo["txid"], "vout" : utxo["vout"]})
- outputs[self.nodes[0].getnewaddress()] = utxo["amount"]
- raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
- tx_hex = self.nodes[0].signrawtransactionwithwallet(raw_tx)["hex"]
- tx_id = self.nodes[0].decoderawtransaction(tx_hex)["txid"]
+ tx_res = self.wallet.create_self_transfer(fee_rate=0)
+ tx_hex = tx_res['hex']
+ tx_id = tx_res['txid']
# This will raise an exception due to min relay fee not being met
assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, tx_hex)
diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py
index 3218a9b14a..e2e9b6dcb2 100755
--- a/test/functional/p2p_addr_relay.py
+++ b/test/functional/p2p_addr_relay.py
@@ -21,8 +21,19 @@ from test_framework.p2p import (
P2P_SERVICES,
)
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal, assert_greater_than
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ assert_greater_than_or_equal
+)
+
+ONE_MINUTE = 60
+TEN_MINUTES = 10 * ONE_MINUTE
+ONE_HOUR = 60 * ONE_MINUTE
+TWO_HOURS = 2 * ONE_HOUR
+ONE_DAY = 24 * ONE_HOUR
+ADDR_DESTINATIONS_THRESHOLD = 4
class AddrReceiver(P2PInterface):
num_ipv4_received = 0
@@ -85,6 +96,9 @@ class AddrTest(BitcoinTestFramework):
self.relay_tests()
self.inbound_blackhole_tests()
+ self.destination_rotates_once_in_24_hours_test()
+ self.destination_rotates_more_than_once_over_several_days_test()
+
# This test populates the addrman, which can impact the node's behavior
# in subsequent tests
self.getaddr_tests()
@@ -362,6 +376,56 @@ class AddrTest(BitcoinTestFramework):
self.nodes[0].disconnect_p2ps()
+ def get_nodes_that_received_addr(self, peer, receiver_peer, addr_receivers,
+ time_interval_1, time_interval_2):
+
+ # Clean addr response related to the initial getaddr. There is no way to avoid initial
+ # getaddr because the peer won't self-announce then.
+ for addr_receiver in addr_receivers:
+ addr_receiver.num_ipv4_received = 0
+
+ for _ in range(10):
+ self.mocktime += time_interval_1
+ self.msg.addrs[0].time = self.mocktime + TEN_MINUTES
+ self.nodes[0].setmocktime(self.mocktime)
+ with self.nodes[0].assert_debug_log(['received: addr (31 bytes) peer=0']):
+ peer.send_and_ping(self.msg)
+ self.mocktime += time_interval_2
+ self.nodes[0].setmocktime(self.mocktime)
+ receiver_peer.sync_with_ping()
+ return [node for node in addr_receivers if node.addr_received()]
+
+ def destination_rotates_once_in_24_hours_test(self):
+ self.restart_node(0, [])
+
+ self.log.info('Test within 24 hours an addr relay destination is rotated at most once')
+ self.mocktime = int(time.time())
+ self.msg = self.setup_addr_msg(1)
+ self.addr_receivers = []
+ peer = self.nodes[0].add_p2p_connection(P2PInterface())
+ receiver_peer = self.nodes[0].add_p2p_connection(AddrReceiver())
+ addr_receivers = [self.nodes[0].add_p2p_connection(AddrReceiver()) for _ in range(20)]
+ nodes_received_addr = self.get_nodes_that_received_addr(peer, receiver_peer, addr_receivers, 0, TWO_HOURS) # 10 intervals of 2 hours
+ # Per RelayAddress, we would announce these addrs to 2 destinations per day.
+ # Since it's at most one rotation, at most 4 nodes can receive ADDR.
+ assert_greater_than_or_equal(ADDR_DESTINATIONS_THRESHOLD, len(nodes_received_addr))
+ self.nodes[0].disconnect_p2ps()
+
+ def destination_rotates_more_than_once_over_several_days_test(self):
+ self.restart_node(0, [])
+
+ self.log.info('Test after several days an addr relay destination is rotated more than once')
+ self.msg = self.setup_addr_msg(1)
+ peer = self.nodes[0].add_p2p_connection(P2PInterface())
+ receiver_peer = self.nodes[0].add_p2p_connection(AddrReceiver())
+ addr_receivers = [self.nodes[0].add_p2p_connection(AddrReceiver()) for _ in range(20)]
+ # 10 intervals of 1 day (+ 1 hour, which should be enough to cover 30-min Poisson in most cases)
+ nodes_received_addr = self.get_nodes_that_received_addr(peer, receiver_peer, addr_receivers, ONE_DAY, ONE_HOUR)
+ # Now that there should have been more than one rotation, more than
+ # ADDR_DESTINATIONS_THRESHOLD nodes should have received ADDR.
+ assert_greater_than(len(nodes_received_addr), ADDR_DESTINATIONS_THRESHOLD)
+ self.nodes[0].disconnect_p2ps()
+
if __name__ == '__main__':
AddrTest().main()
diff --git a/test/functional/p2p_block_sync.py b/test/functional/p2p_block_sync.py
new file mode 100755
index 0000000000..d821edc1b1
--- /dev/null
+++ b/test/functional/p2p_block_sync.py
@@ -0,0 +1,37 @@
+#!/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.
+"""Test block download
+
+Ensure that even in IBD, we'll eventually sync chain from inbound peers
+(whether we have only inbound peers or both inbound and outbound peers).
+"""
+
+from test_framework.test_framework import BitcoinTestFramework
+
+class BlockSyncTest(BitcoinTestFramework):
+
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 3
+
+ def setup_network(self):
+ self.setup_nodes()
+ # Construct a network:
+ # node0 -> node1 -> node2
+ # So node1 has both an inbound and outbound peer.
+ # In our test, we will mine a block on node0, and ensure that it makes
+ # to to both node1 and node2.
+ self.connect_nodes(0, 1)
+ self.connect_nodes(1, 2)
+
+ def run_test(self):
+ self.log.info("Setup network: node0->node1->node2")
+ self.log.info("Mining one block on node0 and verify all nodes sync")
+ self.generate(self.nodes[0], 1)
+ self.log.info("Success!")
+
+
+if __name__ == '__main__':
+ BlockSyncTest().main()
diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py
index e45cef65bd..ef12b5f6b7 100755
--- a/test/functional/p2p_blockfilters.py
+++ b/test/functional/p2p_blockfilters.py
@@ -250,6 +250,17 @@ class CompactFiltersTest(BitcoinTestFramework):
msg = "Error: Cannot set -peerblockfilters without -blockfilterindex."
self.nodes[0].assert_start_raises_init_error(expected_msg=msg)
+ self.log.info("Test unknown value to -blockfilterindex raises an error")
+ self.nodes[0].extra_args = ["-blockfilterindex=abc"]
+ msg = "Error: Unknown -blockfilterindex value abc."
+ self.nodes[0].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test -blockfilterindex with -reindex-chainstate raises an error")
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: -reindex-chainstate option is not compatible with -blockfilterindex. '
+ 'Please temporarily disable blockfilterindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.',
+ extra_args=['-blockfilterindex', '-reindex-chainstate'],
+ )
def compute_last_header(prev_header, hashes):
"""Compute the last filter header from a starting header and a sequence of filter hashes."""
diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py
index 12ee4b3c27..8ac38bff3a 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -89,6 +89,11 @@ class P2PBlocksOnly(BitcoinTestFramework):
assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False)
_, txid, _, tx_hex = self.check_p2p_tx_violation()
+ self.log.info("Tests with node in normal mode with block-relay-only connection, sending an inv")
+ conn = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="block-relay-only")
+ assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False)
+ self.check_p2p_inv_violation(conn)
+
self.log.info("Check that txs from RPC are not sent to blockrelay connection")
conn = self.nodes[0].add_outbound_p2p_connection(P2PTxInvStore(), p2p_idx=1, connection_type="block-relay-only")
@@ -100,6 +105,13 @@ class P2PBlocksOnly(BitcoinTestFramework):
conn.sync_send_with_ping()
assert(int(txid, 16) not in conn.get_invs())
+ def check_p2p_inv_violation(self, peer):
+ self.log.info("Check that tx-invs from P2P are rejected and result in disconnect")
+ with self.nodes[0].assert_debug_log(["inv sent in violation of protocol, disconnecting peer"]):
+ peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0x12345)]))
+ peer.wait_for_disconnect()
+ self.nodes[0].disconnect_p2ps()
+
def check_p2p_tx_violation(self):
self.log.info('Check that txs from P2P are rejected and result in disconnect')
spendtx = self.miniwallet.create_self_transfer()
@@ -108,9 +120,7 @@ class P2PBlocksOnly(BitcoinTestFramework):
self.nodes[0].p2ps[0].send_message(msg_tx(spendtx['tx']))
self.nodes[0].p2ps[0].wait_for_disconnect()
assert_equal(self.nodes[0].getmempoolinfo()['size'], 0)
-
- # Remove the disconnected peer
- del self.nodes[0].p2ps[0]
+ self.nodes[0].disconnect_p2ps()
return spendtx['tx'], spendtx['txid'], spendtx['wtxid'], spendtx['hex']
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 364e806e18..5e50e1ebce 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -2,11 +2,7 @@
# Copyright (c) 2016-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 compact blocks (BIP 152).
-
-Version 1 compact blocks are pre-segwit (txids)
-Version 2 compact blocks are post-segwit (wtxids)
-"""
+"""Test compact blocks (BIP 152)."""
import random
from test_framework.blocktools import (
@@ -31,7 +27,6 @@ from test_framework.messages import (
MSG_BLOCK,
MSG_CMPCT_BLOCK,
MSG_WITNESS_FLAG,
- NODE_NETWORK,
P2PHeaderAndShortIDs,
PrefilledTransaction,
calculate_shortid,
@@ -70,7 +65,7 @@ from test_framework.wallet import MiniWallet
# TestP2PConn: A peer we use to send messages to bitcoind, and store responses.
class TestP2PConn(P2PInterface):
- def __init__(self, cmpct_version):
+ def __init__(self):
super().__init__()
self.last_sendcmpct = []
self.block_announced = False
@@ -78,7 +73,6 @@ class TestP2PConn(P2PInterface):
# This is for synchronizing the p2p message traffic,
# so we can eg wait until a particular block is announced.
self.announced_blockhashes = set()
- self.cmpct_version = cmpct_version
def on_sendcmpct(self, message):
self.last_sendcmpct.append(message)
@@ -152,10 +146,8 @@ class CompactBlocksTest(BitcoinTestFramework):
]]
self.utxos = []
- def build_block_on_tip(self, node, segwit=False):
+ def build_block_on_tip(self, node):
block = create_block(tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS))
- if segwit:
- add_witness_commitment(block)
block.solve()
return block
@@ -185,15 +177,13 @@ class CompactBlocksTest(BitcoinTestFramework):
# Test "sendcmpct" (between peers preferring the same version):
# - No compact block announcements unless sendcmpct is sent.
- # - If sendcmpct is sent with version > preferred_version, the message is ignored.
+ # - If sendcmpct is sent with version = 1, the message is ignored.
+ # - If sendcmpct is sent with version > 2, the message is ignored.
# - If sendcmpct is sent with boolean 0, then block announcements are not
# made with compact blocks.
# - If sendcmpct is then sent with boolean 1, then new block announcements
# are made with compact blocks.
- # If old_node is passed in, request compact blocks with version=preferred-1
- # and verify that it receives block announcements via compact block.
- def test_sendcmpct(self, test_node, old_node=None):
- preferred_version = test_node.cmpct_version
+ def test_sendcmpct(self, test_node):
node = self.nodes[0]
# Make sure we get a SENDCMPCT message from our peer
@@ -201,10 +191,8 @@ class CompactBlocksTest(BitcoinTestFramework):
return (len(test_node.last_sendcmpct) > 0)
test_node.wait_until(received_sendcmpct, timeout=30)
with p2p_lock:
- # Check that the first version received is the preferred one
- assert_equal(test_node.last_sendcmpct[0].version, preferred_version)
- # And that we receive versions down to 1.
- assert_equal(test_node.last_sendcmpct[-1].version, 1)
+ # Check that version 2 is received.
+ assert_equal(test_node.last_sendcmpct[0].version, 2)
test_node.last_sendcmpct = []
tip = int(node.getbestblockhash(), 16)
@@ -232,22 +220,29 @@ class CompactBlocksTest(BitcoinTestFramework):
# Before each test, sync the headers chain.
test_node.request_headers_and_sync(locator=[tip])
+ # Now try a SENDCMPCT message with too-low version
+ test_node.send_and_ping(msg_sendcmpct(announce=True, version=1))
+ check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
+
+ # Headers sync before next test.
+ test_node.request_headers_and_sync(locator=[tip])
+
# Now try a SENDCMPCT message with too-high version
- test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version+1))
+ test_node.send_and_ping(msg_sendcmpct(announce=True, version=3))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
# Headers sync before next test.
test_node.request_headers_and_sync(locator=[tip])
# Now try a SENDCMPCT message with valid version, but announce=False
- test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version))
+ test_node.send_and_ping(msg_sendcmpct(announce=False, version=2))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
# Headers sync before next test.
test_node.request_headers_and_sync(locator=[tip])
# Finally, try a SENDCMPCT message with announce=True
- test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version))
+ test_node.send_and_ping(msg_sendcmpct(announce=True, version=2))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Try one more time (no headers sync should be needed!)
@@ -257,22 +252,14 @@ class CompactBlocksTest(BitcoinTestFramework):
test_node.send_and_ping(msg_sendheaders())
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
- # Try one more time, after sending a version-1, announce=false message.
- test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version-1))
+ # Try one more time, after sending a version=1, announce=false message.
+ test_node.send_and_ping(msg_sendcmpct(announce=False, version=1))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Now turn off announcements
- test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version))
+ test_node.send_and_ping(msg_sendcmpct(announce=False, version=2))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message and "headers" in p.last_message)
- if old_node is not None:
- # Verify that a peer using an older protocol version can receive
- # announcements from this node.
- old_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version-1))
- # Header sync
- old_node.request_headers_and_sync(locator=[tip])
- check_announcement_of_new_block(node, old_node, lambda p: "cmpctblock" in p.last_message)
-
# This test actually causes bitcoind to (reasonably!) disconnect us, so do this last.
def test_invalid_cmpctblock_message(self):
self.generate(self.nodes[0], COINBASE_MATURITY + 1)
@@ -289,8 +276,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# Compare the generated shortids to what we expect based on BIP 152, given
# bitcoind's choice of nonce.
- def test_compactblock_construction(self, test_node, use_witness_address=True):
- version = test_node.cmpct_version
+ def test_compactblock_construction(self, test_node):
node = self.nodes[0]
# Generate a bunch of transactions.
self.generate(node, COINBASE_MATURITY + 1)
@@ -303,8 +289,7 @@ class CompactBlocksTest(BitcoinTestFramework):
if not tx.wit.is_null():
segwit_tx_generated = True
- if use_witness_address:
- assert segwit_tx_generated # check that our test is not broken
+ assert segwit_tx_generated # check that our test is not broken
# Wait until we've seen the block announcement for the resulting tip
tip = int(node.getbestblockhash(), 16)
@@ -331,7 +316,7 @@ class CompactBlocksTest(BitcoinTestFramework):
with p2p_lock:
# Convert the on-the-wire representation to absolute indexes
header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids)
- self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
+ self.check_compactblock_construction_from_block(header_and_shortids, block_hash, block)
# Now fetch the compact block using a normal non-announce getdata
test_node.clear_block_announcement()
@@ -345,9 +330,9 @@ class CompactBlocksTest(BitcoinTestFramework):
with p2p_lock:
# Convert the on-the-wire representation to absolute indexes
header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids)
- self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
+ self.check_compactblock_construction_from_block(header_and_shortids, block_hash, block)
- def check_compactblock_construction_from_block(self, version, header_and_shortids, block_hash, block):
+ def check_compactblock_construction_from_block(self, header_and_shortids, block_hash, block):
# Check that we got the right block!
header_and_shortids.header.calc_sha256()
assert_equal(header_and_shortids.header.sha256, block_hash)
@@ -364,11 +349,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# And this checks the witness
wtxid = entry.tx.calc_sha256(True)
- if version == 2:
- assert_equal(wtxid, block.vtx[entry.index].calc_sha256(True))
- else:
- # Shouldn't have received a witness
- assert entry.tx.wit.is_null()
+ assert_equal(wtxid, block.vtx[entry.index].calc_sha256(True))
# Check that the cmpctblock message announced all the transactions.
assert_equal(len(header_and_shortids.prefilled_txn) + len(header_and_shortids.shortids), len(block.vtx))
@@ -384,9 +365,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# Already checked prefilled transactions above
header_and_shortids.prefilled_txn.pop(0)
else:
- tx_hash = block.vtx[index].sha256
- if version == 2:
- tx_hash = block.vtx[index].calc_sha256(True)
+ tx_hash = block.vtx[index].calc_sha256(True)
shortid = calculate_shortid(k0, k1, tx_hash)
assert_equal(shortid, header_and_shortids.shortids[0])
header_and_shortids.shortids.pop(0)
@@ -395,16 +374,12 @@ class CompactBlocksTest(BitcoinTestFramework):
# Test that bitcoind requests compact blocks when we announce new blocks
# via header or inv, and that responding to getblocktxn causes the block
# to be successfully reconstructed.
- # Post-segwit: upgraded nodes would only make this request of cb-version-2,
- # NODE_WITNESS peers. Unupgraded nodes would still make this request of
- # any cb-version-1-supporting peer.
- def test_compactblock_requests(self, test_node, segwit=True):
- version = test_node.cmpct_version
+ def test_compactblock_requests(self, test_node):
node = self.nodes[0]
# Try announcing a block with an inv or header, expect a compactblock
# request
for announce in ["inv", "header"]:
- block = self.build_block_on_tip(node, segwit=segwit)
+ block = self.build_block_on_tip(node)
if announce == "inv":
test_node.send_message(msg_inv([CInv(MSG_BLOCK, block.sha256)]))
@@ -420,9 +395,7 @@ class CompactBlocksTest(BitcoinTestFramework):
comp_block.header = CBlockHeader(block)
comp_block.nonce = 0
[k0, k1] = comp_block.get_siphash_keys()
- coinbase_hash = block.vtx[0].sha256
- if version == 2:
- coinbase_hash = block.vtx[0].calc_sha256(True)
+ coinbase_hash = block.vtx[0].calc_sha256(True)
comp_block.shortids = [calculate_shortid(k0, k1, coinbase_hash)]
test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))
assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock)
@@ -433,10 +406,7 @@ class CompactBlocksTest(BitcoinTestFramework):
assert_equal(absolute_indexes, [0]) # should be a coinbase request
# Send the coinbase, and verify that the tip advances.
- if version == 2:
- msg = msg_blocktxn()
- else:
- msg = msg_no_witness_blocktxn()
+ msg = msg_blocktxn()
msg.block_transactions.blockhash = block.sha256
msg.block_transactions.transactions = [block.vtx[0]]
test_node.send_and_ping(msg)
@@ -462,9 +432,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# node needs, and that responding to them causes the block to be
# reconstructed.
def test_getblocktxn_requests(self, test_node):
- version = test_node.cmpct_version
node = self.nodes[0]
- with_witness = (version == 2)
def test_getblocktxn_response(compact_block, peer, expected_result):
msg = msg_cmpctblock(compact_block.to_p2p())
@@ -485,13 +453,12 @@ class CompactBlocksTest(BitcoinTestFramework):
block = self.build_block_with_transactions(node, utxo, 5)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
comp_block = HeaderAndShortIDs()
- comp_block.initialize_from_block(block, use_witness=with_witness)
+ comp_block.initialize_from_block(block, use_witness=True)
test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5])
msg_bt = msg_no_witness_blocktxn()
- if with_witness:
- msg_bt = msg_blocktxn() # serialize with witnesses
+ msg_bt = msg_blocktxn() # serialize with witnesses
msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:])
test_tip_after_message(node, test_node, msg_bt, block.sha256)
@@ -500,7 +467,7 @@ class CompactBlocksTest(BitcoinTestFramework):
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
# Now try interspersing the prefilled transactions
- comp_block.initialize_from_block(block, prefill_list=[0, 1, 5], use_witness=with_witness)
+ comp_block.initialize_from_block(block, prefill_list=[0, 1, 5], use_witness=True)
test_getblocktxn_response(comp_block, test_node, [2, 3, 4])
msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[2:5])
test_tip_after_message(node, test_node, msg_bt, block.sha256)
@@ -514,7 +481,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# Prefill 4 out of the 6 transactions, and verify that only the one
# that was not in the mempool is requested.
- comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4], use_witness=with_witness)
+ comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4], use_witness=True)
test_getblocktxn_response(comp_block, test_node, [5])
msg_bt.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]])
@@ -538,7 +505,7 @@ class CompactBlocksTest(BitcoinTestFramework):
test_node.last_message.pop("getblocktxn", None)
# Send compact block
- comp_block.initialize_from_block(block, prefill_list=[0], use_witness=with_witness)
+ comp_block.initialize_from_block(block, prefill_list=[0], use_witness=True)
test_tip_after_message(node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256)
with p2p_lock:
# Shouldn't have gotten a request for any transaction
@@ -547,7 +514,6 @@ class CompactBlocksTest(BitcoinTestFramework):
# Incorrectly responding to a getblocktxn shouldn't cause the block to be
# permanently failed.
def test_incorrect_blocktxn_response(self, test_node):
- version = test_node.cmpct_version
node = self.nodes[0]
utxo = self.utxos.pop(0)
@@ -564,7 +530,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# Send compact block
comp_block = HeaderAndShortIDs()
- comp_block.initialize_from_block(block, prefill_list=[0], use_witness=(version == 2))
+ comp_block.initialize_from_block(block, prefill_list=[0], use_witness=True)
test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))
absolute_indexes = []
with p2p_lock:
@@ -580,9 +546,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# different peer provide the block further down, so that we're still
# verifying that the block isn't marked bad permanently. This is good
# enough for now.
- msg = msg_no_witness_blocktxn()
- if version == 2:
- msg = msg_blocktxn()
+ msg = msg_blocktxn()
msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:])
test_node.send_and_ping(msg)
@@ -595,14 +559,10 @@ class CompactBlocksTest(BitcoinTestFramework):
test_node.last_message["getdata"].inv[0].type == MSG_BLOCK | MSG_WITNESS_FLAG
# Deliver the block
- if version == 2:
- test_node.send_and_ping(msg_block(block))
- else:
- test_node.send_and_ping(msg_no_witness_block(block))
+ test_node.send_and_ping(msg_block(block))
assert_equal(int(node.getbestblockhash(), 16), block.sha256)
def test_getblocktxn_handler(self, test_node):
- version = test_node.cmpct_version
node = self.nodes[0]
# bitcoind will not send blocktxn responses for blocks whose height is
# more than 10 blocks deep.
@@ -628,12 +588,8 @@ class CompactBlocksTest(BitcoinTestFramework):
tx = test_node.last_message["blocktxn"].block_transactions.transactions.pop(0)
tx.calc_sha256()
assert_equal(tx.sha256, block.vtx[index].sha256)
- if version == 1:
- # Witnesses should have been stripped
- assert tx.wit.is_null()
- else:
- # Check that the witness matches
- assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True))
+ # Check that the witness matches
+ assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True))
test_node.last_message.pop("blocktxn", None)
current_height -= 1
@@ -650,6 +606,15 @@ class CompactBlocksTest(BitcoinTestFramework):
assert_equal(test_node.last_message["block"].block.sha256, int(block_hash, 16))
assert "blocktxn" not in test_node.last_message
+ # Request with out-of-bounds tx index results in disconnect
+ bad_peer = self.nodes[0].add_p2p_connection(TestP2PConn())
+ block_hash = node.getblockhash(chain_height)
+ block = from_hex(CBlock(), node.getblock(block_hash, False))
+ msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [len(block.vtx)])
+ with node.assert_debug_log(['getblocktxn with out-of-bounds tx indices']):
+ bad_peer.send_message(msg)
+ bad_peer.wait_for_disconnect()
+
def test_compactblocks_not_at_tip(self, test_node):
node = self.nodes[0]
# Test that requesting old compactblocks doesn't work.
@@ -727,7 +692,7 @@ class CompactBlocksTest(BitcoinTestFramework):
# Test that we don't get disconnected if we relay a compact block with valid header,
# but invalid transactions.
- def test_invalid_tx_in_compactblock(self, test_node, use_segwit=True):
+ def test_invalid_tx_in_compactblock(self, test_node):
node = self.nodes[0]
assert len(self.utxos)
utxo = self.utxos[0]
@@ -735,17 +700,15 @@ class CompactBlocksTest(BitcoinTestFramework):
block = self.build_block_with_transactions(node, utxo, 5)
del block.vtx[3]
block.hashMerkleRoot = block.calc_merkle_root()
- if use_segwit:
- # If we're testing with segwit, also drop the coinbase witness,
- # but include the witness commitment.
- add_witness_commitment(block)
- block.vtx[0].wit.vtxinwit = []
+ # Drop the coinbase witness but include the witness commitment.
+ add_witness_commitment(block)
+ block.vtx[0].wit.vtxinwit = []
block.solve()
# Now send the compact block with all transactions prefilled, and
# verify that we don't get disconnected.
comp_block = HeaderAndShortIDs()
- comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4], use_witness=use_segwit)
+ comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4], use_witness=True)
msg = msg_cmpctblock(comp_block.to_p2p())
test_node.send_and_ping(msg)
@@ -759,7 +722,7 @@ class CompactBlocksTest(BitcoinTestFramework):
node = self.nodes[0]
tip = node.getbestblockhash()
peer.get_headers(locator=[int(tip, 16)], hashstop=0)
- peer.send_and_ping(msg_sendcmpct(announce=True, version=peer.cmpct_version))
+ peer.send_and_ping(msg_sendcmpct(announce=True, version=2))
def test_compactblock_reconstruction_multiple_peers(self, stalling_peer, delivery_peer):
node = self.nodes[0]
@@ -813,7 +776,7 @@ class CompactBlocksTest(BitcoinTestFramework):
def test_highbandwidth_mode_states_via_getpeerinfo(self):
# create new p2p connection for a fresh state w/o any prior sendcmpct messages sent
- hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
+ hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn())
# assert the RPC getpeerinfo boolean fields `bip152_hb_{to, from}`
# match the given parameters for the last peer of a given node
@@ -843,9 +806,8 @@ class CompactBlocksTest(BitcoinTestFramework):
self.wallet = MiniWallet(self.nodes[0])
# Setup the p2p connections
- self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
- self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1), services=NODE_NETWORK)
- self.additional_segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
+ self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn())
+ self.additional_segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn())
# We will need UTXOs to construct transactions in later tests.
self.make_utxos()
@@ -853,11 +815,10 @@ class CompactBlocksTest(BitcoinTestFramework):
assert softfork_active(self.nodes[0], "segwit")
self.log.info("Testing SENDCMPCT p2p message... ")
- self.test_sendcmpct(self.segwit_node, old_node=self.old_node)
+ self.test_sendcmpct(self.segwit_node)
self.test_sendcmpct(self.additional_segwit_node)
self.log.info("Testing compactblock construction...")
- self.test_compactblock_construction(self.old_node)
self.test_compactblock_construction(self.segwit_node)
self.log.info("Testing compactblock requests (segwit node)... ")
@@ -868,11 +829,9 @@ class CompactBlocksTest(BitcoinTestFramework):
self.log.info("Testing getblocktxn handler (segwit node should return witnesses)...")
self.test_getblocktxn_handler(self.segwit_node)
- self.test_getblocktxn_handler(self.old_node)
self.log.info("Testing compactblock requests/announcements not at chain tip...")
self.test_compactblocks_not_at_tip(self.segwit_node)
- self.test_compactblocks_not_at_tip(self.old_node)
self.log.info("Testing handling of incorrect blocktxn responses...")
self.test_incorrect_blocktxn_response(self.segwit_node)
@@ -885,13 +844,12 @@ class CompactBlocksTest(BitcoinTestFramework):
# (Post-segwit activation, blocks won't propagate from node0 to node1
# automatically, so don't bother testing a block announced to node0.)
self.log.info("Testing end-to-end block relay...")
- self.request_cb_announcements(self.old_node)
self.request_cb_announcements(self.segwit_node)
- self.test_end_to_end_block_relay([self.segwit_node, self.old_node])
+ self.request_cb_announcements(self.additional_segwit_node)
+ self.test_end_to_end_block_relay([self.segwit_node, self.additional_segwit_node])
self.log.info("Testing handling of invalid compact blocks...")
self.test_invalid_tx_in_compactblock(self.segwit_node)
- self.test_invalid_tx_in_compactblock(self.old_node)
self.log.info("Testing invalid index in cmpctblock message...")
self.test_invalid_cmpctblock_message()
diff --git a/test/functional/p2p_compactblocks_blocksonly.py b/test/functional/p2p_compactblocks_blocksonly.py
index 6367eb26a3..3d0c421a93 100755
--- a/test/functional/p2p_compactblocks_blocksonly.py
+++ b/test/functional/p2p_compactblocks_blocksonly.py
@@ -48,7 +48,7 @@ class P2PCompactBlocksBlocksOnly(BitcoinTestFramework):
p2p_conn_high_bw = self.nodes[1].add_p2p_connection(P2PInterface())
p2p_conn_low_bw = self.nodes[3].add_p2p_connection(P2PInterface())
for conn in [p2p_conn_blocksonly, p2p_conn_high_bw, p2p_conn_low_bw]:
- assert_equal(conn.message_count['sendcmpct'], 2)
+ assert_equal(conn.message_count['sendcmpct'], 1)
conn.send_and_ping(msg_sendcmpct(announce=False, version=2))
# Nodes:
@@ -74,14 +74,14 @@ class P2PCompactBlocksBlocksOnly(BitcoinTestFramework):
# receiving a new valid block at the tip.
p2p_conn_blocksonly.send_and_ping(msg_block(block0))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block0.sha256)
- assert_equal(p2p_conn_blocksonly.message_count['sendcmpct'], 2)
+ assert_equal(p2p_conn_blocksonly.message_count['sendcmpct'], 1)
assert_equal(p2p_conn_blocksonly.last_message['sendcmpct'].announce, False)
# A normal node participating in transaction relay should request BIP152
# high bandwidth mode upon receiving a new valid block at the tip.
p2p_conn_high_bw.send_and_ping(msg_block(block0))
assert_equal(int(self.nodes[1].getbestblockhash(), 16), block0.sha256)
- p2p_conn_high_bw.wait_until(lambda: p2p_conn_high_bw.message_count['sendcmpct'] == 3)
+ p2p_conn_high_bw.wait_until(lambda: p2p_conn_high_bw.message_count['sendcmpct'] == 2)
assert_equal(p2p_conn_high_bw.last_message['sendcmpct'].announce, True)
# Don't send a block from the p2p_conn_low_bw so the low bandwidth node
diff --git a/test/functional/p2p_getaddr_caching.py b/test/functional/p2p_getaddr_caching.py
index d375af6fe1..8907c34a89 100755
--- a/test/functional/p2p_getaddr_caching.py
+++ b/test/functional/p2p_getaddr_caching.py
@@ -14,6 +14,7 @@ from test_framework.p2p import (
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ p2p_port,
)
# As defined in net_processing.
@@ -42,6 +43,12 @@ class AddrReceiver(P2PInterface):
class AddrTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
+ # Use some of the remaining p2p ports for the onion binds.
+ self.onion_port1 = p2p_port(self.num_nodes)
+ self.onion_port2 = p2p_port(self.num_nodes + 1)
+ self.extra_args = [
+ [f"-bind=127.0.0.1:{self.onion_port1}=onion", f"-bind=127.0.0.1:{self.onion_port2}=onion"],
+ ]
def run_test(self):
self.log.info('Fill peer AddrMan with a lot of records')
@@ -55,35 +62,66 @@ class AddrTest(BitcoinTestFramework):
# only a fraction of all known addresses can be cached and returned.
assert(len(self.nodes[0].getnodeaddresses(0)) > int(MAX_ADDR_TO_SEND / (MAX_PCT_ADDR_TO_SEND / 100)))
- responses = []
+ last_response_on_local_bind = None
+ last_response_on_onion_bind1 = None
+ last_response_on_onion_bind2 = None
self.log.info('Send many addr requests within short time to receive same response')
N = 5
cur_mock_time = int(time.time())
for i in range(N):
- addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
- addr_receiver.send_and_ping(msg_getaddr())
+ addr_receiver_local = self.nodes[0].add_p2p_connection(AddrReceiver())
+ addr_receiver_local.send_and_ping(msg_getaddr())
+ addr_receiver_onion1 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port1)
+ addr_receiver_onion1.send_and_ping(msg_getaddr())
+ addr_receiver_onion2 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port2)
+ addr_receiver_onion2.send_and_ping(msg_getaddr())
+
# Trigger response
cur_mock_time += 5 * 60
self.nodes[0].setmocktime(cur_mock_time)
- addr_receiver.wait_until(addr_receiver.addr_received)
- responses.append(addr_receiver.get_received_addrs())
- for response in responses[1:]:
- assert_equal(response, responses[0])
- assert(len(response) == MAX_ADDR_TO_SEND)
+ addr_receiver_local.wait_until(addr_receiver_local.addr_received)
+ addr_receiver_onion1.wait_until(addr_receiver_onion1.addr_received)
+ addr_receiver_onion2.wait_until(addr_receiver_onion2.addr_received)
+
+ if i > 0:
+ # Responses from different binds should be unique
+ assert(last_response_on_local_bind != addr_receiver_onion1.get_received_addrs())
+ assert(last_response_on_local_bind != addr_receiver_onion2.get_received_addrs())
+ assert(last_response_on_onion_bind1 != addr_receiver_onion2.get_received_addrs())
+ # Responses on from the same bind should be the same
+ assert_equal(last_response_on_local_bind, addr_receiver_local.get_received_addrs())
+ assert_equal(last_response_on_onion_bind1, addr_receiver_onion1.get_received_addrs())
+ assert_equal(last_response_on_onion_bind2, addr_receiver_onion2.get_received_addrs())
+
+ last_response_on_local_bind = addr_receiver_local.get_received_addrs()
+ last_response_on_onion_bind1 = addr_receiver_onion1.get_received_addrs()
+ last_response_on_onion_bind2 = addr_receiver_onion2.get_received_addrs()
+
+ for response in [last_response_on_local_bind, last_response_on_onion_bind1, last_response_on_onion_bind2]:
+ assert_equal(len(response), MAX_ADDR_TO_SEND)
cur_mock_time += 3 * 24 * 60 * 60
self.nodes[0].setmocktime(cur_mock_time)
self.log.info('After time passed, see a new response to addr request')
- last_addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
- last_addr_receiver.send_and_ping(msg_getaddr())
+ addr_receiver_local = self.nodes[0].add_p2p_connection(AddrReceiver())
+ addr_receiver_local.send_and_ping(msg_getaddr())
+ addr_receiver_onion1 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port1)
+ addr_receiver_onion1.send_and_ping(msg_getaddr())
+ addr_receiver_onion2 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port2)
+ addr_receiver_onion2.send_and_ping(msg_getaddr())
+
# Trigger response
cur_mock_time += 5 * 60
self.nodes[0].setmocktime(cur_mock_time)
- last_addr_receiver.wait_until(last_addr_receiver.addr_received)
- # new response is different
- assert(set(responses[0]) != set(last_addr_receiver.get_received_addrs()))
+ addr_receiver_local.wait_until(addr_receiver_local.addr_received)
+ addr_receiver_onion1.wait_until(addr_receiver_onion1.addr_received)
+ addr_receiver_onion2.wait_until(addr_receiver_onion2.addr_received)
+ # new response is different
+ assert(set(last_response_on_local_bind) != set(addr_receiver_local.get_received_addrs()))
+ assert(set(last_response_on_onion_bind1) != set(addr_receiver_onion1.get_received_addrs()))
+ assert(set(last_response_on_onion_bind2) != set(addr_receiver_onion2.get_received_addrs()))
if __name__ == '__main__':
AddrTest().main()
diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py
index 139f4d64e7..28efd5a81e 100755
--- a/test/functional/p2p_invalid_tx.py
+++ b/test/functional/p2p_invalid_tx.py
@@ -60,7 +60,6 @@ class InvalidTxRequestTest(BitcoinTestFramework):
block.solve()
# Save the coinbase for later
block1 = block
- tip = block.sha256
node.p2ps[0].send_blocks_and_test([block], node, success=True)
self.log.info("Mature the block.")
@@ -93,24 +92,24 @@ class InvalidTxRequestTest(BitcoinTestFramework):
SCRIPT_PUB_KEY_OP_TRUE = b'\x51\x75' * 15 + b'\x51'
tx_withhold = CTransaction()
tx_withhold.vin.append(CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0)))
- tx_withhold.vout.append(CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_withhold.vout = [CTxOut(nValue=25 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2
tx_withhold.calc_sha256()
# Our first orphan tx with some outputs to create further orphan txs
tx_orphan_1 = CTransaction()
tx_orphan_1.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0)))
- tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3
+ tx_orphan_1.vout = [CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3
tx_orphan_1.calc_sha256()
# A valid transaction with low fee
tx_orphan_2_no_fee = CTransaction()
tx_orphan_2_no_fee.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0)))
- tx_orphan_2_no_fee.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_2_no_fee.vout.append(CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
# A valid transaction with sufficient fee
tx_orphan_2_valid = CTransaction()
tx_orphan_2_valid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1)))
- tx_orphan_2_valid.vout.append(CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_2_valid.vout.append(CTxOut(nValue=8 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_orphan_2_valid.calc_sha256()
# An invalid transaction with negative fee
@@ -157,6 +156,7 @@ class InvalidTxRequestTest(BitcoinTestFramework):
with node.assert_debug_log(['orphanage overflow, removed 1 tx']):
node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False)
+ self.log.info('Test orphan with rejected parents')
rejected_parent = CTransaction()
rejected_parent.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_2_invalid.sha256, 0)))
rejected_parent.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
@@ -164,6 +164,64 @@ class InvalidTxRequestTest(BitcoinTestFramework):
with node.assert_debug_log(['not keeping orphan with rejected parents {}'.format(rejected_parent.hash)]):
node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False)
+ self.log.info('Test that a peer disconnection causes erase its transactions from the orphan pool')
+ with node.assert_debug_log(['Erased 100 orphan tx from peer=25']):
+ self.reconnect_p2p(num_connections=1)
+
+ self.log.info('Test that a transaction in the orphan pool is included in a new tip block causes erase this transaction from the orphan pool')
+ tx_withhold_until_block_A = CTransaction()
+ tx_withhold_until_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 1)))
+ tx_withhold_until_block_A.vout = [CTxOut(nValue=12 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2
+ tx_withhold_until_block_A.calc_sha256()
+
+ tx_orphan_include_by_block_A = CTransaction()
+ tx_orphan_include_by_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_A.sha256, 0)))
+ tx_orphan_include_by_block_A.vout.append(CTxOut(nValue=12 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_include_by_block_A.calc_sha256()
+
+ self.log.info('Send the orphan ... ')
+ node.p2ps[0].send_txs_and_test([tx_orphan_include_by_block_A], node, success=False)
+
+ tip = int(node.getbestblockhash(), 16)
+ height = node.getblockcount() + 1
+ block_A = create_block(tip, create_coinbase(height))
+ block_A.vtx.extend([tx_withhold, tx_withhold_until_block_A, tx_orphan_include_by_block_A])
+ block_A.hashMerkleRoot = block_A.calc_merkle_root()
+ block_A.solve()
+
+ self.log.info('Send the block that includes the previous orphan ... ')
+ with node.assert_debug_log(["Erased 1 orphan tx included or conflicted by block"]):
+ node.p2ps[0].send_blocks_and_test([block_A], node, success=True)
+
+ self.log.info('Test that a transaction in the orphan pool conflicts with a new tip block causes erase this transaction from the orphan pool')
+ tx_withhold_until_block_B = CTransaction()
+ tx_withhold_until_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_A.sha256, 1)))
+ tx_withhold_until_block_B.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_withhold_until_block_B.calc_sha256()
+
+ tx_orphan_include_by_block_B = CTransaction()
+ tx_orphan_include_by_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_B.sha256, 0)))
+ tx_orphan_include_by_block_B.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_include_by_block_B.calc_sha256()
+
+ tx_orphan_conflict_by_block_B = CTransaction()
+ tx_orphan_conflict_by_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_B.sha256, 0)))
+ tx_orphan_conflict_by_block_B.vout.append(CTxOut(nValue=9 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
+ tx_orphan_conflict_by_block_B.calc_sha256()
+ self.log.info('Send the orphan ... ')
+ node.p2ps[0].send_txs_and_test([tx_orphan_conflict_by_block_B], node, success=False)
+
+ tip = int(node.getbestblockhash(), 16)
+ height = node.getblockcount() + 1
+ block_B = create_block(tip, create_coinbase(height))
+ block_B.vtx.extend([tx_withhold_until_block_B, tx_orphan_include_by_block_B])
+ block_B.hashMerkleRoot = block_B.calc_merkle_root()
+ block_B.solve()
+
+ self.log.info('Send the block that includes a transaction which conflicts with the previous orphan ... ')
+ with node.assert_debug_log(["Erased 1 orphan tx included or conflicted by block"]):
+ node.p2ps[0].send_blocks_and_test([block_B], node, success=True)
+
if __name__ == '__main__':
InvalidTxRequestTest().main()
diff --git a/test/functional/p2p_message_capture.py b/test/functional/p2p_message_capture.py
index 0a7ae44de4..87c77f4540 100755
--- a/test/functional/p2p_message_capture.py
+++ b/test/functional/p2p_message_capture.py
@@ -43,12 +43,8 @@ def mini_parser(dat_file):
break
tmp_header = BytesIO(tmp_header_raw)
tmp_header.read(TIME_SIZE) # skip the timestamp field
- raw_msgtype = tmp_header.read(MSGTYPE_SIZE)
- msgtype: bytes = raw_msgtype.split(b'\x00', 1)[0]
- remainder = raw_msgtype.split(b'\x00', 1)[1]
- assert(len(msgtype) > 0)
+ msgtype = tmp_header.read(MSGTYPE_SIZE).rstrip(b'\x00')
assert(msgtype in MESSAGEMAP)
- assert(len(remainder) == 0 or not remainder.decode().isprintable())
length: int = int.from_bytes(tmp_header.read(LENGTH_SIZE), "little")
data = f_in.read(length)
assert_equal(len(data), length)
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index 1dc3a5b9a0..185011c2df 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -91,6 +91,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
self.nodes[1].assert_start_raises_init_error(["-whitelist=oopsie@127.0.0.1"], "Invalid P2P permission", match=ErrorMatch.PARTIAL_REGEX)
self.nodes[1].assert_start_raises_init_error(["-whitelist=noban@127.0.0.1:230"], "Invalid netmask specified in", match=ErrorMatch.PARTIAL_REGEX)
self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1/10"], "Cannot resolve -whitebind address", match=ErrorMatch.PARTIAL_REGEX)
+ self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1", "-bind=127.0.0.1", "-listen=0"], "Cannot set -bind or -whitebind together with -listen=0", match=ErrorMatch.PARTIAL_REGEX)
def check_tx_relay(self):
block_op_true = self.nodes[0].getblock(self.generatetoaddress(self.nodes[0], 100, ADDRESS_BCRT1_P2WSH_OP_TRUE)[0])
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index 89ddfd3bcf..eedb7e6fa1 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -371,6 +371,10 @@ class SegWitTest(BitcoinTestFramework):
block1 = self.build_next_block()
block1.solve()
+ # Send an empty headers message, to clear out any prior getheaders
+ # messages that our peer may be waiting for us on.
+ self.test_node.send_message(msg_headers())
+
self.test_node.announce_block_and_wait_for_getdata(block1, use_header=False)
assert self.test_node.last_message["getdata"].inv[0].type == blocktype
test_witness_block(self.nodes[0], self.test_node, block1, True)
@@ -1998,7 +2002,7 @@ class SegWitTest(BitcoinTestFramework):
def serialize(self):
return serialize_with_bogus_witness(self.tx)
- tx = self.wallet.create_self_transfer(from_node=self.nodes[0])['tx']
+ tx = self.wallet.create_self_transfer()['tx']
assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, hexstring=serialize_with_bogus_witness(tx).hex(), iswitness=True)
with self.nodes[0].assert_debug_log(['Unknown transaction optional data']):
self.test_node.send_and_ping(msg_bogus_tx(tx))
diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py
index f0abbc7d8b..15a879ae3c 100755
--- a/test/functional/p2p_timeouts.py
+++ b/test/functional/p2p_timeouts.py
@@ -94,6 +94,11 @@ class TimeoutsTest(BitcoinTestFramework):
no_version_node.wait_for_disconnect(timeout=1)
no_send_node.wait_for_disconnect(timeout=1)
+ self.stop_nodes(0)
+ self.nodes[0].assert_start_raises_init_error(
+ expected_msg='Error: peertimeout must be a positive integer.',
+ extra_args=['-peertimeout=0'],
+ )
if __name__ == '__main__':
TimeoutsTest().main()
diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py
index 9c4e1dd1b1..76d9b045ce 100755
--- a/test/functional/p2p_unrequested_blocks.py
+++ b/test/functional/p2p_unrequested_blocks.py
@@ -257,16 +257,11 @@ class AcceptBlockTest(BitcoinTestFramework):
test_node.send_message(msg_block(block_291))
# At this point we've sent an obviously-bogus block, wait for full processing
- # without assuming whether we will be disconnected or not
- try:
- # Only wait a short while so the test doesn't take forever if we do get
- # disconnected
- test_node.sync_with_ping(timeout=1)
- except AssertionError:
- test_node.wait_for_disconnect()
-
- self.nodes[0].disconnect_p2ps()
- test_node = self.nodes[0].add_p2p_connection(P2PInterface())
+ # and assume disconnection
+ test_node.wait_for_disconnect()
+
+ self.nodes[0].disconnect_p2ps()
+ test_node = self.nodes[0].add_p2p_connection(P2PInterface())
# We should have failed reorg and switched back to 290 (but have block 291)
assert_equal(self.nodes[0].getblockcount(), 290)
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 1695acaaa8..1ad3fc24c9 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -43,7 +43,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
if self.is_bdb_compiled():
self.final = node2.getnewaddress()
else:
- self.final = getnewdestination()[2]
+ self.final = getnewdestination('bech32')[2]
def run_test(self):
node0, node1, node2 = self.nodes
@@ -66,9 +66,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
# Test mixed compressed and uncompressed pubkeys
self.log.info('Mixed compressed and uncompressed multisigs are not allowed')
- pk0 = getnewdestination()[0].hex()
- pk1 = getnewdestination()[0].hex()
- pk2 = getnewdestination()[0].hex()
+ pk0, pk1, pk2 = [getnewdestination('bech32')[0].hex() for _ in range(3)]
# decompress pk2
pk_obj = ECPubKey()
@@ -91,15 +89,17 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
assert 'warnings' not in result
# Generate addresses with the segwit types. These should all make legacy addresses
+ err_msg = ["Unable to make chosen address type, please ensure no uncompressed public keys are present."]
+
for addr_type in ['bech32', 'p2sh-segwit']:
- result = self.nodes[0].createmultisig(2, keys, addr_type)
+ result = self.nodes[0].createmultisig(nrequired=2, keys=keys, address_type=addr_type)
assert_equal(legacy_addr, result['address'])
- assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."])
+ assert_equal(result['warnings'], err_msg)
if self.is_bdb_compiled():
- result = wmulti0.addmultisigaddress(2, keys, '', addr_type)
+ result = wmulti0.addmultisigaddress(nrequired=2, keys=keys, address_type=addr_type)
assert_equal(legacy_addr, result['address'])
- assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."])
+ assert_equal(result['warnings'], err_msg)
self.log.info('Testing sortedmulti descriptors with BIP 67 test vectors')
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_bip67.json'), encoding='utf-8') as f:
@@ -173,6 +173,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
desc = descsum_create(desc)
msig = node2.createmultisig(self.nsigs, self.pub, self.output_type)
+ assert 'warnings' not in msig
madd = msig["address"]
mredeem = msig["redeemScript"]
assert_equal(desc, msig['descriptor'])
diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py
index 4ca84748b2..672c9a53dc 100755
--- a/test/functional/rpc_dumptxoutset.py
+++ b/test/functional/rpc_dumptxoutset.py
@@ -49,9 +49,13 @@ class DumptxoutsetTest(BitcoinTestFramework):
out['txoutset_hash'], '1f7e3befd45dc13ae198dfbb22869a9c5c4196f8e9ef9735831af1288033f890')
assert_equal(out['nchaintx'], 101)
- # Specifying a path to an existing file will fail.
+ # Specifying a path to an existing or invalid file will fail.
assert_raises_rpc_error(
-8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME)
+ invalid_path = str(Path(node.datadir) / "invalid" / "path")
+ assert_raises_rpc_error(
+ -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path)
+
if __name__ == '__main__':
DumptxoutsetTest().main()
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 759e43194b..948deaaec4 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -11,6 +11,9 @@ from math import ceil
from test_framework.descriptors import descsum_create
from test_framework.key import ECKey
+from test_framework.messages import (
+ COIN,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
@@ -103,6 +106,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.generate(self.nodes[2], 1)
self.generate(self.nodes[0], 121)
+ self.test_weight_calculation()
self.test_change_position()
self.test_simple()
self.test_simple_two_coins()
@@ -1069,6 +1073,27 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[2].unloadwallet("extfund")
+ def test_weight_calculation(self):
+ self.log.info("Test weight calculation with external inputs")
+
+ self.nodes[2].createwallet("test_weight_calculation")
+ wallet = self.nodes[2].get_wallet_rpc("test_weight_calculation")
+
+ addr = wallet.getnewaddress()
+ txid = self.nodes[0].sendtoaddress(addr, 5)
+ vout = find_vout_for_address(self.nodes[0], txid, addr)
+
+ self.nodes[0].sendtoaddress(wallet.getnewaddress(), 5)
+ self.generate(self.nodes[0], 1)
+
+ rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(): 9.999}])
+ fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10})
+ # with 71-byte signatures we should expect following tx size
+ tx_size = 10 + 41*2 + 31*2 + (2 + 107*2)/4
+ assert_equal(fundedtx['fee'] * COIN, tx_size * 10)
+
+ self.nodes[2].unloadwallet("test_weight_calculation")
+
def test_include_unsafe(self):
self.log.info("Test fundrawtxn with unsafe inputs")
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
index a379132db4..41e430d87e 100755
--- a/test/functional/rpc_getblockfrompeer.py
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -5,6 +5,11 @@
"""Test the getblockfrompeer RPC."""
from test_framework.authproxy import JSONRPCException
+from test_framework.messages import NODE_WITNESS
+from test_framework.p2p import (
+ P2P_SERVICES,
+ P2PInterface,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -61,6 +66,13 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
for peer_id in [-1, peer_0_peer_1_id + 1]:
assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_id)
+ self.log.info("Fetching from pre-segwit peer generates error")
+ self.nodes[0].add_p2p_connection(P2PInterface(), services=P2P_SERVICES & ~NODE_WITNESS)
+ peers = self.nodes[0].getpeerinfo()
+ assert_equal(len(peers), 2)
+ presegwit_peer_id = peers[1]["id"]
+ assert_raises_rpc_error(-1, "Pre-SegWit peer", self.nodes[0].getblockfrompeer, short_tip, presegwit_peer_id)
+
self.log.info("Successful fetch")
result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
self.wait_until(lambda: self.check_for_block(short_tip), timeout=1)
@@ -69,5 +81,6 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
self.log.info("Don't fetch blocks we already have")
assert_raises_rpc_error(-1, "Block already downloaded", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id)
+
if __name__ == '__main__':
GetBlockFromPeerTest().main()
diff --git a/test/functional/rpc_mempool_entry_fee_fields_deprecation.py b/test/functional/rpc_mempool_entry_fee_fields_deprecation.py
deleted file mode 100755
index 82761ff7c8..0000000000
--- a/test/functional/rpc_mempool_entry_fee_fields_deprecation.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 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 deprecation of fee fields from top level mempool entry object"""
-
-from test_framework.blocktools import COIN
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import assert_equal
-from test_framework.wallet import MiniWallet
-
-
-def assertions_helper(new_object, deprecated_object, deprecated_fields):
- for field in deprecated_fields:
- assert field in deprecated_object
- assert field not in new_object
-
-
-class MempoolFeeFieldsDeprecationTest(BitcoinTestFramework):
- def set_test_params(self):
- self.num_nodes = 2
- self.extra_args = [[], ["-deprecatedrpc=fees"]]
-
- def run_test(self):
- # we get spendable outputs from the premined chain starting
- # at block 76. see BitcoinTestFramework._initialize_chain() for details
- self.wallet = MiniWallet(self.nodes[0])
- self.wallet.rescan_utxos()
-
- # we create the tx on the first node and wait until it syncs to node_deprecated
- # thus, any differences must be coming from getmempoolentry or getrawmempool
- tx = self.wallet.send_self_transfer(from_node=self.nodes[0])
- self.nodes[1].sendrawtransaction(tx["hex"])
-
- deprecated_fields = ["ancestorfees", "descendantfees", "modifiedfee", "fee"]
- self.test_getmempoolentry(tx["txid"], deprecated_fields)
- self.test_getrawmempool(tx["txid"], deprecated_fields)
- self.test_deprecated_fields_match(tx["txid"])
-
- def test_getmempoolentry(self, txid, deprecated_fields):
-
- self.log.info("Test getmempoolentry rpc")
- entry = self.nodes[0].getmempoolentry(txid)
- deprecated_entry = self.nodes[1].getmempoolentry(txid)
- assertions_helper(entry, deprecated_entry, deprecated_fields)
-
- def test_getrawmempool(self, txid, deprecated_fields):
-
- self.log.info("Test getrawmempool rpc")
- entry = self.nodes[0].getrawmempool(verbose=True)[txid]
- deprecated_entry = self.nodes[1].getrawmempool(verbose=True)[txid]
- assertions_helper(entry, deprecated_entry, deprecated_fields)
-
- def test_deprecated_fields_match(self, txid):
-
- self.log.info("Test deprecated fee fields match new fees object")
- entry = self.nodes[0].getmempoolentry(txid)
- deprecated_entry = self.nodes[1].getmempoolentry(txid)
-
- assert_equal(deprecated_entry["fee"], entry["fees"]["base"])
- assert_equal(deprecated_entry["modifiedfee"], entry["fees"]["modified"])
- assert_equal(deprecated_entry["descendantfees"], entry["fees"]["descendant"] * COIN)
- assert_equal(deprecated_entry["ancestorfees"], entry["fees"]["ancestor"] * COIN)
-
-
-if __name__ == "__main__":
- MempoolFeeFieldsDeprecationTest().main()
diff --git a/test/functional/rpc_mempool_info.py b/test/functional/rpc_mempool_info.py
new file mode 100755
index 0000000000..9b5a3b9112
--- /dev/null
+++ b/test/functional/rpc_mempool_info.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-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.
+"""Test RPCs that retrieve information from the mempool."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_raises_rpc_error,
+)
+from test_framework.wallet import MiniWallet
+
+
+class RPCMempoolInfoTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.rescan_utxos()
+ confirmed_utxo = self.wallet.get_utxo()
+
+ # Create a tree of unconfirmed transactions in the mempool:
+ # txA
+ # / \
+ # / \
+ # / \
+ # / \
+ # / \
+ # txB txC
+ # / \ / \
+ # / \ / \
+ # txD txE txF txG
+ # \ /
+ # \ /
+ # txH
+
+ def create_tx(**kwargs):
+ return self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ **kwargs,
+ )
+
+ txA = create_tx(utxos_to_spend=[confirmed_utxo], num_outputs=2)
+ txB = create_tx(utxos_to_spend=[txA["new_utxos"][0]], num_outputs=2)
+ txC = create_tx(utxos_to_spend=[txA["new_utxos"][1]], num_outputs=2)
+ txD = create_tx(utxos_to_spend=[txB["new_utxos"][0]], num_outputs=1)
+ txE = create_tx(utxos_to_spend=[txB["new_utxos"][1]], num_outputs=1)
+ txF = create_tx(utxos_to_spend=[txC["new_utxos"][0]], num_outputs=2)
+ txG = create_tx(utxos_to_spend=[txC["new_utxos"][1]], num_outputs=1)
+ txH = create_tx(utxos_to_spend=[txE["new_utxos"][0],txF["new_utxos"][0]], num_outputs=1)
+ txidA, txidB, txidC, txidD, txidE, txidF, txidG, txidH = [
+ tx["txid"] for tx in [txA, txB, txC, txD, txE, txF, txG, txH]
+ ]
+
+ mempool = self.nodes[0].getrawmempool()
+ assert_equal(len(mempool), 8)
+ for txid in [txidA, txidB, txidC, txidD, txidE, txidF, txidG, txidH]:
+ assert_equal(txid in mempool, True)
+
+ self.log.info("Find transactions spending outputs")
+ result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ])
+ assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : txidA}, {'txid' : txidA, 'vout' : 1, 'spendingtxid' : txidC} ])
+
+ self.log.info("Find transaction spending multiple outputs")
+ result = self.nodes[0].gettxspendingprevout([ {'txid' : txidE, 'vout' : 0}, {'txid' : txidF, 'vout' : 0} ])
+ assert_equal(result, [ {'txid' : txidE, 'vout' : 0, 'spendingtxid' : txidH}, {'txid' : txidF, 'vout' : 0, 'spendingtxid' : txidH} ])
+
+ self.log.info("Find no transaction when output is unspent")
+ result = self.nodes[0].gettxspendingprevout([ {'txid' : txidH, 'vout' : 0} ])
+ assert_equal(result, [ {'txid' : txidH, 'vout' : 0} ])
+ result = self.nodes[0].gettxspendingprevout([ {'txid' : txidA, 'vout' : 5} ])
+ assert_equal(result, [ {'txid' : txidA, 'vout' : 5} ])
+
+ self.log.info("Mixed spent and unspent outputs")
+ result = self.nodes[0].gettxspendingprevout([ {'txid' : txidB, 'vout' : 0}, {'txid' : txidG, 'vout' : 3} ])
+ assert_equal(result, [ {'txid' : txidB, 'vout' : 0, 'spendingtxid' : txidD}, {'txid' : txidG, 'vout' : 3} ])
+
+ self.log.info("Unknown input fields")
+ assert_raises_rpc_error(-3, "Unexpected key unknown", self.nodes[0].gettxspendingprevout, [{'txid' : txidC, 'vout' : 1, 'unknown' : 42}])
+
+ self.log.info("Invalid vout provided")
+ assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].gettxspendingprevout, [{'txid' : txidA, 'vout' : -1}])
+
+ self.log.info("Invalid txid provided")
+ assert_raises_rpc_error(-3, "Expected type string for txid, got number", self.nodes[0].gettxspendingprevout, [{'txid' : 42, 'vout' : 0}])
+
+ self.log.info("Missing outputs")
+ assert_raises_rpc_error(-8, "Invalid parameter, outputs are missing", self.nodes[0].gettxspendingprevout, [])
+
+ self.log.info("Missing vout")
+ assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].gettxspendingprevout, [{'txid' : txidA}])
+
+ self.log.info("Missing txid")
+ assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].gettxspendingprevout, [{'vout' : 3}])
+
+
+if __name__ == '__main__':
+ RPCMempoolInfoTest().main()
diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py
index f64aae7223..f6ee6a5215 100755
--- a/test/functional/rpc_misc.py
+++ b/test/functional/rpc_misc.py
@@ -27,7 +27,7 @@ class RpcMiscTest(BitcoinTestFramework):
self.log.info("test CHECK_NONFATAL")
assert_raises_rpc_error(
-1,
- 'Internal bug detected: \'request.params[9].get_str() != "trigger_internal_bug"\'',
+ 'Internal bug detected: "request.params[9].get_str() != "trigger_internal_bug""',
lambda: node.echo(arg9='trigger_internal_bug'),
)
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 81a3cfee97..ad8ba06824 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -257,6 +257,10 @@ class NetTest(BitcoinTestFramework):
assert_equal(node.addpeeraddress(address="", port=8333), {"success": False})
assert_equal(node.getnodeaddresses(count=0), [])
+ self.log.debug("Test that adding an address with invalid port fails")
+ assert_raises_rpc_error(-1, "JSON integer out of range", self.nodes[0].addpeeraddress, address="1.2.3.4", port=-1)
+ assert_raises_rpc_error(-1, "JSON integer out of range", self.nodes[0].addpeeraddress,address="1.2.3.4", port=65536)
+
self.log.debug("Test that adding a valid address to the tried table succeeds")
assert_equal(node.addpeeraddress(address="1.2.3.4", tried=True, port=8333), {"success": True})
with node.assert_debug_log(expected_msgs=["CheckAddrman: new 0, tried 1, total 1 started"]):
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 63533affd0..f687849287 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -15,16 +15,20 @@ from test_framework.messages import (
CTxInWitness,
tx_from_hex,
)
+from test_framework.p2p import P2PTxInvStore
from test_framework.script import (
CScript,
OP_TRUE,
)
from test_framework.util import (
assert_equal,
+ assert_fee_amount,
+ assert_raises_rpc_error,
)
from test_framework.wallet import (
create_child_with_parents,
create_raw_chain,
+ DEFAULT_FEE,
make_chain,
)
@@ -51,7 +55,7 @@ class RPCPackagesTest(BitcoinTestFramework):
self.address = node.get_deterministic_priv_key().address
self.coins = []
# The last 100 coinbase transactions are premature
- for b in self.generatetoaddress(node, 200, self.address)[:100]:
+ for b in self.generatetoaddress(node, 220, self.address)[:-100]:
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
self.coins.append({
"txid": coinbase["txid"],
@@ -82,7 +86,7 @@ class RPCPackagesTest(BitcoinTestFramework):
self.test_multiple_parents()
self.test_conflicting()
self.test_rbf()
-
+ self.test_submitpackage()
def test_independent(self):
self.log.info("Test multiple independent transactions in a package")
@@ -132,8 +136,7 @@ class RPCPackagesTest(BitcoinTestFramework):
def test_chain(self):
node = self.nodes[0]
- first_coin = self.coins.pop()
- (chain_hex, chain_txns) = create_raw_chain(node, first_coin, self.address, self.privkeys)
+ (chain_hex, chain_txns) = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys)
self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency")
assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]),
[{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "package-error": "package-not-sorted"} for tx in chain_txns[::-1]])
@@ -306,5 +309,127 @@ class RPCPackagesTest(BitcoinTestFramework):
}]
self.assert_testres_equal(self.independent_txns_hex + [signed_replacement_tx["hex"]], testres_rbf_package)
+ def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result):
+ """Assert that a successful submitpackage result is consistent with testmempoolaccept
+ results and getmempoolentry info. Note that the result structs are different and, due to
+ policy differences between testmempoolaccept and submitpackage (i.e. package feerate),
+ some information may be different.
+ """
+ for testres_tx in testmempoolaccept_result:
+ # Grab this result from the submitpackage_result
+ submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]]
+ assert_equal(submitres_tx["txid"], testres_tx["txid"])
+ # No "allowed" if the tx was already in the mempool
+ if "allowed" in testres_tx and testres_tx["allowed"]:
+ assert_equal(submitres_tx["vsize"], testres_tx["vsize"])
+ assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"])
+ entry_info = node.getmempoolentry(submitres_tx["txid"])
+ assert_equal(submitres_tx["vsize"], entry_info["vsize"])
+ assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"])
+
+ def test_submit_child_with_parents(self, num_parents, partial_submit):
+ node = self.nodes[0]
+ peer = node.add_p2p_connection(P2PTxInvStore())
+ # Test a package with num_parents parents and 1 child transaction.
+ package_hex = []
+ package_txns = []
+ values = []
+ scripts = []
+ for _ in range(num_parents):
+ parent_coin = self.coins.pop()
+ value = parent_coin["amount"]
+ (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value)
+ package_hex.append(txhex)
+ package_txns.append(tx)
+ values.append(value)
+ scripts.append(spk)
+ if partial_submit and random.choice([True, False]):
+ node.sendrawtransaction(txhex)
+ child_hex = create_child_with_parents(node, self.address, self.privkeys, package_txns, values, scripts)
+ package_hex.append(child_hex)
+ package_txns.append(tx_from_hex(child_hex))
+
+ testmempoolaccept_result = node.testmempoolaccept(rawtxs=package_hex)
+ submitpackage_result = node.submitpackage(package=package_hex)
+
+ # Check that each result is present, with the correct size and fees
+ for i in range(num_parents + 1):
+ tx = package_txns[i]
+ wtxid = tx.getwtxid()
+ assert wtxid in submitpackage_result["tx-results"]
+ tx_result = submitpackage_result["tx-results"][wtxid]
+ assert_equal(tx_result, {
+ "txid": tx.rehash(),
+ "vsize": tx.get_vsize(),
+ "fees": {
+ "base": DEFAULT_FEE,
+ }
+ })
+
+ # submitpackage result should be consistent with testmempoolaccept and getmempoolentry
+ self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result)
+
+ # Package feerate is calculated for the remaining transactions after deduplication and
+ # individual submission. If only 0 or 1 transaction is left, e.g. because all transactions
+ # had high-feerates or were already in the mempool, no package feerate is provided.
+ # In this case, since all of the parents have high fees, each is accepted individually.
+ assert "package-feerate" not in submitpackage_result
+
+ # The node should announce each transaction. No guarantees for propagation.
+ peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
+ self.generate(node, 1)
+
+
+ def test_submit_cpfp(self):
+ node = self.nodes[0]
+ peer = node.add_p2p_connection(P2PTxInvStore())
+
+ # 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is
+ # fee-bumped by the child.
+ coin_rich = self.coins.pop()
+ coin_poor = self.coins.pop()
+ tx_rich, hex_rich, value_rich, spk_rich = make_chain(node, self.address, self.privkeys, coin_rich["txid"], coin_rich["amount"])
+ tx_poor, hex_poor, value_poor, spk_poor = make_chain(node, self.address, self.privkeys, coin_poor["txid"], coin_poor["amount"], fee=0)
+ package_txns = [tx_rich, tx_poor]
+ hex_child = create_child_with_parents(node, self.address, self.privkeys, package_txns, [value_rich, value_poor], [spk_rich, spk_poor])
+ tx_child = tx_from_hex(hex_child)
+ package_txns.append(tx_child)
+
+ submitpackage_result = node.submitpackage([hex_rich, hex_poor, hex_child])
+
+ rich_parent_result = submitpackage_result["tx-results"][tx_rich.getwtxid()]
+ poor_parent_result = submitpackage_result["tx-results"][tx_poor.getwtxid()]
+ child_result = submitpackage_result["tx-results"][tx_child.getwtxid()]
+ assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE)
+ assert_equal(poor_parent_result["fees"]["base"], 0)
+ assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
+ # Package feerate is calculated for the remaining transactions after deduplication and
+ # individual submission. Since this package had a 0-fee parent, package feerate must have
+ # been used and returned.
+ assert "package-feerate" in submitpackage_result
+ assert_fee_amount(DEFAULT_FEE, rich_parent_result["vsize"] + child_result["vsize"], submitpackage_result["package-feerate"])
+
+ # The node will broadcast each transaction, still abiding by its peer's fee filter
+ peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
+ self.generate(node, 1)
+
+
+ def test_submitpackage(self):
+ node = self.nodes[0]
+
+ self.log.info("Submitpackage valid packages with 1 child and some number of parents")
+ for num_parents in [1, 2, 24]:
+ self.test_submit_child_with_parents(num_parents, False)
+ self.test_submit_child_with_parents(num_parents, True)
+
+ self.log.info("Submitpackage valid packages with CPFP")
+ self.test_submit_cpfp()
+
+ self.log.info("Submitpackage only allows packages of 1 child with its parents")
+ # Chain of 3 transactions has too many generations
+ chain_hex, _ = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys, 3)
+ assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex)
+
+
if __name__ == "__main__":
RPCPackagesTest().main()
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index b037807b53..d2a888fd31 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -9,7 +9,11 @@ from decimal import Decimal
from itertools import product
from test_framework.descriptors import descsum_create
-from test_framework.key import ECKey
+from test_framework.key import ECKey, H_POINT
+from test_framework.messages import (
+ ser_compact_size,
+ WITNESS_SCALE_FACTOR,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
@@ -615,8 +619,8 @@ class PSBTTest(BitcoinTestFramework):
self.nodes[1].createwallet("extfund")
wallet = self.nodes[1].get_wallet_rpc("extfund")
- # Make a weird but signable script. sh(pkh()) descriptor accomplishes this
- desc = descsum_create("sh(pkh({}))".format(privkey))
+ # Make a weird but signable script. sh(wsh(pkh())) descriptor accomplishes this
+ desc = descsum_create("sh(wsh(pkh({})))".format(privkey))
if self.options.descriptors:
res = self.nodes[0].importdescriptors([{"desc": desc, "timestamp": "now"}])
else:
@@ -634,7 +638,7 @@ class PSBTTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 15})
# But funding should work when the solving data is provided
- psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}})
+ psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]}})
signed = wallet.walletprocesspsbt(psbt['psbt'])
assert not signed['complete']
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
@@ -655,10 +659,11 @@ class PSBTTest(BitcoinTestFramework):
break
psbt_in = dec["inputs"][input_idx]
# Calculate the input weight
- # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness
+ # (prevout + sequence + length of scriptSig + scriptsig + 1 byte buffer) * WITNESS_SCALE_FACTOR + num scriptWitness stack items + (length of stack item + stack item) * N stack items + 1 byte buffer
len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0
- len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0
- input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness
+ len_scriptsig += len(ser_compact_size(len_scriptsig)) + 1
+ len_scriptwitness = (sum([(len(x) // 2) + len(ser_compact_size(len(x) // 2)) for x in psbt_in["final_scriptwitness"]]) + len(psbt_in["final_scriptwitness"]) + 1) if "final_scriptwitness" in psbt_in else 0
+ input_weight = ((40 + len_scriptsig) * WITNESS_SCALE_FACTOR) + len_scriptwitness
low_input_weight = input_weight // 2
high_input_weight = input_weight * 2
@@ -718,5 +723,46 @@ class PSBTTest(BitcoinTestFramework):
)
assert_equal(psbt2["fee"], psbt3["fee"])
+ self.log.info("Test signing inputs that the wallet has keys for but is not watching the scripts")
+ self.nodes[1].createwallet(wallet_name="scriptwatchonly", disable_private_keys=True)
+ watchonly = self.nodes[1].get_wallet_rpc("scriptwatchonly")
+
+ eckey = ECKey()
+ eckey.generate()
+ privkey = bytes_to_wif(eckey.get_bytes())
+
+ desc = descsum_create("wsh(pkh({}))".format(eckey.get_pubkey().get_bytes().hex()))
+ if self.options.descriptors:
+ res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}])
+ else:
+ res = watchonly.importmulti([{"desc": desc, "timestamp": "now"}])
+ assert res[0]["success"]
+ addr = self.nodes[0].deriveaddresses(desc)[0]
+ self.nodes[0].sendtoaddress(addr, 10)
+ self.generate(self.nodes[0], 1)
+ self.nodes[0].importprivkey(privkey)
+
+ psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"]
+ psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"]
+ self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"])
+
+ # Same test but for taproot
+ if self.options.descriptors:
+ eckey = ECKey()
+ eckey.generate()
+ privkey = bytes_to_wif(eckey.get_bytes())
+
+ desc = descsum_create("tr({},pk({}))".format(H_POINT, eckey.get_pubkey().get_bytes().hex()))
+ res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}])
+ assert res[0]["success"]
+ addr = self.nodes[0].deriveaddresses(desc)[0]
+ self.nodes[0].sendtoaddress(addr, 10)
+ self.generate(self.nodes[0], 1)
+ self.nodes[0].importdescriptors([{"desc": descsum_create("tr({})".format(privkey)), "timestamp":"now"}])
+
+ psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"]
+ psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"]
+ self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"])
+
if __name__ == '__main__':
PSBTTest().main()
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index a839af0288..26a5da85b7 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -17,6 +17,7 @@ from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.messages import (
+ BIP125_SEQUENCE_NUMBER,
CTransaction,
tx_from_hex,
)
@@ -24,7 +25,10 @@ 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.wallet import (
+ getnewdestination,
+ MiniWallet,
)
@@ -52,79 +56,67 @@ class multidict(dict):
class RawTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
- self.num_nodes = 4
+ self.num_nodes = 3
self.extra_args = [
["-txindex"],
["-txindex"],
- ["-txindex"],
[],
]
# whitelist all peers to speed up tx relay / mempool sync
for args in self.extra_args:
args.append("-whitelist=noban@127.0.0.1")
+ self.requires_wallet = self.is_specified_wallet_compiled()
self.supports_cli = False
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
def setup_network(self):
super().setup_network()
self.connect_nodes(0, 2)
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
self.log.info("Prepare some coins for multiple *rawtransaction commands")
- self.generate(self.nodes[2], 1)
+ self.generate(self.wallet, 10)
self.generate(self.nodes[0], COINBASE_MATURITY + 1)
- for amount in [1.5, 1.0, 5.0]:
- self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), amount)
- self.sync_all()
- self.generate(self.nodes[0], 5)
self.getrawtransaction_tests()
self.createrawtransaction_tests()
- self.signrawtransactionwithwallet_tests()
self.sendrawtransaction_tests()
self.sendrawtransaction_testmempoolaccept_tests()
self.decoderawtransaction_tests()
self.transaction_version_number_tests()
- if not self.options.descriptors:
+ if self.requires_wallet and not self.options.descriptors:
self.raw_multisig_transaction_legacy_tests()
def getrawtransaction_tests(self):
- addr = self.nodes[1].getnewaddress()
- txid = self.nodes[0].sendtoaddress(addr, 10)
+ tx = self.wallet.send_self_transfer(from_node=self.nodes[0])
self.generate(self.nodes[0], 1)
- vout = find_vout_for_address(self.nodes[1], txid, addr)
- rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999})
- rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx)
- txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex'])
- self.generateblock(self.nodes[0], output=self.nodes[0].getnewaddress(), transactions=[rawTxSigned['hex']])
+ txId = tx['txid']
err_msg = (
"No such mempool transaction. Use -txindex or provide a block hash to enable"
" blockchain transaction queries. Use gettransaction for wallet transactions."
)
- for n in [0, 3]:
+ for n in [0, 2]:
self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex")
if n == 0:
# With -txindex.
# 1. valid parameters - only supply txid
- assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex'])
+ assert_equal(self.nodes[n].getrawtransaction(txId), tx['hex'])
# 2. valid parameters - supply txid and 0 for non-verbose
- assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex'])
+ assert_equal(self.nodes[n].getrawtransaction(txId, 0), tx['hex'])
# 3. valid parameters - supply txid and False for non-verbose
- assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex'])
+ assert_equal(self.nodes[n].getrawtransaction(txId, False), tx['hex'])
# 4. valid parameters - supply txid and 1 for verbose.
# We only check the "hex" field of the output so we don't need to update this test every time the output format changes.
- assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex'])
+ assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], tx['hex'])
# 5. valid parameters - supply txid and True for non-verbose
- assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex'])
+ assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], tx['hex'])
else:
# Without -txindex, expect to raise.
for verbose in [None, 0, False, 1, True]:
@@ -141,9 +133,9 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txId, {})
# Make a tx by sending, then generate 2 blocks; block1 has the tx in it
- tx = self.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), 1)
+ tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid']
block1, block2 = self.generate(self.nodes[2], 2)
- for n in [0, 3]:
+ for n in [0, 2]:
self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex, with blockhash")
# We should be able to get the raw transaction by providing the correct block
gottx = self.nodes[n].getrawtransaction(txid=tx, verbose=True, blockhash=block1)
@@ -200,20 +192,21 @@ class RawTransactionsTest(BitcoinTestFramework):
# sequence number out of range
for invalid_seq in [-1, 4294967296]:
inputs = [{'txid': TXID, 'vout': 1, 'sequence': invalid_seq}]
- outputs = {self.nodes[0].getnewaddress(): 1}
+ address = getnewdestination()[2]
+ outputs = {address: 1}
assert_raises_rpc_error(-8, 'Invalid parameter, sequence number is out of range',
self.nodes[0].createrawtransaction, inputs, outputs)
# with valid sequence number
for valid_seq in [1000, 4294967294]:
inputs = [{'txid': TXID, 'vout': 1, 'sequence': valid_seq}]
- outputs = {self.nodes[0].getnewaddress(): 1}
+ address = getnewdestination()[2]
+ outputs = {address: 1}
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['vin'][0]['sequence'], valid_seq)
# Test `createrawtransaction` invalid `outputs`
- address = self.nodes[0].getnewaddress()
- address2 = self.nodes[0].getnewaddress()
+ address = getnewdestination()[2]
assert_raises_rpc_error(-1, "JSON value is not an array as expected", self.nodes[0].createrawtransaction, [], 'foo')
self.nodes[0].createrawtransaction(inputs=[], outputs={}) # Should not throw for backwards compatibility
self.nodes[0].createrawtransaction(inputs=[], outputs=[])
@@ -228,6 +221,10 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Invalid parameter, key-value pair must contain exactly one key", self.nodes[0].createrawtransaction, [], [{'a': 1, 'b': 2}])
assert_raises_rpc_error(-8, "Invalid parameter, key-value pair not an object as expected", self.nodes[0].createrawtransaction, [], [['key-value pair1'], ['2']])
+ # Test `createrawtransaction` mismatch between sequence number(s) and `replaceable` option
+ assert_raises_rpc_error(-8, "Invalid parameter combination: Sequence number(s) contradict replaceable option",
+ self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 0, 'sequence': BIP125_SEQUENCE_NUMBER+1}], {}, 0, True)
+
# Test `createrawtransaction` invalid `locktime`
assert_raises_rpc_error(-3, "Expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo')
assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1)
@@ -245,6 +242,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}]),
)
# Two outputs
+ address2 = getnewdestination()[2]
tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)])))
assert_equal(len(tx.vout), 2)
assert_equal(
@@ -259,122 +257,53 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}, {address2: 99}, {'data': '99'}]),
)
- def signrawtransactionwithwallet_tests(self):
- for type in ["bech32", "p2sh-segwit", "legacy"]:
- self.log.info(f"Test signrawtransactionwithwallet with missing prevtx info ({type})")
- addr = self.nodes[0].getnewaddress("", type)
- addrinfo = self.nodes[0].getaddressinfo(addr)
- pubkey = addrinfo["scriptPubKey"]
- inputs = [{'txid': TXID, 'vout': 3, 'sequence': 1000}]
- outputs = {self.nodes[0].getnewaddress(): 1}
- rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
-
- prevtx = dict(txid=TXID, scriptPubKey=pubkey, vout=3, amount=1)
- succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx])
- assert succ["complete"]
-
- if type == "legacy":
- del prevtx["amount"]
- succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx])
- assert succ["complete"]
- else:
- assert_raises_rpc_error(-3, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [
- {
- "txid": TXID,
- "scriptPubKey": pubkey,
- "vout": 3,
- }
- ])
-
- assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [
- {
- "txid": TXID,
- "scriptPubKey": pubkey,
- "amount": 1,
- }
- ])
- assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].signrawtransactionwithwallet, rawtx, [
- {
- "scriptPubKey": pubkey,
- "vout": 3,
- "amount": 1,
- }
- ])
- assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [
- {
- "txid": TXID,
- "vout": 3,
- "amount": 1
- }
- ])
-
def sendrawtransaction_tests(self):
self.log.info("Test sendrawtransaction with missing input")
inputs = [{'txid': TXID, 'vout': 1}] # won't exist
- outputs = {self.nodes[0].getnewaddress(): 4.998}
+ address = getnewdestination()[2]
+ outputs = {address: 4.998}
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
- rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx)
- assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx['hex'])
+ assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx)
def sendrawtransaction_testmempoolaccept_tests(self):
self.log.info("Test sendrawtransaction/testmempoolaccept with maxfeerate")
fee_exceeds_max = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"
# Test a transaction with a small fee.
- txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
- rawTx = self.nodes[0].getrawtransaction(txId, True)
- vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1.00000000'))
-
- self.sync_all()
- inputs = [{"txid": txId, "vout": vout['n']}]
- # Fee 10,000 satoshis, (1 - (10000 sat * 0.00000001 BTC/sat)) = 0.9999
- outputs = {self.nodes[0].getnewaddress(): Decimal("0.99990000")}
- rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
- rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx)
- assert_equal(rawTxSigned['complete'], True)
- # Fee 10,000 satoshis, ~100 b transaction, fee rate should land around 100 sat/byte = 0.00100000 BTC/kB
+ # Fee rate is 0.00100000 BTC/kvB
+ tx = self.wallet.create_self_transfer(fee_rate=Decimal('0.00100000'))
# Thus, testmempoolaccept should reject
- testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']], 0.00001000)[0]
+ testres = self.nodes[2].testmempoolaccept([tx['hex']], 0.00001000)[0]
assert_equal(testres['allowed'], False)
assert_equal(testres['reject-reason'], 'max-fee-exceeded')
# and sendrawtransaction should throw
- assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000)
+ assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, tx['hex'], 0.00001000)
# and the following calls should both succeed
- testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']])[0]
+ testres = self.nodes[2].testmempoolaccept(rawtxs=[tx['hex']])[0]
assert_equal(testres['allowed'], True)
- self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'])
+ self.nodes[2].sendrawtransaction(hexstring=tx['hex'])
# Test a transaction with a large fee.
- txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
- rawTx = self.nodes[0].getrawtransaction(txId, True)
- vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1.00000000'))
-
- self.sync_all()
- inputs = [{"txid": txId, "vout": vout['n']}]
- # Fee 2,000,000 satoshis, (1 - (2000000 sat * 0.00000001 BTC/sat)) = 0.98
- outputs = {self.nodes[0].getnewaddress() : Decimal("0.98000000")}
- rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
- rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx)
- assert_equal(rawTxSigned['complete'], True)
- # Fee 2,000,000 satoshis, ~100 b transaction, fee rate should land around 20,000 sat/byte = 0.20000000 BTC/kB
+ # Fee rate is 0.20000000 BTC/kvB
+ tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.20000000"))
# Thus, testmempoolaccept should reject
- testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']])[0]
+ testres = self.nodes[2].testmempoolaccept([tx['hex']])[0]
assert_equal(testres['allowed'], False)
assert_equal(testres['reject-reason'], 'max-fee-exceeded')
# and sendrawtransaction should throw
- assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, rawTxSigned['hex'])
+ assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, tx['hex'])
# and the following calls should both succeed
- testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate='0.20000000')[0]
+ testres = self.nodes[2].testmempoolaccept(rawtxs=[tx['hex']], maxfeerate='0.20000000')[0]
assert_equal(testres['allowed'], True)
- self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate='0.20000000')
+ self.nodes[2].sendrawtransaction(hexstring=tx['hex'], maxfeerate='0.20000000')
self.log.info("Test sendrawtransaction/testmempoolaccept with tx already in the chain")
self.generate(self.nodes[2], 1)
for node in self.nodes:
- testres = node.testmempoolaccept([rawTxSigned['hex']])[0]
+ 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, rawTxSigned['hex'])
+ assert_raises_rpc_error(-27, 'Transaction already in block chain', node.sendrawtransaction, tx['hex'])
def decoderawtransaction_tests(self):
self.log.info("Test decoderawtransaction")
diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py
new file mode 100755
index 0000000000..0da5a99fdb
--- /dev/null
+++ b/test/functional/rpc_signrawtransactionwithkey.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-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.
+"""Test transaction signing using the signrawtransactionwithkey RPC."""
+
+from test_framework.blocktools import (
+ COINBASE_MATURITY,
+)
+from test_framework.address import (
+ script_to_p2sh,
+)
+from test_framework.key import ECKey
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ find_vout_for_address,
+)
+from test_framework.script_util import (
+ key_to_p2pk_script,
+ key_to_p2pkh_script,
+ script_to_p2sh_p2wsh_script,
+ script_to_p2wsh_script,
+)
+from test_framework.wallet_util import (
+ bytes_to_wif,
+)
+
+from decimal import (
+ Decimal,
+)
+from test_framework.wallet import (
+ getnewdestination,
+)
+
+
+class SignRawTransactionWithKeyTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+
+ 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
+
+ def successful_signing_test(self):
+ """Create and sign a valid raw transaction with one input.
+
+ Expected results:
+
+ 1) The transaction has a complete set of signatures
+ 2) No script verification error occurred"""
+ self.log.info("Test valid raw transaction with one input")
+ privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA']
+
+ inputs = [
+ # Valid pay-to-pubkey scripts
+ {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0,
+ 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'},
+ {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0,
+ 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'},
+ ]
+
+ outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
+
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, inputs)
+
+ # 1) The transaction has a complete set of signatures
+ assert rawTxSigned['complete']
+
+ # 2) No script verification error occurred
+ assert 'errors' not in rawTxSigned
+
+ def witness_script_test(self):
+ self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet")
+ # Create a new P2SH-P2WSH 1-of-1 multisig address:
+ eckey = ECKey()
+ eckey.generate()
+ embedded_privkey = bytes_to_wif(eckey.get_bytes())
+ embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
+ p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit")
+ # send transaction to P2SH-P2WSH 1-of-1 multisig address
+ self.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]
+ spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex()
+ unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript']
+ unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex()
+ assert_equal(spk, unspent_output['scriptPubKey'])
+ # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
+ spending_tx = self.nodes[0].createrawtransaction([unspent_output], {getnewdestination()[2]: Decimal("49.998")})
+ spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output])
+ # Check the signing completed successfully
+ assert 'complete' in spending_tx_signed
+ assert_equal(spending_tx_signed['complete'], True)
+
+ # Now test with P2PKH and P2PK scripts as the witnessScript
+ for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent
+ self.verify_txn_with_witness_script(tx_type)
+
+ def verify_txn_with_witness_script(self, tx_type):
+ self.log.info("Test with a {} script as the witnessScript".format(tx_type))
+ eckey = ECKey()
+ eckey.generate()
+ embedded_privkey = bytes_to_wif(eckey.get_bytes())
+ embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
+ witness_script = {
+ 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(),
+ 'P2PK': key_to_p2pk_script(embedded_pubkey).hex()
+ }.get(tx_type, "Invalid tx_type")
+ redeem_script = script_to_p2wsh_script(witness_script).hex()
+ addr = script_to_p2sh(redeem_script)
+ script_pub_key = self.nodes[1].validateaddress(addr)['scriptPubKey']
+ # 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)
+ # 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}])
+ # Check the signing completed successfully
+ assert 'complete' in spending_tx_signed
+ assert_equal(spending_tx_signed['complete'], True)
+ self.nodes[0].sendrawtransaction(spending_tx_signed['hex'])
+
+ def run_test(self):
+ self.successful_signing_test()
+ self.witness_script_test()
+
+
+if __name__ == '__main__':
+ SignRawTransactionWithKeyTest().main()
diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py
index 7cedb4336b..1a35a57802 100755
--- a/test/functional/rpc_users.py
+++ b/test/functional/rpc_users.py
@@ -107,6 +107,9 @@ class HTTPBasicsTest(BitcoinTestFramework):
self.stop_node(0)
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo'])
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar'])
+ self.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 that failure to write cookie file will abort the node gracefully')
cookie_file = os.path.join(get_datadir_path(self.options.tmpdir, 0), self.chain, '.cookie.tmp')
diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py
index fcea24655b..92244b5ed8 100644
--- a/test/functional/test_framework/address.py
+++ b/test/functional/test_framework/address.py
@@ -47,8 +47,7 @@ def create_deterministic_address_bcrt1_p2tr_op_true():
Returns a tuple with the generated address and the internal key.
"""
internal_key = (1).to_bytes(32, 'big')
- scriptPubKey = taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).scriptPubKey
- address = encode_segwit_address("bcrt", 1, scriptPubKey[2:])
+ address = output_key_to_p2tr(taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).output_pubkey)
assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka')
return (address, internal_key)
@@ -141,6 +140,10 @@ def script_to_p2sh_p2wsh(script, main=False):
p2shscript = CScript([OP_0, sha256(script)])
return script_to_p2sh(p2shscript, main)
+def output_key_to_p2tr(key, main=False):
+ assert len(key) == 32
+ return program_to_witness(1, key, 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 40fcbf7761..574ea10356 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -162,30 +162,6 @@ def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=C
tx.calc_sha256()
return tx
-def create_transaction(node, txid, to_address, *, amount):
- """ Return signed transaction spending the first output of the
- input txid. Note that the node must have a wallet that can
- sign for the output that is being spent.
- """
- raw_tx = create_raw_transaction(node, txid, to_address, amount=amount)
- tx = tx_from_hex(raw_tx)
- return tx
-
-def create_raw_transaction(node, txid, to_address, *, amount):
- """ Return raw signed transaction spending the first output of the
- input txid. Note that the node must have a wallet that can sign
- for the output that is being spent.
- """
- psbt = node.createpsbt(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount})
- for _ in range(2):
- for w in node.listwallets():
- wrpc = node.get_wallet_rpc(w)
- signed_psbt = wrpc.walletprocesspsbt(psbt)
- psbt = signed_psbt['psbt']
- final_psbt = node.finalizepsbt(psbt)
- assert_equal(final_psbt["complete"], True)
- return final_psbt['hex']
-
def get_legacy_sigopcount_block(block, accurate=True):
count = 0
for tx in block.vtx:
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
index e5dea66963..68afc1383d 100644
--- a/test/functional/test_framework/key.py
+++ b/test/functional/test_framework/key.py
@@ -15,6 +15,10 @@ import unittest
from .util import modinv
+# Point with no known discrete log.
+H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
+
+
def TaggedHash(tag, data):
ss = hashlib.sha256(tag.encode('utf-8')).digest()
ss += ss
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index f57b6e7494..aae44c0ac0 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -1672,7 +1672,7 @@ class msg_getcfilters:
__slots__ = ("filter_type", "start_height", "stop_hash")
msgtype = b"getcfilters"
- def __init__(self, filter_type, start_height, stop_hash):
+ def __init__(self, filter_type=None, start_height=None, stop_hash=None):
self.filter_type = filter_type
self.start_height = start_height
self.stop_hash = stop_hash
@@ -1722,7 +1722,7 @@ class msg_getcfheaders:
__slots__ = ("filter_type", "start_height", "stop_hash")
msgtype = b"getcfheaders"
- def __init__(self, filter_type, start_height, stop_hash):
+ def __init__(self, filter_type=None, start_height=None, stop_hash=None):
self.filter_type = filter_type
self.start_height = start_height
self.stop_hash = stop_hash
@@ -1775,7 +1775,7 @@ class msg_getcfcheckpt:
__slots__ = ("filter_type", "stop_hash")
msgtype = b"getcfcheckpt"
- def __init__(self, filter_type, stop_hash):
+ def __init__(self, filter_type=None, stop_hash=None):
self.filter_type = filter_type
self.stop_hash = stop_hash
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index 251d3d5eae..fc72a9ab73 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -47,6 +47,9 @@ from test_framework.messages import (
msg_getaddr,
msg_getblocks,
msg_getblocktxn,
+ msg_getcfcheckpt,
+ msg_getcfheaders,
+ msg_getcfilters,
msg_getdata,
msg_getheaders,
msg_headers,
@@ -108,6 +111,9 @@ MESSAGEMAP = {
b"getaddr": msg_getaddr,
b"getblocks": msg_getblocks,
b"getblocktxn": msg_getblocktxn,
+ b"getcfcheckpt": msg_getcfcheckpt,
+ b"getcfheaders": msg_getcfheaders,
+ b"getcfilters": msg_getcfilters,
b"getdata": msg_getdata,
b"getheaders": msg_getheaders,
b"headers": msg_headers,
diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py
index f7d8422eee..b114002145 100755
--- a/test/functional/test_framework/script_util.py
+++ b/test/functional/test_framework/script_util.py
@@ -105,6 +105,11 @@ def script_to_p2sh_p2wsh_script(script):
return script_to_p2sh_script(p2shscript)
+def output_key_to_p2tr_script(key):
+ assert len(key) == 32
+ return program_to_witness_script(1, key)
+
+
def check_key(key):
if isinstance(key, str):
key = bytes.fromhex(key) # Assuming this is hex string
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index a39ee003ef..c880aabd21 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -223,11 +223,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# It still needs to exist and be None in order for tests to work however.
self.options.descriptors = None
+ PortSeed.n = self.options.port_seed
+
def setup(self):
"""Call this method to start up the test framework object with options set."""
- PortSeed.n = self.options.port_seed
-
check_json_precision()
self.options.cachedir = os.path.abspath(self.options.cachedir)
@@ -581,6 +581,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def connect_nodes(self, a, b):
from_connection = self.nodes[a]
to_connection = self.nodes[b]
+ from_num_peers = 1 + len(from_connection.getpeerinfo())
+ to_num_peers = 1 + len(to_connection.getpeerinfo())
ip_port = "127.0.0.1:" + str(p2p_port(b))
from_connection.addnode(ip_port, "onetry")
# poll until version handshake complete to avoid race conditions
@@ -588,10 +590,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# See comments in net_processing:
# * Must have a version message before anything else
# * Must have a verack message before anything else
- wait_until_helper(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
- wait_until_helper(lambda: all(peer['version'] != 0 for peer in to_connection.getpeerinfo()))
- wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()))
- wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo()))
+ self.wait_until(lambda: sum(peer['version'] != 0 for peer in from_connection.getpeerinfo()) == from_num_peers)
+ self.wait_until(lambda: sum(peer['version'] != 0 for peer in to_connection.getpeerinfo()) == to_num_peers)
+ self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()) == from_num_peers)
+ self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo()) == to_num_peers)
def disconnect_nodes(self, a, b):
def disconnect_nodes_helper(from_connection, node_num):
@@ -620,7 +622,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
raise
# wait to disconnect
- wait_until_helper(lambda: not get_peer_ids(), timeout=5)
+ self.wait_until(lambda: not get_peer_ids(), timeout=5)
disconnect_nodes_helper(self.nodes[a], b)
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index e56d4aa492..03f6c8adea 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -423,7 +423,7 @@ class TestNode():
self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log))
@contextlib.contextmanager
- def wait_for_debug_log(self, expected_msgs, timeout=60, ignore_case=False):
+ def wait_for_debug_log(self, expected_msgs, timeout=60):
"""
Block until we see a particular debug log message fragment or until we exceed the timeout.
Return:
@@ -431,18 +431,17 @@ class TestNode():
"""
time_end = time.time() + timeout * self.timeout_factor
prev_size = self.debug_log_bytes()
- re_flags = re.MULTILINE | (re.IGNORECASE if ignore_case else 0)
yield
while True:
found = True
- with open(self.debug_log_path, encoding='utf-8') as dl:
+ with open(self.debug_log_path, "rb") as dl:
dl.seek(prev_size)
log = dl.read()
for expected_msg in expected_msgs:
- if re.search(re.escape(expected_msg), log, flags=re_flags) is None:
+ if expected_msg not in log:
found = False
if found:
@@ -545,6 +544,7 @@ class TestNode():
Will throw if bitcoind starts without an error.
Will throw if an expected_msg is provided and it does not match bitcoind's stdout."""
+ assert not self.running
with tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) as log_stderr, \
tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) as log_stdout:
try:
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 8651bcf636..58528b7858 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -378,6 +378,7 @@ def write_config(config_path, *, n, chain, extra_config="", disable_autoconnect=
f.write("[{}]\n".format(chain_name_conf_section))
f.write("port=" + str(p2p_port(n)) + "\n")
f.write("rpcport=" + str(rpc_port(n)) + "\n")
+ f.write("rpcdoccheck=1\n")
f.write("fallbackfee=0.0002\n")
f.write("server=1\n")
f.write("keypool=1\n")
@@ -475,39 +476,6 @@ def find_output(node, txid, amount, *, blockhash=None):
raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount)))
-# Helper to create at least "count" utxos
-# Pass in a fee that is sufficient for relay and mining new transactions.
-def create_confirmed_utxos(test_framework, fee, node, count, **kwargs):
- to_generate = int(0.5 * count) + 101
- while to_generate > 0:
- test_framework.generate(node, min(25, to_generate), **kwargs)
- to_generate -= 25
- utxos = node.listunspent()
- iterations = count - len(utxos)
- addr1 = node.getnewaddress()
- addr2 = node.getnewaddress()
- if iterations <= 0:
- return utxos
- for _ in range(iterations):
- t = utxos.pop()
- inputs = []
- inputs.append({"txid": t["txid"], "vout": t["vout"]})
- outputs = {}
- send_value = t['amount'] - fee
- outputs[addr1] = satoshi_round(send_value / 2)
- outputs[addr2] = satoshi_round(send_value / 2)
- raw_tx = node.createrawtransaction(inputs, outputs)
- signed_tx = node.signrawtransactionwithwallet(raw_tx)["hex"]
- node.sendrawtransaction(signed_tx)
-
- while (node.getmempoolinfo()['size'] > 0):
- test_framework.generate(node, 1, **kwargs)
-
- utxos = node.listunspent()
- assert len(utxos) >= count
- return utxos
-
-
def chain_transaction(node, parent_txids, vouts, value, fee, num_outputs):
"""Build and send a transaction that spends the given inputs (specified
by lists of parent_txid:vout each), with the desired total value and fee,
@@ -531,45 +499,30 @@ def chain_transaction(node, parent_txids, vouts, value, fee, num_outputs):
# Create large OP_RETURN txouts that can be appended to a transaction
-# to make it large (helper for constructing large transactions).
+# to make it large (helper for constructing large transactions). The
+# total serialized size of the txouts is about 66k vbytes.
def gen_return_txouts():
- # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
- # So we have big transactions (and therefore can't fit very many into each block)
- # create one script_pubkey
- script_pubkey = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
- for _ in range(512):
- script_pubkey = script_pubkey + "01"
- # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
- txouts = []
from .messages import CTxOut
- txout = CTxOut()
- txout.nValue = 0
- txout.scriptPubKey = bytes.fromhex(script_pubkey)
- for _ in range(128):
- txouts.append(txout)
+ from .script import CScript, OP_RETURN
+ txouts = [CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, b'\x01'*67437]))]
+ assert_equal(sum([len(txout.serialize()) for txout in txouts]), 67456)
return txouts
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
# transaction to make it large. See gen_return_txouts() above.
-def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
- addr = node.getnewaddress()
+def create_lots_of_big_transactions(mini_wallet, node, fee, tx_batch_size, txouts, utxos=None):
txids = []
- from .messages import tx_from_hex
- for _ in range(num):
- t = utxos.pop()
- inputs = [{"txid": t["txid"], "vout": t["vout"]}]
- outputs = {}
- change = t['amount'] - fee
- outputs[addr] = satoshi_round(change)
- rawtx = node.createrawtransaction(inputs, outputs)
- tx = tx_from_hex(rawtx)
- for txout in txouts:
- tx.vout.append(txout)
- newtx = tx.serialize().hex()
- signresult = node.signrawtransactionwithwallet(newtx, None, "NONE")
- txid = node.sendrawtransaction(signresult["hex"], 0)
- txids.append(txid)
+ use_internal_utxos = utxos is None
+ for _ in range(tx_batch_size):
+ tx = mini_wallet.create_self_transfer(
+ utxo_to_spend=None if use_internal_utxos else utxos.pop(),
+ fee=fee,
+ )["tx"]
+ tx.vout.extend(txouts)
+ res = node.testmempoolaccept([tx.serialize().hex()])[0]
+ assert_equal(res['fees']['base'], fee)
+ txids.append(node.sendrawtransaction(tx.serialize().hex()))
return txids
@@ -577,13 +530,8 @@ def mine_large_block(test_framework, mini_wallet, node):
# generate a 66k transaction,
# and 14 of them is close to the 1MB block limit
txouts = gen_return_txouts()
- from .messages import COIN
- fee = 100 * int(node.getnetworkinfo()["relayfee"] * COIN)
- for _ in range(14):
- tx = mini_wallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx']
- tx.vout[0].nValue -= fee
- tx.vout.extend(txouts)
- mini_wallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex())
+ fee = 100 * node.getnetworkinfo()["relayfee"]
+ create_lots_of_big_transactions(mini_wallet, node, fee, 14, txouts)
test_framework.generate(node, 1)
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index e86f365f11..2164627781 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -10,6 +10,7 @@ from enum import Enum
from random import choice
from typing import (
Any,
+ List,
Optional,
)
from test_framework.address import (
@@ -18,9 +19,13 @@ from test_framework.address import (
key_to_p2pkh,
key_to_p2sh_p2wpkh,
key_to_p2wpkh,
+ output_key_to_p2tr,
)
from test_framework.descriptors import descsum_create
-from test_framework.key import ECKey
+from test_framework.key import (
+ ECKey,
+ compute_xonly_pubkey,
+)
from test_framework.messages import (
COIN,
COutPoint,
@@ -37,6 +42,7 @@ from test_framework.script import (
OP_NOP,
OP_TRUE,
SIGHASH_ALL,
+ taproot_construct,
)
from test_framework.script_util import (
key_to_p2pk_script,
@@ -80,8 +86,7 @@ class MiniWallet:
def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE):
self._test_node = test_node
self._utxos = []
- self._priv_key = None
- self._address = None
+ self._mode = mode
assert isinstance(mode, MiniWalletMode)
if mode == MiniWalletMode.RAW_OP_TRUE:
@@ -96,6 +101,9 @@ class MiniWallet:
self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true()
self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey'])
+ def _create_utxo(self, *, txid, vout, value, height):
+ return {"txid": txid, "vout": vout, "value": value, "height": height}
+
def get_balance(self):
return sum(u['value'] for u in self._utxos)
@@ -105,17 +113,26 @@ class MiniWallet:
res = self._test_node.scantxoutset(action="start", scanobjects=[self.get_descriptor()])
assert_equal(True, res['success'])
for utxo in res['unspents']:
- self._utxos.append({'txid': utxo['txid'], 'vout': utxo['vout'], 'value': utxo['amount'], 'height': utxo['height']})
+ self._utxos.append(self._create_utxo(txid=utxo["txid"], vout=utxo["vout"], value=utxo["amount"], height=utxo["height"]))
def scan_tx(self, tx):
- """Scan the tx for self._scriptPubKey outputs and add them to self._utxos"""
+ """Scan the tx and adjust the internal list of owned utxos"""
+ for spent in tx["vin"]:
+ # Mark spent. This may happen when the caller has ownership of a
+ # utxo that remained in this wallet. For example, by passing
+ # mark_as_spent=False to get_utxo or by using an utxo returned by a
+ # create_self_transfer* call.
+ try:
+ self.get_utxo(txid=spent["txid"], vout=spent["vout"])
+ except StopIteration:
+ pass
for out in tx['vout']:
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
- self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value'], 'height': 0})
+ self._utxos.append(self._create_utxo(txid=tx["txid"], vout=out["n"], value=out["value"], height=0))
def sign_tx(self, tx, fixed_length=True):
"""Sign tx that has been created by MiniWallet in P2PK mode"""
- assert self._priv_key is not None
+ assert_equal(self._mode, MiniWalletMode.RAW_P2PK)
(sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL)
assert err is None
# for exact fee calculation, create only signatures with fixed size by default (>49.89% probability):
@@ -127,14 +144,19 @@ class MiniWallet:
if not fixed_length:
break
tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))])
+ tx.rehash()
def generate(self, num_blocks, **kwargs):
- """Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list"""
+ """Generate blocks with coinbase outputs to the internal address, and call rescan_utxos"""
blocks = self._test_node.generatetodescriptor(num_blocks, self.get_descriptor(), **kwargs)
- for b in blocks:
- block_info = self._test_node.getblock(blockhash=b, verbosity=2)
- cb_tx = block_info['tx'][0]
- self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value'], 'height': block_info['height']})
+ # Calling rescan_utxos here makes sure that after a generate the utxo
+ # set is in a clean state. For example, the wallet will update
+ # - if the caller consumed utxos, but never used them
+ # - if the caller sent a transaction that is not mined or got rbf'd
+ # - after block re-orgs
+ # - the utxo height for mined mempool txs
+ # - However, the wallet will not consider remaining mempool txs
+ self.rescan_utxos()
return blocks
def get_scriptPubKey(self):
@@ -144,9 +166,10 @@ class MiniWallet:
return descsum_create(f'raw({self._scriptPubKey.hex()})')
def get_address(self):
+ assert_equal(self._mode, MiniWalletMode.ADDRESS_OP_TRUE)
return self._address
- def get_utxo(self, *, txid: str = '', vout: Optional[int] = None, mark_as_spent=True):
+ def get_utxo(self, *, txid: str = '', vout: Optional[int] = None, mark_as_spent=True) -> dict:
"""
Returns a utxo and marks it as spent (pops it from the internal list)
@@ -166,10 +189,17 @@ class MiniWallet:
else:
return self._utxos[index]
- def send_self_transfer(self, **kwargs):
- """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
+ def get_utxos(self, *, mark_as_spent=True):
+ """Returns the list of all utxos and optionally mark them as spent"""
+ utxos = deepcopy(self._utxos)
+ if mark_as_spent:
+ self._utxos = []
+ return utxos
+
+ def send_self_transfer(self, *, from_node, **kwargs):
+ """Call create_self_transfer and send the transaction."""
tx = self.create_self_transfer(**kwargs)
- self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex'])
+ self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex'])
return tx
def send_to(self, *, from_node, scriptPubKey, amount, fee=1000):
@@ -184,40 +214,45 @@ class MiniWallet:
Returns a tuple (txid, n) referring to the created external utxo outpoint.
"""
- tx = self.create_self_transfer(from_node=from_node, fee_rate=0, mempool_valid=False)['tx']
+ tx = self.create_self_transfer(fee_rate=0)["tx"]
assert_greater_than_or_equal(tx.vout[0].nValue, amount + fee)
tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet
tx.vout.append(CTxOut(amount, scriptPubKey)) # arbitrary output -> to be returned
txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex())
return txid, 1
- def send_self_transfer_multi(self, **kwargs):
- """
- Create and send a transaction that spends the given UTXOs and creates a
- certain number of outputs with equal amounts.
-
- Returns a dictionary with
- - txid
- - serialized transaction in hex format
- - transaction as CTransaction instance
- - list of newly created UTXOs, ordered by vout index
- """
+ def send_self_transfer_multi(self, *, from_node, **kwargs):
+ """Call create_self_transfer_multi and send the transaction."""
tx = self.create_self_transfer_multi(**kwargs)
- txid = self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx.serialize().hex())
- return {'new_utxos': [self.get_utxo(txid=txid, vout=vout) for vout in range(len(tx.vout))],
- 'txid': txid, 'hex': tx.serialize().hex(), 'tx': tx}
+ self.sendrawtransaction(from_node=from_node, tx_hex=tx["hex"])
+ return tx
- def create_self_transfer_multi(self, *, from_node, utxos_to_spend=None, num_outputs=1, fee_per_output=1000):
+ def create_self_transfer_multi(
+ self,
+ *,
+ utxos_to_spend: Optional[List[dict]] = None,
+ num_outputs=1,
+ amount_per_output=0,
+ sequence=0,
+ fee_per_output=1000,
+ ):
"""
Create and return a transaction that spends the given UTXOs and creates a
- certain number of outputs with equal amounts.
+ certain number of outputs with equal amounts. The output amounts can be
+ set by amount_per_output or automatically calculated with a fee_per_output.
"""
utxos_to_spend = utxos_to_spend or [self.get_utxo()]
+ sequence = [sequence] * len(utxos_to_spend) if type(sequence) is int else sequence
+ assert_equal(len(utxos_to_spend), len(sequence))
# create simple tx template (1 input, 1 output)
- tx = self.create_self_transfer(fee_rate=0, from_node=from_node, utxo_to_spend=utxos_to_spend[0], mempool_valid=False)['tx']
+ tx = self.create_self_transfer(
+ fee_rate=0,
+ utxo_to_spend=utxos_to_spend[0])["tx"]
# duplicate inputs, witnesses and outputs
tx.vin = [deepcopy(tx.vin[0]) for _ in range(len(utxos_to_spend))]
+ for txin, seq in zip(tx.vin, sequence):
+ txin.nSequence = seq
tx.wit.vtxinwit = [deepcopy(tx.wit.vtxinwit[0]) for _ in range(len(utxos_to_spend))]
tx.vout = [deepcopy(tx.vout[0]) for _ in range(num_outputs)]
@@ -229,54 +264,64 @@ class MiniWallet:
inputs_value_total = sum([int(COIN * utxo['value']) for utxo in utxos_to_spend])
outputs_value_total = inputs_value_total - fee_per_output * num_outputs
for o in tx.vout:
- o.nValue = outputs_value_total // num_outputs
- return tx
-
- def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node=None, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0):
- """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
- from_node = from_node or self._test_node
+ o.nValue = amount_per_output or (outputs_value_total // num_outputs)
+ txid = tx.rehash()
+ return {
+ "new_utxos": [self._create_utxo(
+ txid=txid,
+ vout=i,
+ value=Decimal(tx.vout[i].nValue) / COIN,
+ height=0,
+ ) for i in range(len(tx.vout))],
+ "txid": txid,
+ "hex": tx.serialize().hex(),
+ "tx": tx,
+ }
+
+ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), fee=Decimal("0"), utxo_to_spend=None, locktime=0, sequence=0):
+ """Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed."""
utxo_to_spend = utxo_to_spend or self.get_utxo()
- if self._priv_key is None:
+ assert fee_rate >= 0
+ assert fee >= 0
+ if self._mode in (MiniWalletMode.RAW_OP_TRUE, MiniWalletMode.ADDRESS_OP_TRUE):
vsize = Decimal(104) # anyone-can-spend
- else:
+ elif self._mode == MiniWalletMode.RAW_P2PK:
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
- send_value = int(COIN * (utxo_to_spend['value'] - fee_rate * (vsize / 1000)))
+ else:
+ assert False
+ send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000))
assert send_value > 0
tx = CTransaction()
tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)]
- tx.vout = [CTxOut(send_value, self._scriptPubKey)]
+ tx.vout = [CTxOut(int(COIN * send_value), bytearray(self._scriptPubKey))]
tx.nLockTime = locktime
- if not self._address:
- # raw script
- if self._priv_key is not None:
- # P2PK, need to sign
- self.sign_tx(tx)
- else:
- # anyone-can-spend
- tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size
- else:
+ if self._mode == MiniWalletMode.RAW_P2PK:
+ self.sign_tx(tx)
+ elif self._mode == MiniWalletMode.RAW_OP_TRUE:
+ tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size
+ elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE:
tx.wit.vtxinwit = [CTxInWitness()]
tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key]
+ else:
+ assert False
tx_hex = tx.serialize().hex()
- tx_info = from_node.testmempoolaccept([tx_hex])[0]
- assert_equal(mempool_valid, tx_info['allowed'])
- if mempool_valid:
- assert_equal(tx_info['vsize'], vsize)
- assert_equal(tx_info['fees']['base'], utxo_to_spend['value'] - Decimal(send_value) / COIN)
- return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], 'hex': tx_hex, 'tx': tx}
+ assert_equal(tx.get_vsize(), vsize)
+ new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0)
+
+ return {"txid": new_utxo["txid"], "wtxid": tx.getwtxid(), "hex": tx_hex, "tx": tx, "new_utxo": new_utxo}
- def sendrawtransaction(self, *, from_node, tx_hex, **kwargs):
- txid = from_node.sendrawtransaction(hexstring=tx_hex, **kwargs)
+ def sendrawtransaction(self, *, from_node, tx_hex, maxfeerate=0, **kwargs):
+ txid = from_node.sendrawtransaction(hexstring=tx_hex, maxfeerate=maxfeerate, **kwargs)
self.scan_tx(from_node.decoderawtransaction(tx_hex))
return txid
-def getnewdestination(address_type='bech32'):
+def getnewdestination(address_type='bech32m'):
"""Generate a random destination of the specified type and return the
corresponding public key, scriptPubKey and address. Supported types are
- 'legacy', 'p2sh-segwit' and 'bech32'. Can be used when a random
+ 'legacy', 'p2sh-segwit', 'bech32' and 'bech32m'. Can be used when a random
destination is needed, but no compiled wallet is available (e.g. as
replacement to the getnewaddress/getaddressinfo RPCs)."""
key = ECKey()
@@ -291,7 +336,11 @@ def getnewdestination(address_type='bech32'):
elif address_type == 'bech32':
scriptpubkey = key_to_p2wpkh_script(pubkey)
address = key_to_p2wpkh(pubkey)
- # TODO: also support bech32m (need to generate x-only-pubkey)
+ elif address_type == 'bech32m':
+ tap = taproot_construct(compute_xonly_pubkey(key.get_bytes())[0])
+ pubkey = tap.output_pubkey
+ scriptpubkey = tap.scriptPubKey
+ address = output_key_to_p2tr(pubkey)
else:
assert False
return pubkey, scriptpubkey, address
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index a3c938ae26..7f4758af43 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -28,7 +28,7 @@ import logging
import unittest
# Formatting. Default colors to empty strings.
-BOLD, GREEN, RED, GREY = ("", ""), ("", ""), ("", ""), ("", "")
+DEFAULT, BOLD, GREEN, RED = ("", ""), ("", ""), ("", ""), ("", "")
try:
# Make sure python thinks it can write unicode to its stdout
"\u2713".encode("utf_8").decode(sys.stdout.encoding)
@@ -59,10 +59,10 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): #type:ignore
kernel32.SetConsoleMode(stderr, stderr_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
# primitive formatting on supported
# terminal via ANSI escape sequences:
+ DEFAULT = ('\033[0m', '\033[0m')
BOLD = ('\033[0m', '\033[1m')
GREEN = ('\033[0m', '\033[0;32m')
RED = ('\033[0m', '\033[0;31m')
- GREY = ('\033[0m', '\033[1;30m')
TEST_EXIT_PASSED = 0
TEST_EXIT_SKIPPED = 77
@@ -82,6 +82,7 @@ EXTENDED_SCRIPTS = [
# Longest test should go first, to favor running tests in parallel
'feature_pruning.py',
'feature_dbcrash.py',
+ 'feature_index_prune.py',
]
BASE_SCRIPTS = [
@@ -111,7 +112,6 @@ BASE_SCRIPTS = [
'p2p_tx_download.py',
'mempool_updatefromblock.py',
'wallet_dump.py --legacy-wallet',
- 'feature_taproot.py --previous_release',
'feature_taproot.py',
'rpc_signer.py',
'wallet_signer.py --descriptors',
@@ -130,6 +130,7 @@ BASE_SCRIPTS = [
'wallet_address_types.py --descriptors',
'feature_bip68_sequence.py',
'p2p_feefilter.py',
+ 'rpc_packages.py',
'feature_reindex.py',
'feature_abortnode.py',
# vv Tests less than 30s vv
@@ -157,6 +158,7 @@ BASE_SCRIPTS = [
'wallet_avoidreuse.py --descriptors',
'mempool_reorg.py',
'mempool_persist.py',
+ 'p2p_block_sync.py',
'wallet_multiwallet.py --legacy-wallet',
'wallet_multiwallet.py --descriptors',
'wallet_multiwallet.py --usecli',
@@ -170,6 +172,7 @@ BASE_SCRIPTS = [
'wallet_reorgsrestore.py',
'interface_http.py',
'interface_rpc.py',
+ 'interface_usdt_coinselection.py',
'interface_usdt_net.py',
'interface_usdt_utxocache.py',
'interface_usdt_validation.py',
@@ -179,10 +182,10 @@ BASE_SCRIPTS = [
'rpc_whitelist.py',
'feature_proxy.py',
'feature_syscall_sandbox.py',
- 'rpc_signrawtransaction.py --legacy-wallet',
- 'rpc_signrawtransaction.py --descriptors',
+ 'wallet_signrawtransactionwithwallet.py --legacy-wallet',
+ 'wallet_signrawtransactionwithwallet.py --descriptors',
+ 'rpc_signrawtransactionwithkey.py',
'rpc_rawtransaction.py --legacy-wallet',
- 'rpc_rawtransaction.py --descriptors',
'wallet_groups.py --legacy-wallet',
'wallet_transactiontime_rescan.py --descriptors',
'wallet_transactiontime_rescan.py --legacy-wallet',
@@ -230,7 +233,6 @@ BASE_SCRIPTS = [
'mempool_packages.py',
'mempool_package_onemore.py',
'rpc_createmultisig.py',
- 'rpc_packages.py',
'mempool_package_limits.py',
'feature_versionbits_warning.py',
'rpc_preciousblock.py',
@@ -243,8 +245,7 @@ BASE_SCRIPTS = [
'rpc_generate.py',
'wallet_balance.py --legacy-wallet',
'wallet_balance.py --descriptors',
- 'feature_nulldummy.py --legacy-wallet',
- 'feature_nulldummy.py --descriptors',
+ 'feature_nulldummy.py',
'mempool_accept.py',
'mempool_expiry.py',
'wallet_import_rescan.py --legacy-wallet',
@@ -254,6 +255,7 @@ BASE_SCRIPTS = [
'rpc_bind.py --ipv4',
'rpc_bind.py --ipv6',
'rpc_bind.py --nonloopback',
+ 'wallet_crosschain.py',
'mining_basic.py',
'feature_signet.py',
'wallet_bumpfee.py --legacy-wallet',
@@ -326,13 +328,12 @@ BASE_SCRIPTS = [
'feature_presegwit_node_upgrade.py',
'feature_settings.py',
'rpc_getdescriptorinfo.py',
- 'rpc_mempool_entry_fee_fields_deprecation.py',
+ 'rpc_mempool_info.py',
'rpc_help.py',
'feature_dirsymlinks.py',
'feature_help.py',
'feature_shutdown.py',
'p2p_ibd_txrelay.py',
- 'feature_blockfilterindex_prune.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
]
@@ -371,11 +372,11 @@ def main():
args, unknown_args = parser.parse_known_args()
if not args.ansi:
- global BOLD, GREEN, RED, GREY
+ global DEFAULT, BOLD, GREEN, RED
+ DEFAULT = ("", "")
BOLD = ("", "")
GREEN = ("", "")
RED = ("", "")
- GREY = ("", "")
# args to be passed on always start with two dashes; tests are the remaining unknown args
tests = [arg for arg in unknown_args if arg[:2] != "--"]
@@ -719,7 +720,7 @@ class TestResult():
color = RED
glyph = CROSS
elif self.status == "Skipped":
- color = GREY
+ color = DEFAULT
glyph = CIRCLE
return color[1] + "%s | %s%s | %s s\n" % (self.name.ljust(self.padding), glyph, self.status.ljust(7), self.time) + color[0]
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index a6c93ba5f9..f66fab19ac 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -25,7 +25,7 @@ class WalletTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.extra_args = [[
- "-acceptnonstdtxn=1", "-walletrejectlongchains=0"
+ "-dustrelayfee=0", "-walletrejectlongchains=0"
]] * self.num_nodes
self.setup_clean_chain = True
self.supports_cli = False
diff --git a/test/functional/wallet_coinbase_category.py b/test/functional/wallet_coinbase_category.py
index 5a6b6cee59..c2a8e612cf 100755
--- a/test/functional/wallet_coinbase_category.py
+++ b/test/functional/wallet_coinbase_category.py
@@ -15,6 +15,7 @@ from test_framework.util import (
class CoinbaseCategoryTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
+ self.setup_clean_chain = True
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py
index dcf2e98638..12480d4d1e 100755
--- a/test/functional/wallet_createwallet.py
+++ b/test/functional/wallet_createwallet.py
@@ -29,7 +29,7 @@ class CreateWalletTest(BitcoinTestFramework):
self.log.info("Run createwallet with invalid parameters.")
# Run createwallet with invalid parameters. This must not prevent a new wallet with the same name from being created with the correct parameters.
assert_raises_rpc_error(-4, "Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.",
- self.nodes[0].createwallet, wallet_name='w0', descriptors=True, disable_private_keys=True, passphrase="passphrase")
+ self.nodes[0].createwallet, wallet_name='w0', disable_private_keys=True, passphrase="passphrase")
self.nodes[0].createwallet(wallet_name='w0')
w0 = node.get_wallet_rpc('w0')
diff --git a/test/functional/wallet_crosschain.py b/test/functional/wallet_crosschain.py
new file mode 100755
index 0000000000..b6d0c87985
--- /dev/null
+++ b/test/functional/wallet_crosschain.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import os
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_raises_rpc_error
+
+class WalletCrossChain(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+ self.setup_clean_chain = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def setup_network(self):
+ self.add_nodes(self.num_nodes)
+
+ # Switch node 1 to testnet before starting it.
+ self.nodes[1].chain = 'testnet3'
+ self.nodes[1].extra_args = ['-maxconnections=0'] # disable testnet sync
+ with open(self.nodes[1].bitcoinconf, 'r', encoding='utf8') as conf:
+ conf_data = conf.read()
+ with open (self.nodes[1].bitcoinconf, 'w', encoding='utf8') as conf:
+ conf.write(conf_data.replace('regtest=', 'testnet=').replace('[regtest]', '[test]'))
+
+ self.start_nodes()
+
+ def run_test(self):
+ self.log.info("Creating wallets")
+
+ node0_wallet = os.path.join(self.nodes[0].datadir, 'node0_wallet')
+ self.nodes[0].createwallet(node0_wallet)
+ self.nodes[0].unloadwallet(node0_wallet)
+ node1_wallet = os.path.join(self.nodes[1].datadir, 'node1_wallet')
+ self.nodes[1].createwallet(node1_wallet)
+ self.nodes[1].unloadwallet(node1_wallet)
+
+ self.log.info("Loading wallets into nodes with a different genesis blocks")
+
+ 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[1].loadwallet, node0_wallet)
+ 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[1].loadwallet, node0_wallet)
+
+ if not self.options.descriptors:
+ self.log.info("Override cross-chain wallet load protection")
+ self.stop_nodes()
+ self.start_nodes([['-walletcrosschain']] * self.num_nodes)
+ self.nodes[0].loadwallet(node1_wallet)
+ self.nodes[1].loadwallet(node0_wallet)
+
+
+if __name__ == '__main__':
+ WalletCrossChain().main()
diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py
index e47d021210..5dc23ba245 100755
--- a/test/functional/wallet_descriptor.py
+++ b/test/functional/wallet_descriptor.py
@@ -93,15 +93,15 @@ class WalletDescriptorTest(BitcoinTestFramework):
# Make sure things are disabled
self.log.info("Test disabled RPCs")
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importprivkey, "cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW")
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importpubkey, send_wrpc.getaddressinfo(send_wrpc.getnewaddress()))
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importaddress, recv_wrpc.getnewaddress())
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importmulti, [])
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.addmultisigaddress, 1, [recv_wrpc.getnewaddress()])
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.dumpprivkey, recv_wrpc.getnewaddress())
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.dumpwallet, 'wallet.dump')
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importwallet, 'wallet.dump')
- assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.sethdseed)
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importprivkey, "cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW")
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importpubkey, send_wrpc.getaddressinfo(send_wrpc.getnewaddress()))
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importaddress, recv_wrpc.getnewaddress())
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importmulti, [])
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.addmultisigaddress, 1, [recv_wrpc.getnewaddress()])
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.dumpprivkey, recv_wrpc.getnewaddress())
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.dumpwallet, 'wallet.dump')
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importwallet, 'wallet.dump')
+ assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.sethdseed)
self.log.info("Test encryption")
# Get the master fingerprint before encrypt
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index ac74ff2484..ff11f421a1 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -454,7 +454,7 @@ class ImportDescriptorsTest(BitcoinTestFramework):
send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8)
decoded = wmulti_priv.gettransaction(txid=send_txid, verbose=True)['decoded']
assert_equal(len(decoded['vin'][0]['txinwitness']), 4)
- self.generate(self.nodes[0], 6)
+ self.sync_all()
self.nodes[1].createwallet(wallet_name="wmulti_pub", disable_private_keys=True, blank=True, descriptors=True)
wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub")
diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py
index 48b92796fc..7ae3a40eaf 100755
--- a/test/functional/wallet_listreceivedby.py
+++ b/test/functional/wallet_listreceivedby.py
@@ -18,17 +18,12 @@ from test_framework.wallet_util import test_address
class ReceivedByTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
- # Test deprecated exclude coinbase on second node
- self.extra_args = [[], ["-deprecatedrpc=exclude_coinbase"]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
self.skip_if_no_cli()
def run_test(self):
- # Generate block to get out of IBD
- self.generate(self.nodes[0], 1)
-
# save the number of coinbase reward addresses so far
num_cb_reward_addresses = len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True))
@@ -62,6 +57,11 @@ class ReceivedByTest(BitcoinTestFramework):
{"address": empty_addr},
{"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []})
+ # No returned addy should be a change addr
+ for node in self.nodes:
+ for addr_obj in node.listreceivedbyaddress():
+ assert_equal(node.getaddressinfo(addr_obj["address"])["ischange"], False)
+
# Test Address filtering
# Only on addr
expected = {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]}
@@ -134,6 +134,9 @@ class ReceivedByTest(BitcoinTestFramework):
txid = self.nodes[0].sendtoaddress(addr, 0.1)
self.sync_all()
+ # getreceivedbylabel returns an error if the wallet doesn't own the label
+ assert_raises_rpc_error(-4, "Label not found in wallet", self.nodes[0].getreceivedbylabel, "dummy")
+
# listreceivedbylabel should return received_by_label_json because of 0 confirmations
assert_array_result(self.nodes[1].listreceivedbylabel(),
{"label": label},
@@ -172,7 +175,7 @@ class ReceivedByTest(BitcoinTestFramework):
address = self.nodes[0].getnewaddress(label)
reward = Decimal("25")
- self.generatetoaddress(self.nodes[0], 1, address, sync_fun=self.no_op)
+ self.generatetoaddress(self.nodes[0], 1, address)
hash = self.nodes[0].getbestblockhash()
self.log.info("getreceivedbyaddress returns nothing with defaults")
@@ -212,7 +215,7 @@ class ReceivedByTest(BitcoinTestFramework):
{"label": label, "amount": reward})
self.log.info("Generate 100 more blocks")
- self.generate(self.nodes[0], COINBASE_MATURITY, sync_fun=self.no_op)
+ self.generate(self.nodes[0], COINBASE_MATURITY)
self.log.info("getreceivedbyaddress returns reward with defaults")
balance = self.nodes[0].getreceivedbyaddress(address)
@@ -253,35 +256,6 @@ class ReceivedByTest(BitcoinTestFramework):
{"label": label},
{}, True)
- # Test exclude_coinbase
- address2 = self.nodes[1].getnewaddress(label)
- self.generatetoaddress(self.nodes[1], COINBASE_MATURITY + 1, address2, sync_fun=self.no_op)
-
- self.log.info("getreceivedbyaddress returns nothing when excluding coinbase")
- balance = self.nodes[1].getreceivedbyaddress(address2)
- assert_equal(balance, 0)
-
- self.log.info("getreceivedbylabel returns nothing when excluding coinbase")
- balance = self.nodes[1].getreceivedbylabel("label")
- assert_equal(balance, 0)
-
- self.log.info("listreceivedbyaddress does not include address when excluding coinbase")
- assert_array_result(self.nodes[1].listreceivedbyaddress(),
- {"address": address2},
- {}, True)
-
- self.log.info("listreceivedbylabel does not include label when excluding coinbase")
- assert_array_result(self.nodes[1].listreceivedbylabel(),
- {"label": label},
- {}, True)
-
- self.log.info("getreceivedbyaddress throws when setting include_immature_coinbase with deprecated exclude_coinbase")
- assert_raises_rpc_error(-8, 'include_immature_coinbase is incompatible with deprecated exclude_coinbase', self.nodes[1].getreceivedbyaddress, address2, 1, True)
-
-
- self.log.info("listreceivedbyaddress throws when setting include_immature_coinbase with deprecated exclude_coinbase")
- assert_raises_rpc_error(-8, 'include_immature_coinbase is incompatible with deprecated exclude_coinbase', self.nodes[1].listreceivedbyaddress, 1, False, False, "", True)
-
if __name__ == '__main__':
ReceivedByTest().main()
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index 86e36be8f7..07baa0595e 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -10,6 +10,10 @@ from itertools import product
from test_framework.authproxy import JSONRPCException
from test_framework.descriptors import descsum_create
from test_framework.key import ECKey
+from test_framework.messages import (
+ ser_compact_size,
+ WITNESS_SCALE_FACTOR,
+)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -488,8 +492,8 @@ class WalletSendTest(BitcoinTestFramework):
self.nodes[1].createwallet("extfund")
ext_fund = self.nodes[1].get_wallet_rpc("extfund")
- # Make a weird but signable script. sh(pkh()) descriptor accomplishes this
- desc = descsum_create("sh(pkh({}))".format(privkey))
+ # Make a weird but signable script. sh(wsh(pkh())) descriptor accomplishes this
+ desc = descsum_create("sh(wsh(pkh({})))".format(privkey))
if self.options.descriptors:
res = ext_fund.importdescriptors([{"desc": desc, "timestamp": "now"}])
else:
@@ -507,7 +511,7 @@ class WalletSendTest(BitcoinTestFramework):
self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, expect_error=(-4, "Insufficient funds"))
# But funding should work when the solving data is provided
- res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]})
+ res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]})
signed = ext_wallet.walletprocesspsbt(res["psbt"])
signed = ext_fund.walletprocesspsbt(res["psbt"])
assert signed["complete"]
@@ -526,10 +530,11 @@ class WalletSendTest(BitcoinTestFramework):
break
psbt_in = dec["inputs"][input_idx]
# Calculate the input weight
- # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness
+ # (prevout + sequence + length of scriptSig + scriptsig + 1 byte buffer) * WITNESS_SCALE_FACTOR + num scriptWitness stack items + (length of stack item + stack item) * N stack items + 1 byte buffer
len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0
- len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0
- input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness
+ len_scriptsig += len(ser_compact_size(len_scriptsig)) + 1
+ len_scriptwitness = (sum([(len(x) // 2) + len(ser_compact_size(len(x) // 2)) for x in psbt_in["final_scriptwitness"]]) + len(psbt_in["final_scriptwitness"]) + 1) if "final_scriptwitness" in psbt_in else 0
+ input_weight = ((40 + len_scriptsig) * WITNESS_SCALE_FACTOR) + len_scriptwitness
# Input weight error conditions
assert_raises_rpc_error(
diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/wallet_signrawtransactionwithwallet.py
index a2091b4ece..6b30386b7e 100755
--- a/test/functional/rpc_signrawtransaction.py
+++ b/test/functional/wallet_signrawtransactionwithwallet.py
@@ -2,16 +2,14 @@
# Copyright (c) 2015-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 transaction signing using the signrawtransaction* RPCs."""
+"""Test transaction signing using the signrawtransactionwithwallet RPC."""
from test_framework.blocktools import (
COINBASE_MATURITY,
)
from test_framework.address import (
- script_to_p2sh,
script_to_p2wsh,
)
-from test_framework.key import ECKey
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -29,20 +27,13 @@ from test_framework.script import (
OP_DROP,
OP_TRUE,
)
-from test_framework.script_util import (
- key_to_p2pk_script,
- key_to_p2pkh_script,
- script_to_p2sh_p2wsh_script,
- script_to_p2wsh_script,
-)
-from test_framework.wallet_util import bytes_to_wif
from decimal import (
Decimal,
getcontext,
)
-class SignRawTransactionsTest(BitcoinTestFramework):
+class SignRawTransactionWithWalletTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
@@ -50,35 +41,6 @@ class SignRawTransactionsTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
- def successful_signing_test(self):
- """Create and sign a valid raw transaction with one input.
-
- Expected results:
-
- 1) The transaction has a complete set of signatures
- 2) No script verification error occurred"""
- self.log.info("Test valid raw transaction with one input")
- privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA']
-
- inputs = [
- # Valid pay-to-pubkey scripts
- {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0,
- 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'},
- {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0,
- 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'},
- ]
-
- outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1}
-
- rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
- rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, inputs)
-
- # 1) The transaction has a complete set of signatures
- assert rawTxSigned['complete']
-
- # 2) No script verification error occurred
- assert 'errors' not in rawTxSigned
-
def test_with_lock_outputs(self):
self.log.info("Test correct error reporting when trying to sign a locked output")
self.nodes[0].encryptwallet("password")
@@ -191,60 +153,6 @@ class SignRawTransactionsTest(BitcoinTestFramework):
assert_equal(signedtx["hex"], signedtx2["hex"])
self.nodes[0].walletlock()
- def witness_script_test(self):
- self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet")
- # Create a new P2SH-P2WSH 1-of-1 multisig address:
- eckey = ECKey()
- eckey.generate()
- embedded_privkey = bytes_to_wif(eckey.get_bytes())
- embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
- p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit")
- # send transaction to P2SH-P2WSH 1-of-1 multisig address
- self.generate(self.nodes[0], COINBASE_MATURITY + 1)
- self.nodes[0].sendtoaddress(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]
- spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex()
- unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript']
- unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex()
- assert_equal(spk, unspent_output['scriptPubKey'])
- # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
- spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].get_wallet_rpc(self.default_wallet_name).getnewaddress(): Decimal("49.998")})
- spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output])
- # Check the signing completed successfully
- assert 'complete' in spending_tx_signed
- assert_equal(spending_tx_signed['complete'], True)
-
- # Now test with P2PKH and P2PK scripts as the witnessScript
- for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent
- self.verify_txn_with_witness_script(tx_type)
-
- def verify_txn_with_witness_script(self, tx_type):
- self.log.info("Test with a {} script as the witnessScript".format(tx_type))
- eckey = ECKey()
- eckey.generate()
- embedded_privkey = bytes_to_wif(eckey.get_bytes())
- embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
- witness_script = {
- 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(),
- 'P2PK': key_to_p2pk_script(embedded_pubkey).hex()
- }.get(tx_type, "Invalid tx_type")
- redeem_script = script_to_p2wsh_script(witness_script).hex()
- addr = script_to_p2sh(redeem_script)
- script_pub_key = self.nodes[1].validateaddress(addr)['scriptPubKey']
- # Fund that address
- txid = self.nodes[0].sendtoaddress(addr, 10)
- vout = find_vout_for_address(self.nodes[0], txid, addr)
- self.generate(self.nodes[0], 1)
- # 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}], {self.nodes[1].getnewaddress(): 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}])
- # Check the signing completed successfully
- assert 'complete' in spending_tx_signed
- assert_equal(spending_tx_signed['complete'], True)
- self.nodes[0].sendrawtransaction(spending_tx_signed['hex'])
-
def OP_1NEGATE_test(self):
self.log.info("Test OP_1NEGATE (0x4f) satisfies BIP62 minimal push standardness rule")
hex_str = (
@@ -334,16 +242,65 @@ class SignRawTransactionsTest(BitcoinTestFramework):
assert_equal(signed["complete"], True)
self.nodes[0].sendrawtransaction(signed["hex"])
+ def test_signing_with_missing_prevtx_info(self):
+ txid = "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000"
+ for type in ["bech32", "p2sh-segwit", "legacy"]:
+ self.log.info(f"Test signing with missing prevtx info ({type})")
+ addr = self.nodes[0].getnewaddress("", type)
+ addrinfo = self.nodes[0].getaddressinfo(addr)
+ pubkey = addrinfo["scriptPubKey"]
+ inputs = [{'txid': txid, 'vout': 3, 'sequence': 1000}]
+ outputs = {self.nodes[0].getnewaddress(): 1}
+ rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
+
+ prevtx = dict(txid=txid, scriptPubKey=pubkey, vout=3, amount=1)
+ succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx])
+ assert succ["complete"]
+
+ if type == "legacy":
+ del prevtx["amount"]
+ succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx])
+ assert succ["complete"]
+ else:
+ assert_raises_rpc_error(-3, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [
+ {
+ "txid": txid,
+ "scriptPubKey": pubkey,
+ "vout": 3,
+ }
+ ])
+
+ assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [
+ {
+ "txid": txid,
+ "scriptPubKey": pubkey,
+ "amount": 1,
+ }
+ ])
+ assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].signrawtransactionwithwallet, rawtx, [
+ {
+ "scriptPubKey": pubkey,
+ "vout": 3,
+ "amount": 1,
+ }
+ ])
+ assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [
+ {
+ "txid": txid,
+ "vout": 3,
+ "amount": 1
+ }
+ ])
+
def run_test(self):
- self.successful_signing_test()
self.script_verification_error_test()
- self.witness_script_test()
self.OP_1NEGATE_test()
self.test_with_lock_outputs()
self.test_fully_signed_tx()
self.test_signing_with_csv()
self.test_signing_with_cltv()
+ self.test_signing_with_missing_prevtx_info()
if __name__ == '__main__':
- SignRawTransactionsTest().main()
+ SignRawTransactionWithWalletTest().main()
diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py
index 41bb86f962..c8d4a1da45 100755
--- a/test/functional/wallet_taproot.py
+++ b/test/functional/wallet_taproot.py
@@ -7,19 +7,19 @@
import random
from decimal import Decimal
+from test_framework.address import output_key_to_p2tr
+from test_framework.key import H_POINT
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework.descriptors import descsum_create
from test_framework.script import (
CScript,
MAX_PUBKEYS_PER_MULTI_A,
- OP_1,
OP_CHECKSIG,
OP_CHECKSIGADD,
OP_NUMEQUAL,
taproot_construct,
)
-from test_framework.segwit_addr import encode_segwit_address
# xprvs/xpubs, and m/* derived x-only pubkeys (created using independent implementation)
KEYS = [
@@ -158,9 +158,6 @@ KEYS = [
CHANGE_XPRV = "tprv8ZgxMBicQKsPcyDrWwiecVnTtFmfRwbfFqEfR4ZGWvq5aTTwLBWmAm5zrbMcYtb9gQNFfhRfqhhrBG37U3nhmXxEgeEPBJGHAPrHCrAd1WX"
CHANGE_XPUB = "tpubD6NzVbkrYhZ4WSFeQbPF1uSaTHHbbGnZq8qShabZwCdUQwihxaLMMFhs2kidGF2qrRKiQVqw8VoyuTHj1bZqmMXMeciaU1gBjWA1sim2zUB"
-# Point with no known discrete log.
-H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
-
def key(hex_key):
"""Construct an x-only pubkey from its hex representation."""
@@ -183,10 +180,7 @@ def multi_a(k, hex_keys, sort=False):
def compute_taproot_address(pubkey, scripts):
"""Compute the address for a taproot output with given inner key and scripts."""
- tap = taproot_construct(pubkey, scripts)
- assert tap.scriptPubKey[0] == OP_1
- assert tap.scriptPubKey[1] == 0x20
- return encode_segwit_address("bcrt", 1, tap.scriptPubKey[2:])
+ return output_key_to_p2tr(taproot_construct(pubkey, scripts).output_pubkey)
class WalletTaprootTest(BitcoinTestFramework):
"""Test generation and spending of P2TR address outputs."""
@@ -305,9 +299,21 @@ class WalletTaprootTest(BitcoinTestFramework):
test_balance = int(self.psbt_online.getbalance() * 100000000)
ret_amnt = random.randrange(100000, test_balance)
# Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends.
- psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200})['psbt']
- res = self.psbt_offline.walletprocesspsbt(psbt)
- assert(res['complete'])
+ psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": "bech32m"})['psbt']
+ res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False)
+
+ decoded = self.psbt_offline.decodepsbt(res["psbt"])
+ if pattern.startswith("tr("):
+ for psbtin in decoded["inputs"]:
+ assert "non_witness_utxo" not in psbtin
+ assert "witness_utxo" in psbtin
+ assert "taproot_internal_key" in psbtin
+ assert "taproot_bip32_derivs" in psbtin
+ assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin
+ if "taproot_script_path_sigs" in psbtin:
+ assert "taproot_merkle_root" in psbtin
+ assert "taproot_scripts" in psbtin
+
rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
txid = self.nodes[0].sendrawtransaction(rawtx)
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
@@ -441,13 +447,12 @@ class WalletTaprootTest(BitcoinTestFramework):
self.log.info("Sending everything back...")
- txid = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=self.rpc_online.getbalance(), subtractfeefromamount=True)
+ txid = self.rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"]
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0)
- psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): self.psbt_online.getbalance()}], None, {"subtractFeeFromOutputs": [0]})['psbt']
- res = self.psbt_offline.walletprocesspsbt(psbt)
- assert(res['complete'])
+ psbt = self.psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"]
+ res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False)
rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
txid = self.nodes[0].sendrawtransaction(rawtx)
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py
index 688ca58d7f..7b7cfbfef5 100755
--- a/test/get_previous_releases.py
+++ b/test/get_previous_releases.py
@@ -71,6 +71,15 @@ SHA256_SUMS = {
"91b1e012975c5a363b5b5fcc81b5b7495e86ff703ec8262d4b9afcfec633c30d": "bitcoin-22.0-powerpc64le-linux-gnu.tar.gz",
"9cc3a62c469fe57e11485fdd32c916f10ce7a2899299855a2e479256ff49ff3c": "bitcoin-22.0-riscv64-linux-gnu.tar.gz",
"59ebd25dd82a51638b7a6bb914586201e67db67b919b2a1ff08925a7936d1b16": "bitcoin-22.0-x86_64-linux-gnu.tar.gz",
+
+ "06f4c78271a77752ba5990d60d81b1751507f77efda1e5981b4e92fd4d9969fb": "bitcoin-23.0-aarch64-linux-gnu.tar.gz",
+ "952c574366aff76f6d6ad1c9ee45a361d64fa04155e973e926dfe7e26f9703a3": "bitcoin-23.0-arm-linux-gnueabihf.tar.gz",
+ "7c8bc63731aa872b7b334a8a7d96e33536ad77d49029bad179b09dca32cd77ac": "bitcoin-23.0-arm64-apple-darwin.tar.gz",
+ "2caa5898399e415f61d9af80a366a3008e5856efa15aaff74b88acf429674c99": "bitcoin-23.0-powerpc64-linux-gnu.tar.gz",
+ "217dd0469d0f4962d22818c368358575f6a0abcba8804807bb75325eb2f28b19": "bitcoin-23.0-powerpc64le-linux-gnu.tar.gz",
+ "078f96b1e92895009c798ab827fb3fde5f6719eee886bd0c0e93acab18ea4865": "bitcoin-23.0-riscv64-linux-gnu.tar.gz",
+ "c816780583009a9dad426dc0c183c89be9da98906e1e2c7ebae91041c1aaaaf3": "bitcoin-23.0-x86_64-apple-darwin.tar.gz",
+ "2cca490c1f2842884a3c5b0606f179f9f937177da4eadd628e3f7fd7e25d26d0": "bitcoin-23.0-x86_64-linux-gnu.tar.gz",
}
@@ -96,8 +105,11 @@ def download_binary(tag, args) -> int:
if match:
bin_path = 'bin/bitcoin-core-{}/test.{}'.format(
match.group(1), match.group(2))
+ platform = args.platform
+ if tag < "v23" and platform in ["x86_64-apple-darwin", "arm64-apple-darwin"]:
+ platform = "osx64"
tarball = 'bitcoin-{tag}-{platform}.tar.gz'.format(
- tag=tag[1:], platform=args.platform)
+ tag=tag[1:], platform=platform)
tarballUrl = 'https://bitcoincore.org/{bin_path}/{tarball}'.format(
bin_path=bin_path, tarball=tarball)
@@ -201,8 +213,8 @@ def check_host(args) -> int:
platforms = {
'aarch64-*-linux*': 'aarch64-linux-gnu',
'x86_64-*-linux*': 'x86_64-linux-gnu',
- 'x86_64-apple-darwin*': 'osx64',
- 'aarch64-apple-darwin*': 'osx64',
+ 'x86_64-apple-darwin*': 'x86_64-apple-darwin',
+ 'aarch64-apple-darwin*': 'arm64-apple-darwin',
}
args.platform = ''
for pattern, target in platforms.items():
diff --git a/test/lint/README.md b/test/lint/README.md
index f4165f908e..8d592c3282 100644
--- a/test/lint/README.md
+++ b/test/lint/README.md
@@ -28,7 +28,6 @@ To do a full check with `-r`, make sure that you have fetched the upstream repos
maintained:
* for `src/secp256k1`: https://github.com/bitcoin-core/secp256k1.git (branch master)
* for `src/leveldb`: https://github.com/bitcoin-core/leveldb-subtree.git (branch bitcoin-fork)
-* for `src/univalue`: https://github.com/bitcoin-core/univalue-subtree.git (branch master)
* for `src/crypto/ctaes`: https://github.com/bitcoin-core/ctaes.git (branch master)
* for `src/crc32c`: https://github.com/bitcoin-core/crc32c-subtree.git (branch bitcoin-fork)
* for `src/minisketch`: https://github.com/sipa/minisketch.git (branch master)
@@ -39,6 +38,6 @@ To do so, add the upstream repository as remote:
git remote add --fetch secp256k1 https://github.com/bitcoin-core/secp256k1.git
```
-lint-all.sh
+all-lint.py
===========
Calls other scripts with the `lint-` prefix.
diff --git a/test/lint/all-lint.py b/test/lint/all-lint.py
new file mode 100755
index 0000000000..34a7b9742a
--- /dev/null
+++ b/test/lint/all-lint.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2017-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.
+#
+# This script runs all test/lint/lint-* files, and fails if any exit
+# with a non-zero status code.
+
+from glob import glob
+from pathlib import Path
+from subprocess import run
+
+exit_code = 0
+mod_path = Path(__file__).parent
+for lint in glob(f"{mod_path}/lint-*.py"):
+ result = run([lint])
+ if result.returncode != 0:
+ print(f"^---- failure generated from {lint.split('/')[-1]}")
+ exit_code |= result.returncode
+
+exit(exit_code)
diff --git a/test/lint/extended-lint-all.sh b/test/lint/extended-lint-all.sh
deleted file mode 100755
index be5d9db4a9..0000000000
--- a/test/lint/extended-lint-all.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2019-2020 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#
-# This script runs all contrib/devtools/extended-lint-*.sh files, and fails if
-# any exit with a non-zero status code.
-
-# This script is intentionally locale dependent by not setting "export LC_ALL=C"
-# in order to allow for the executed lint scripts to opt in or opt out of locale
-# dependence themselves.
-
-set -u
-
-SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
-LINTALL=$(basename "${BASH_SOURCE[0]}")
-
-for f in "${SCRIPTDIR}"/extended-lint-*.sh; do
- if [ "$(basename "$f")" != "$LINTALL" ]; then
- if ! "$f"; then
- echo "^---- failure generated from $f"
- exit 1
- fi
- fi
-done
diff --git a/test/lint/extended-lint-cppcheck.sh b/test/lint/extended-lint-cppcheck.sh
deleted file mode 100755
index 2af39ed60a..0000000000
--- a/test/lint/extended-lint-cppcheck.sh
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/env bash
-#
-# 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.
-#
-
-export LC_ALL=C
-
-ENABLED_CHECKS=(
- "Class '.*' has a constructor with 1 argument that is not explicit."
- "Struct '.*' has a constructor with 1 argument that is not explicit."
-)
-
-IGNORED_WARNINGS=(
- "src/arith_uint256.h:.* Class 'arith_uint256' has a constructor with 1 argument that is not explicit."
- "src/arith_uint256.h:.* Class 'base_uint < 256 >' has a constructor with 1 argument that is not explicit."
- "src/arith_uint256.h:.* Class 'base_uint' has a constructor with 1 argument that is not explicit."
- "src/coins.h:.* Class 'CCoinsViewBacked' has a constructor with 1 argument that is not explicit."
- "src/coins.h:.* Class 'CCoinsViewCache' has a constructor with 1 argument that is not explicit."
- "src/coins.h:.* Class 'CCoinsViewCursor' has a constructor with 1 argument that is not explicit."
- "src/net.h:.* Class 'CNetMessage' has a constructor with 1 argument that is not explicit."
- "src/policy/feerate.h:.* Class 'CFeeRate' has a constructor with 1 argument that is not explicit."
- "src/prevector.h:.* Class 'const_iterator' has a constructor with 1 argument that is not explicit."
- "src/prevector.h:.* Class 'const_reverse_iterator' has a constructor with 1 argument that is not explicit."
- "src/prevector.h:.* Class 'iterator' has a constructor with 1 argument that is not explicit."
- "src/prevector.h:.* Class 'reverse_iterator' has a constructor with 1 argument that is not explicit."
- "src/primitives/block.h:.* Class 'CBlock' has a constructor with 1 argument that is not explicit."
- "src/primitives/transaction.h:.* Class 'CTransaction' has a constructor with 1 argument that is not explicit."
- "src/protocol.h:.* Class 'CMessageHeader' has a constructor with 1 argument that is not explicit."
- "src/qt/guiutil.h:.* Class 'ItemDelegate' has a constructor with 1 argument that is not explicit."
- "src/rpc/util.h:.* Struct 'RPCResults' has a constructor with 1 argument that is not explicit."
- "src/rpc/util.h:.* Struct 'UniValueType' has a constructor with 1 argument that is not explicit."
- "src/rpc/util.h:.* style: Struct 'UniValueType' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'AddressDescriptor' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'ComboDescriptor' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'ConstPubkeyProvider' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'PKDescriptor' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'PKHDescriptor' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'RawDescriptor' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'SHDescriptor' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'WPKHDescriptor' has a constructor with 1 argument that is not explicit."
- "src/script/descriptor.cpp:.* Class 'WSHDescriptor' has a constructor with 1 argument that is not explicit."
- "src/script/script.h:.* Class 'CScript' has a constructor with 1 argument that is not explicit."
- "src/script/standard.h:.* Class 'CScriptID' has a constructor with 1 argument that is not explicit."
- "src/span.h:.* Class 'Span < const CRPCCommand >' has a constructor with 1 argument that is not explicit."
- "src/span.h:.* Class 'Span < const char >' has a constructor with 1 argument that is not explicit."
- "src/span.h:.* Class 'Span < const std :: vector <unsigned char > >' has a constructor with 1 argument that is not explicit."
- "src/span.h:.* Class 'Span < const uint8_t >' has a constructor with 1 argument that is not explicit."
- "src/span.h:.* Class 'Span' has a constructor with 1 argument that is not explicit."
- "src/support/allocators/secure.h:.* Struct 'secure_allocator < char >' has a constructor with 1 argument that is not explicit."
- "src/support/allocators/secure.h:.* Struct 'secure_allocator < RNGState >' has a constructor with 1 argument that is not explicit."
- "src/support/allocators/secure.h:.* Struct 'secure_allocator < unsigned char >' has a constructor with 1 argument that is not explicit."
- "src/support/allocators/zeroafterfree.h:.* Struct 'zero_after_free_allocator < char >' has a constructor with 1 argument that is not explicit."
- "src/test/checkqueue_tests.cpp:.* Struct 'FailingCheck' has a constructor with 1 argument that is not explicit."
- "src/test/checkqueue_tests.cpp:.* Struct 'MemoryCheck' has a constructor with 1 argument that is not explicit."
- "src/test/checkqueue_tests.cpp:.* Struct 'UniqueCheck' has a constructor with 1 argument that is not explicit."
- "src/test/fuzz/util.h:.* Class 'FuzzedFileProvider' has a constructor with 1 argument that is not explicit."
- "src/test/fuzz/util.h:.* Class 'FuzzedAutoFileProvider' has a constructor with 1 argument that is not explicit."
- "src/wallet/db.h:.* Class 'BerkeleyEnvironment' has a constructor with 1 argument that is not explicit."
-)
-
-if ! command -v cppcheck > /dev/null; then
- echo "Skipping cppcheck linting since cppcheck is not installed. Install by running \"apt install cppcheck\""
- exit 0
-fi
-
-function join_array {
- local IFS="$1"
- shift
- echo "$*"
-}
-
-ENABLED_CHECKS_REGEXP=$(join_array "|" "${ENABLED_CHECKS[@]}")
-IGNORED_WARNINGS_REGEXP=$(join_array "|" "${IGNORED_WARNINGS[@]}")
-WARNINGS=$(git ls-files -- "*.cpp" "*.h" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/minisketch/" ":(exclude)src/univalue/" | \
- xargs cppcheck --enable=all -j "$(getconf _NPROCESSORS_ONLN)" --language=c++ --std=c++17 --template=gcc -D__cplusplus -DCLIENT_VERSION_BUILD -DCLIENT_VERSION_IS_RELEASE -DCLIENT_VERSION_MAJOR -DCLIENT_VERSION_MINOR -DCOPYRIGHT_YEAR -DDEBUG -I src/ -q 2>&1 | sort -u | \
- grep -E "${ENABLED_CHECKS_REGEXP}" | \
- grep -vE "${IGNORED_WARNINGS_REGEXP}")
-if [[ ${WARNINGS} != "" ]]; then
- echo "${WARNINGS}"
- echo
- echo "Advice not applicable in this specific case? Add an exception by updating"
- echo "IGNORED_WARNINGS in $0"
- # Uncomment to enforce the developer note policy "By default, declare single-argument constructors `explicit`"
- # exit 1
-fi
-exit 0
diff --git a/test/lint/lint-all.sh b/test/lint/lint-all.sh
deleted file mode 100755
index fa37fa51c6..0000000000
--- a/test/lint/lint-all.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2017-2019 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#
-# This script runs all contrib/devtools/lint-* files, and fails if any exit
-# with a non-zero status code.
-
-# This script is intentionally locale dependent by not setting "export LC_ALL=C"
-# in order to allow for the executed lint scripts to opt in or opt out of locale
-# dependence themselves.
-
-set -u
-
-SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
-LINTALL=$(basename "${BASH_SOURCE[0]}")
-
-EXIT_CODE=0
-
-for f in "${SCRIPTDIR}"/lint-*; do
- if [ "$(basename "$f")" != "$LINTALL" ]; then
- if ! "$f"; then
- echo "^---- failure generated from $f"
- EXIT_CODE=1
- fi
- fi
-done
-
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-assertions.py b/test/lint/lint-assertions.py
new file mode 100755
index 0000000000..195ff33d11
--- /dev/null
+++ b/test/lint/lint-assertions.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2018-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.
+#
+# Check for assertions with obvious side effects.
+
+import sys
+import subprocess
+
+
+def git_grep(params: [], error_msg: ""):
+ try:
+ output = subprocess.check_output(["git", "grep", *params], universal_newlines=True, encoding="utf8")
+ print(error_msg)
+ print(output)
+ return 1
+ except subprocess.CalledProcessError as ex1:
+ if ex1.returncode > 1:
+ raise ex1
+ return 0
+
+
+def main():
+ # PRE31-C (SEI CERT C Coding Standard):
+ # "Assertions should not contain assignments, increment, or decrement operators."
+ exit_code = git_grep([
+ "-E",
+ r"[^_]assert\(.*(\+\+|\-\-|[^=!<>]=[^=!<>]).*\);",
+ "--",
+ "*.cpp",
+ "*.h",
+ ], "Assertions should not have side effects:")
+
+ # Aborting the whole process is undesirable for RPC code. So nonfatal
+ # 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) *\(.*\);",
+ "--",
+ "src/rpc/",
+ "src/wallet/rpc*",
+ ":(exclude)src/rpc/server.cpp",
+ ], "CHECK_NONFATAL(condition) or NONFATAL_UNREACHABLE should be used instead of assert for RPC code.")
+
+ sys.exit(exit_code)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/lint/lint-assertions.sh b/test/lint/lint-assertions.sh
deleted file mode 100755
index 2860f5621b..0000000000
--- a/test/lint/lint-assertions.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018-2020 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#
-# Check for assertions with obvious side effects.
-
-export LC_ALL=C
-
-EXIT_CODE=0
-
-# PRE31-C (SEI CERT C Coding Standard):
-# "Assertions should not contain assignments, increment, or decrement operators."
-OUTPUT=$(git grep -E '[^_]assert\(.*(\+\+|\-\-|[^=!<>]=[^=!<>]).*\);' -- "*.cpp" "*.h")
-if [[ ${OUTPUT} != "" ]]; then
- echo "Assertions should not have side effects:"
- echo
- echo "${OUTPUT}"
- EXIT_CODE=1
-fi
-
-# Macro CHECK_NONFATAL(condition) should be used instead of assert for RPC code, where it
-# is undesirable to crash the whole program. See: src/util/check.h
-# src/rpc/server.cpp is excluded from this check since it's mostly meta-code.
-OUTPUT=$(git grep -nE '\<(A|a)ssert *\(.*\);' -- "src/rpc/" "src/wallet/rpc*" ":(exclude)src/rpc/server.cpp")
-if [[ ${OUTPUT} != "" ]]; then
- echo "CHECK_NONFATAL(condition) should be used instead of assert for RPC code."
- echo
- echo "${OUTPUT}"
- EXIT_CODE=1
-fi
-
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py
new file mode 100755
index 0000000000..5d157eb4b1
--- /dev/null
+++ b/test/lint/lint-circular-dependencies.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2020-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.
+#
+# Check for circular dependencies
+
+import os
+import re
+import subprocess
+import sys
+
+EXPECTED_CIRCULAR_DEPENDENCIES = (
+ "chainparamsbase -> util/system -> chainparamsbase",
+ "node/blockstorage -> validation -> node/blockstorage",
+ "policy/fees -> txmempool -> policy/fees",
+ "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel",
+ "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel",
+ "qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog",
+ "qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel",
+ "wallet/fees -> wallet/wallet -> wallet/fees",
+ "wallet/wallet -> wallet/walletdb -> wallet/wallet",
+ "kernel/coinstats -> validation -> kernel/coinstats",
+)
+
+CODE_DIR = "src"
+
+
+def main():
+ circular_dependencies = []
+ exit_code = 0
+
+ os.chdir(CODE_DIR)
+ files = subprocess.check_output(
+ ['git', 'ls-files', '--', '*.h', '*.cpp'],
+ universal_newlines=True,
+ ).splitlines()
+
+ command = [sys.executable, "../contrib/devtools/circular-dependencies.py", *files]
+ dependencies_output = subprocess.run(
+ command,
+ stdout=subprocess.PIPE,
+ universal_newlines=True,
+ )
+
+ for dependency_str in dependencies_output.stdout.rstrip().split("\n"):
+ circular_dependencies.append(
+ re.sub("^Circular dependency: ", "", dependency_str)
+ )
+
+ # Check for an unexpected dependencies
+ for dependency in circular_dependencies:
+ if dependency not in EXPECTED_CIRCULAR_DEPENDENCIES:
+ exit_code = 1
+ print(
+ f'A new circular dependency in the form of "{dependency}" appears to have been introduced.\n',
+ file=sys.stderr,
+ )
+
+ # Check for missing expected dependencies
+ for expected_dependency in EXPECTED_CIRCULAR_DEPENDENCIES:
+ if expected_dependency not in circular_dependencies:
+ exit_code = 1
+ print(
+ f'Good job! The circular dependency "{expected_dependency}" is no longer present.',
+ )
+ print(
+ f"Please remove it from EXPECTED_CIRCULAR_DEPENDENCIES in {__file__}",
+ )
+ print(
+ "to make sure this circular dependency is not accidentally reintroduced.\n",
+ )
+
+ sys.exit(exit_code)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
deleted file mode 100755
index 69185090d1..0000000000
--- a/test/lint/lint-circular-dependencies.sh
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018-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.
-#
-# Check for circular dependencies
-
-export LC_ALL=C
-
-EXPECTED_CIRCULAR_DEPENDENCIES=(
- "chainparamsbase -> util/system -> chainparamsbase"
- "node/blockstorage -> validation -> node/blockstorage"
- "index/blockfilterindex -> node/blockstorage -> validation -> index/blockfilterindex"
- "index/base -> validation -> index/blockfilterindex -> index/base"
- "index/coinstatsindex -> node/coinstats -> index/coinstatsindex"
- "policy/fees -> txmempool -> policy/fees"
- "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel"
- "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel"
- "qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog"
- "qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel"
- "wallet/fees -> wallet/wallet -> wallet/fees"
- "wallet/wallet -> wallet/walletdb -> wallet/wallet"
- "node/coinstats -> validation -> node/coinstats"
-)
-
-EXIT_CODE=0
-
-CIRCULAR_DEPENDENCIES=()
-
-IFS=$'\n'
-for CIRC in $(cd src && ../contrib/devtools/circular-dependencies.py {*,*/*,*/*/*}.{h,cpp} | sed -e 's/^Circular dependency: //'); do
- CIRCULAR_DEPENDENCIES+=( "$CIRC" )
- IS_EXPECTED_CIRC=0
- for EXPECTED_CIRC in "${EXPECTED_CIRCULAR_DEPENDENCIES[@]}"; do
- if [[ "${CIRC}" == "${EXPECTED_CIRC}" ]]; then
- IS_EXPECTED_CIRC=1
- break
- fi
- done
- if [[ ${IS_EXPECTED_CIRC} == 0 ]]; then
- echo "A new circular dependency in the form of \"${CIRC}\" appears to have been introduced."
- echo
- EXIT_CODE=1
- fi
-done
-
-for EXPECTED_CIRC in "${EXPECTED_CIRCULAR_DEPENDENCIES[@]}"; do
- IS_PRESENT_EXPECTED_CIRC=0
- for CIRC in "${CIRCULAR_DEPENDENCIES[@]}"; do
- if [[ "${CIRC}" == "${EXPECTED_CIRC}" ]]; then
- IS_PRESENT_EXPECTED_CIRC=1
- break
- fi
- done
- if [[ ${IS_PRESENT_EXPECTED_CIRC} == 0 ]]; then
- echo "Good job! The circular dependency \"${EXPECTED_CIRC}\" is no longer present."
- echo "Please remove it from EXPECTED_CIRCULAR_DEPENDENCIES in $0"
- echo "to make sure this circular dependency is not accidentally reintroduced."
- echo
- EXIT_CODE=1
- fi
-done
-
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-files.py b/test/lint/lint-files.py
index 3723e0ee6a..123bee2cbc 100755
--- a/test/lint/lint-files.py
+++ b/test/lint/lint-files.py
@@ -11,19 +11,20 @@ import os
import re
import sys
from subprocess import check_output
-from typing import Optional, NoReturn
+from typing import Dict, Optional, NoReturn
CMD_TOP_LEVEL = ["git", "rev-parse", "--show-toplevel"]
-CMD_ALL_FILES = "git ls-files -z --full-name"
-CMD_SOURCE_FILES = 'git ls-files -z --full-name -- "*.[cC][pP][pP]" "*.[hH]" "*.[pP][yY]" "*.[sS][hH]"'
-CMD_SHEBANG_FILES = "git grep --full-name --line-number -I '^#!'"
+CMD_ALL_FILES = ["git", "ls-files", "-z", "--full-name", "--stage"]
+CMD_SHEBANG_FILES = ["git", "grep", "--full-name", "--line-number", "-I", "^#!"]
+
+ALL_SOURCE_FILENAMES_REGEXP = r"^.*\.(cpp|h|py|sh)$"
ALLOWED_FILENAME_REGEXP = "^[a-zA-Z0-9/_.@][a-zA-Z0-9/_.@-]*$"
ALLOWED_SOURCE_FILENAME_REGEXP = "^[a-z0-9_./-]+$"
ALLOWED_SOURCE_FILENAME_EXCEPTION_REGEXP = (
- "^src/(secp256k1/|minisketch/|univalue/|test/fuzz/FuzzedDataProvider.h)"
+ "^src/(secp256k1/|minisketch/|test/fuzz/FuzzedDataProvider.h)"
)
-ALLOWED_PERMISSION_NON_EXECUTABLES = 644
-ALLOWED_PERMISSION_EXECUTABLES = 755
+ALLOWED_PERMISSION_NON_EXECUTABLES = 0o644
+ALLOWED_PERMISSION_EXECUTABLES = 0o755
ALLOWED_EXECUTABLE_SHEBANG = {
"py": [b"#!/usr/bin/env python3"],
"sh": [b"#!/usr/bin/env bash", b"#!/bin/sh"],
@@ -31,8 +32,15 @@ ALLOWED_EXECUTABLE_SHEBANG = {
class FileMeta(object):
- def __init__(self, file_path: str):
- self.file_path = file_path
+ def __init__(self, file_spec: str):
+ '''Parse a `git ls files --stage` output line.'''
+ # 100755 5a150d5f8031fcd75e80a4dd9843afa33655f579 0 ci/test/00_setup_env.sh
+ meta, self.file_path = file_spec.split('\t', 2)
+ meta = meta.split()
+ # The octal file permission of the file. Internally, git only
+ # keeps an 'executable' bit, so this will always be 0o644 or 0o755.
+ self.permissions = int(meta[0], 8) & 0o7777
+ # We don't currently care about the other fields
@property
def extension(self) -> Optional[str]:
@@ -60,20 +68,24 @@ class FileMeta(object):
except IndexError:
return None
- @property
- def permissions(self) -> int:
- """
- Returns the octal file permission of the file
- """
- return int(oct(os.stat(self.file_path).st_mode)[-3:])
+def get_git_file_metadata() -> Dict[str, FileMeta]:
+ '''
+ Return a dictionary mapping the name of all files in the repository to git tree metadata.
+ '''
+ files_raw = check_output(CMD_ALL_FILES).decode("utf8").rstrip("\0").split("\0")
+ files = {}
+ for file_spec in files_raw:
+ meta = FileMeta(file_spec)
+ files[meta.file_path] = meta
+ return files
-def check_all_filenames() -> int:
+def check_all_filenames(files) -> int:
"""
Checks every file in the repository against an allowed regexp to make sure only lowercase or uppercase
alphanumerics (a-zA-Z0-9), underscores (_), hyphens (-), at (@) and dots (.) are used in repository filenames.
"""
- filenames = check_output(CMD_ALL_FILES, shell=True).decode("utf8").rstrip("\0").split("\0")
+ filenames = files.keys()
filename_regex = re.compile(ALLOWED_FILENAME_REGEXP)
failed_tests = 0
for filename in filenames:
@@ -85,14 +97,14 @@ def check_all_filenames() -> int:
return failed_tests
-def check_source_filenames() -> int:
+def check_source_filenames(files) -> int:
"""
Checks only source files (*.cpp, *.h, *.py, *.sh) against a stricter allowed regexp to make sure only lowercase
alphanumerics (a-z0-9), underscores (_), hyphens (-) and dots (.) are used in source code filenames.
Additionally there is an exception regexp for directories or files which are excepted from matching this regexp.
"""
- filenames = check_output(CMD_SOURCE_FILES, shell=True).decode("utf8").rstrip("\0").split("\0")
+ filenames = [filename for filename in files.keys() if re.match(ALL_SOURCE_FILENAMES_REGEXP, filename, re.IGNORECASE)]
filename_regex = re.compile(ALLOWED_SOURCE_FILENAME_REGEXP)
filename_exception_regex = re.compile(ALLOWED_SOURCE_FILENAME_EXCEPTION_REGEXP)
failed_tests = 0
@@ -105,16 +117,14 @@ def check_source_filenames() -> int:
return failed_tests
-def check_all_file_permissions() -> int:
+def check_all_file_permissions(files) -> int:
"""
Checks all files in the repository match an allowed executable or non-executable file permission octal.
Additionally checks that for executable files, the file contains a shebang line
"""
- filenames = check_output(CMD_ALL_FILES, shell=True).decode("utf8").rstrip("\0").split("\0")
failed_tests = 0
- for filename in filenames:
- file_meta = FileMeta(filename)
+ for filename, file_meta in files.items():
if file_meta.permissions == ALLOWED_PERMISSION_EXECUTABLES:
with open(filename, "rb") as f:
shebang = f.readline().rstrip(b"\n")
@@ -122,7 +132,7 @@ def check_all_file_permissions() -> int:
# For any file with executable permissions the first line must contain a shebang
if not shebang.startswith(b"#!"):
print(
- f"""File "{filename}" has permission {ALLOWED_PERMISSION_EXECUTABLES} (executable) and is thus expected to contain a shebang '#!'. Add shebang or do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES} {filename}" to make it non-executable."""
+ f"""File "{filename}" has permission {ALLOWED_PERMISSION_EXECUTABLES:03o} (executable) and is thus expected to contain a shebang '#!'. Add shebang or do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES:03o} {filename}" to make it non-executable."""
)
failed_tests += 1
@@ -145,18 +155,18 @@ def check_all_file_permissions() -> int:
continue
else:
print(
- f"""File "{filename}" has unexpected permission {file_meta.permissions}. Do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES} {filename}" (if non-executable) or "chmod {ALLOWED_PERMISSION_EXECUTABLES} {filename}" (if executable)."""
+ f"""File "{filename}" has unexpected permission {file_meta.permissions:03o}. Do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES:03o} {filename}" (if non-executable) or "chmod {ALLOWED_PERMISSION_EXECUTABLES:03o} {filename}" (if executable)."""
)
failed_tests += 1
return failed_tests
-def check_shebang_file_permissions() -> int:
+def check_shebang_file_permissions(files_meta) -> int:
"""
Checks every file that contains a shebang line to ensure it has an executable permission
"""
- filenames = check_output(CMD_SHEBANG_FILES, shell=True).decode("utf8").strip().split("\n")
+ filenames = check_output(CMD_SHEBANG_FILES).decode("utf8").strip().split("\n")
# The git grep command we use returns files which contain a shebang on any line within the file
# so we need to filter the list to only files with the shebang on the first line
@@ -164,7 +174,7 @@ def check_shebang_file_permissions() -> int:
failed_tests = 0
for filename in filenames:
- file_meta = FileMeta(filename)
+ file_meta = files_meta[filename]
if file_meta.permissions != ALLOWED_PERMISSION_EXECUTABLES:
# These file types are typically expected to be sourced and not executed directly
if file_meta.full_extension in ["bash", "init", "openrc", "sh.in"]:
@@ -178,7 +188,7 @@ def check_shebang_file_permissions() -> int:
continue
print(
- f"""File "{filename}" contains a shebang line, but has the file permission {file_meta.permissions} instead of the expected executable permission {ALLOWED_PERMISSION_EXECUTABLES}. Do "chmod {ALLOWED_PERMISSION_EXECUTABLES} {filename}" (or remove the shebang line)."""
+ f"""File "{filename}" contains a shebang line, but has the file permission {file_meta.permissions:03o} instead of the expected executable permission {ALLOWED_PERMISSION_EXECUTABLES:03o}. Do "chmod {ALLOWED_PERMISSION_EXECUTABLES:03o} {filename}" (or remove the shebang line)."""
)
failed_tests += 1
return failed_tests
@@ -187,11 +197,14 @@ def check_shebang_file_permissions() -> int:
def main() -> NoReturn:
root_dir = check_output(CMD_TOP_LEVEL).decode("utf8").strip()
os.chdir(root_dir)
+
+ files = get_git_file_metadata()
+
failed_tests = 0
- failed_tests += check_all_filenames()
- failed_tests += check_source_filenames()
- failed_tests += check_all_file_permissions()
- failed_tests += check_shebang_file_permissions()
+ failed_tests += check_all_filenames(files)
+ failed_tests += check_source_filenames(files)
+ failed_tests += check_all_file_permissions(files)
+ failed_tests += check_shebang_file_permissions(files)
if failed_tests:
print(
diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py
new file mode 100755
index 0000000000..f54126e023
--- /dev/null
+++ b/test/lint/lint-format-strings.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2018-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.
+#
+
+"""
+Lint format strings: This program checks that the number of arguments passed
+to a variadic format string function matches the number of format specifiers
+in the format string.
+"""
+
+import subprocess
+import re
+import sys
+
+FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS = [
+ 'FatalError,0',
+ 'fprintf,1',
+ 'tfm::format,1', # Assuming tfm::::format(std::ostream&, ...
+ 'LogConnectFailure,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'
+
+def check_doctest():
+ command = [
+ sys.executable,
+ '-m',
+ 'doctest',
+ RUN_LINT_FILE,
+ ]
+ try:
+ subprocess.run(command, check = True)
+ except subprocess.CalledProcessError:
+ sys.exit(1)
+
+def get_matching_files(function_name):
+ command = [
+ 'git',
+ 'grep',
+ '--full-name',
+ '-l',
+ function_name,
+ '--',
+ '*.c',
+ '*.cpp',
+ '*.h',
+ ]
+ try:
+ return subprocess.check_output(command, stderr = subprocess.STDOUT).decode('utf-8').splitlines()
+ except subprocess.CalledProcessError as e:
+ if e.returncode > 1: # return code is 1 when match is empty
+ print(e.output.decode('utf-8'), end='')
+ sys.exit(1)
+ return []
+
+def main():
+ exit_code = 0
+ check_doctest()
+ for s in FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS:
+ function_name, skip_arguments = s.split(',')
+ matching_files = get_matching_files(function_name)
+
+ matching_files_filtered = []
+ for matching_file in matching_files:
+ 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()
+
+ run_lint_args = [
+ RUN_LINT_FILE,
+ '--skip-arguments',
+ skip_arguments,
+ function_name,
+ ]
+ run_lint_args.extend(matching_files_filtered)
+
+ try:
+ subprocess.run(run_lint_args, check = True)
+ except subprocess.CalledProcessError:
+ exit_code = 1
+
+ sys.exit(exit_code)
+
+if __name__ == '__main__':
+ main()
diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh
deleted file mode 100755
index 73730f16b3..0000000000
--- a/test/lint/lint-format-strings.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018-2020 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#
-# Lint format strings: This program checks that the number of arguments passed
-# to a variadic format string function matches the number of format specifiers
-# in the format string.
-
-export LC_ALL=C
-
-FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS=(
- "FatalError,0"
- "fprintf,1"
- "tfm::format,1" # Assuming tfm::::format(std::ostream&, ...
- "LogConnectFailure,1"
- "LogPrint,1"
- "LogPrintf,0"
- "printf,0"
- "snprintf,2"
- "sprintf,1"
- "strprintf,0"
- "vfprintf,1"
- "vprintf,1"
- "vsnprintf,1"
- "vsprintf,1"
- "WalletLogPrintf,0"
-)
-
-EXIT_CODE=0
-if ! python3 -m doctest "test/lint/run-lint-format-strings.py"; then
- EXIT_CODE=1
-fi
-for S in "${FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS[@]}"; do
- IFS="," read -r FUNCTION_NAME SKIP_ARGUMENTS <<< "${S}"
- for MATCHING_FILE in $(git grep --full-name -l "${FUNCTION_NAME}" -- "*.c" "*.cpp" "*.h" | sort | grep -vE "^src/(leveldb|secp256k1|minisketch|tinyformat|univalue|test/fuzz/strprintf.cpp)"); do
- MATCHING_FILES+=("${MATCHING_FILE}")
- done
- if ! "test/lint/run-lint-format-strings.py" --skip-arguments "${SKIP_ARGUMENTS}" "${FUNCTION_NAME}" "${MATCHING_FILES[@]}"; then
- EXIT_CODE=1
- fi
-done
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-include-guards.py b/test/lint/lint-include-guards.py
new file mode 100755
index 0000000000..5867aae028
--- /dev/null
+++ b/test/lint/lint-include-guards.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2018-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.
+
+"""
+Check include guards.
+"""
+
+import re
+import sys
+from subprocess import check_output
+from typing import List
+
+
+HEADER_ID_PREFIX = 'BITCOIN_'
+HEADER_ID_SUFFIX = '_H'
+
+EXCLUDE_FILES_WITH_PREFIX = ['src/crypto/ctaes',
+ 'src/leveldb',
+ 'src/crc32c',
+ 'src/secp256k1',
+ 'src/minisketch',
+ 'src/tinyformat.h',
+ 'src/bench/nanobench.h',
+ 'src/test/fuzz/FuzzedDataProvider.h']
+
+
+def _get_header_file_lst() -> List[str]:
+ """ Helper function to get a list of header filepaths to be
+ checked for include guards.
+ """
+ git_cmd_lst = ['git', 'ls-files', '--', '*.h']
+ header_file_lst = check_output(
+ git_cmd_lst).decode('utf-8').splitlines()
+
+ header_file_lst = [hf for hf in header_file_lst
+ if not any(ef in hf for ef
+ in EXCLUDE_FILES_WITH_PREFIX)]
+
+ return header_file_lst
+
+
+def _get_header_id(header_file: str) -> str:
+ """ Helper function to get the header id from a header file
+ string.
+
+ eg: 'src/wallet/walletdb.h' -> 'BITCOIN_WALLET_WALLETDB_H'
+
+ Args:
+ header_file: Filepath to header file.
+
+ Returns:
+ The header id.
+ """
+ header_id_base = header_file.split('/')[1:]
+ header_id_base = '_'.join(header_id_base)
+ header_id_base = header_id_base.replace('.h', '').replace('-', '_')
+ header_id_base = header_id_base.upper()
+
+ header_id = f'{HEADER_ID_PREFIX}{header_id_base}{HEADER_ID_SUFFIX}'
+
+ return header_id
+
+
+def main():
+ exit_code = 0
+
+ header_file_lst = _get_header_file_lst()
+ for header_file in header_file_lst:
+ header_id = _get_header_id(header_file)
+
+ regex_pattern = f'^#(ifndef|define|endif //) {header_id}'
+
+ with open(header_file, 'r', encoding='utf-8') as f:
+ header_file_contents = f.readlines()
+
+ count = 0
+ for header_file_contents_string in header_file_contents:
+ include_guard_lst = re.findall(
+ regex_pattern, header_file_contents_string)
+
+ count += len(include_guard_lst)
+
+ if count != 3:
+ print(f'{header_file} seems to be missing the expected '
+ 'include guard:')
+ print(f' #ifndef {header_id}')
+ print(f' #define {header_id}')
+ print(' ...')
+ print(f' #endif // {header_id}\n')
+ exit_code = 1
+
+ sys.exit(exit_code)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/lint/lint-include-guards.sh b/test/lint/lint-include-guards.sh
deleted file mode 100755
index f14218aa74..0000000000
--- a/test/lint/lint-include-guards.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018-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.
-#
-# Check include guards.
-
-export LC_ALL=C
-HEADER_ID_PREFIX="BITCOIN_"
-HEADER_ID_SUFFIX="_H"
-
-REGEXP_EXCLUDE_FILES_WITH_PREFIX="src/(crypto/ctaes/|leveldb/|crc32c/|secp256k1/|minisketch/|test/fuzz/FuzzedDataProvider.h|tinyformat.h|bench/nanobench.h|univalue/)"
-
-EXIT_CODE=0
-for HEADER_FILE in $(git ls-files -- "*.h" | grep -vE "^${REGEXP_EXCLUDE_FILES_WITH_PREFIX}")
-do
- HEADER_ID_BASE=$(cut -f2- -d/ <<< "${HEADER_FILE}" | sed "s/\.h$//g" | tr / _ | tr - _ | tr "[:lower:]" "[:upper:]")
- HEADER_ID="${HEADER_ID_PREFIX}${HEADER_ID_BASE}${HEADER_ID_SUFFIX}"
- if [[ $(grep --count --extended-regexp "^#(ifndef|define|endif //) ${HEADER_ID}" "${HEADER_FILE}") != 3 ]]; then
- echo "${HEADER_FILE} seems to be missing the expected include guard:"
- echo " #ifndef ${HEADER_ID}"
- echo " #define ${HEADER_ID}"
- echo " ..."
- echo " #endif // ${HEADER_ID}"
- echo
- EXIT_CODE=1
- fi
-done
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-includes.py b/test/lint/lint-includes.py
index b29c7f8b4d..afdca0d418 100755
--- a/test/lint/lint-includes.py
+++ b/test/lint/lint-includes.py
@@ -19,12 +19,9 @@ EXCLUDED_DIRS = ["src/leveldb/",
"src/crc32c/",
"src/secp256k1/",
"src/minisketch/",
- "src/univalue/"]
+ ]
-EXPECTED_BOOST_INCLUDES = ["boost/algorithm/string.hpp",
- "boost/algorithm/string/classification.hpp",
- "boost/algorithm/string/replace.hpp",
- "boost/algorithm/string/split.hpp",
+EXPECTED_BOOST_INCLUDES = ["boost/algorithm/string/replace.hpp",
"boost/date_time/posix_time/posix_time.hpp",
"boost/multi_index/hashed_index.hpp",
"boost/multi_index/ordered_index.hpp",
diff --git a/test/lint/lint-locale-dependence.py b/test/lint/lint-locale-dependence.py
new file mode 100755
index 0000000000..4876ac2e2d
--- /dev/null
+++ b/test/lint/lint-locale-dependence.py
@@ -0,0 +1,260 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018-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.
+#
+# Be aware that bitcoind and bitcoin-qt differ in terms of localization: Qt
+# opts in to POSIX localization by running setlocale(LC_ALL, "") on startup,
+# whereas no such call is made in bitcoind.
+#
+# Qt runs setlocale(LC_ALL, "") on initialization. This installs the locale
+# specified by the user's LC_ALL (or LC_*) environment variable as the new
+# C locale.
+#
+# In contrast, bitcoind does not opt in to localization -- no call to
+# setlocale(LC_ALL, "") is made and the environment variables LC_* are
+# thus ignored.
+#
+# This results in situations where bitcoind is guaranteed to be running
+# with the classic locale ("C") whereas the locale of bitcoin-qt will vary
+# depending on the user's environment variables.
+#
+# An example: Assuming the environment variable LC_ALL=de_DE then the
+# call std::to_string(1.23) will return "1.230000" in bitcoind but
+# "1,230000" in bitcoin-qt.
+#
+# From the Qt documentation:
+# "On Unix/Linux Qt is configured to use the system locale settings by default.
+# This can cause a conflict when using POSIX functions, for instance, when
+# converting between data types such as floats and strings, since the notation
+# may differ between locales. To get around this problem, call the POSIX function
+# setlocale(LC_NUMERIC,"C") right after initializing QApplication, QGuiApplication
+# or QCoreApplication to reset the locale that is used for number formatting to
+# "C"-locale."
+#
+# See https://doc.qt.io/qt-5/qcoreapplication.html#locale-settings and
+# https://stackoverflow.com/a/34878283 for more details.
+#
+# TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent snprintf with strprintf.
+
+import re
+import sys
+
+from subprocess import check_output, CalledProcessError
+
+
+KNOWN_VIOLATIONS = [
+ "src/dbwrapper.cpp:.*vsnprintf",
+ "src/test/dbwrapper_tests.cpp:.*snprintf",
+ "src/test/fuzz/locale.cpp:.*setlocale",
+ "src/test/fuzz/string.cpp:.*strtol",
+ "src/test/fuzz/string.cpp:.*strtoul",
+ "src/test/util_tests.cpp:.*strtoll",
+ "src/wallet/bdb.cpp:.*DbEnv::strerror", # False positive
+ "src/util/syserror.cpp:.*strerror", # Outside this function use `SysErrorString`
+]
+
+REGEXP_EXTERNAL_DEPENDENCIES_EXCLUSIONS = [
+ "src/crypto/ctaes/",
+ "src/leveldb/",
+ "src/secp256k1/",
+ "src/minisketch/",
+ "src/tinyformat.h",
+]
+
+LOCALE_DEPENDENT_FUNCTIONS = [
+ "alphasort", # LC_COLLATE (via strcoll)
+ "asctime", # LC_TIME (directly)
+ "asprintf", # (via vasprintf)
+ "atof", # LC_NUMERIC (via strtod)
+ "atoi", # LC_NUMERIC (via strtol)
+ "atol", # LC_NUMERIC (via strtol)
+ "atoll", # (via strtoll)
+ "atoq",
+ "btowc", # LC_CTYPE (directly)
+ "ctime", # (via asctime or localtime)
+ "dprintf", # (via vdprintf)
+ "fgetwc",
+ "fgetws",
+ "fold_case", # boost::locale::fold_case
+ "fprintf", # (via vfprintf)
+ "fputwc",
+ "fputws",
+ "fscanf", # (via __vfscanf)
+ "fwprintf", # (via __vfwprintf)
+ "getdate", # via __getdate_r => isspace // __localtime_r
+ "getwc",
+ "getwchar",
+ "is_digit", # boost::algorithm::is_digit
+ "is_space", # boost::algorithm::is_space
+ "isalnum", # LC_CTYPE
+ "isalpha", # LC_CTYPE
+ "isblank", # LC_CTYPE
+ "iscntrl", # LC_CTYPE
+ "isctype", # LC_CTYPE
+ "isdigit", # LC_CTYPE
+ "isgraph", # LC_CTYPE
+ "islower", # LC_CTYPE
+ "isprint", # LC_CTYPE
+ "ispunct", # LC_CTYPE
+ "isspace", # LC_CTYPE
+ "isupper", # LC_CTYPE
+ "iswalnum", # LC_CTYPE
+ "iswalpha", # LC_CTYPE
+ "iswblank", # LC_CTYPE
+ "iswcntrl", # LC_CTYPE
+ "iswctype", # LC_CTYPE
+ "iswdigit", # LC_CTYPE
+ "iswgraph", # LC_CTYPE
+ "iswlower", # LC_CTYPE
+ "iswprint", # LC_CTYPE
+ "iswpunct", # LC_CTYPE
+ "iswspace", # LC_CTYPE
+ "iswupper", # LC_CTYPE
+ "iswxdigit", # LC_CTYPE
+ "isxdigit", # LC_CTYPE
+ "localeconv", # LC_NUMERIC + LC_MONETARY
+ "mblen", # LC_CTYPE
+ "mbrlen",
+ "mbrtowc",
+ "mbsinit",
+ "mbsnrtowcs",
+ "mbsrtowcs",
+ "mbstowcs", # LC_CTYPE
+ "mbtowc", # LC_CTYPE
+ "mktime",
+ "normalize", # boost::locale::normalize
+ "printf", # LC_NUMERIC
+ "putwc",
+ "putwchar",
+ "scanf", # LC_NUMERIC
+ "setlocale",
+ "snprintf",
+ "sprintf",
+ "sscanf",
+ "std::locale::global",
+ "std::to_string",
+ "stod",
+ "stof",
+ "stoi",
+ "stol",
+ "stold",
+ "stoll",
+ "stoul",
+ "stoull",
+ "strcasecmp",
+ "strcasestr",
+ "strcoll", # LC_COLLATE
+ "strerror",
+ "strfmon",
+ "strftime", # LC_TIME
+ "strncasecmp",
+ "strptime",
+ "strtod", # LC_NUMERIC
+ "strtof",
+ "strtoimax",
+ "strtol", # LC_NUMERIC
+ "strtold",
+ "strtoll",
+ "strtoq",
+ "strtoul", # LC_NUMERIC
+ "strtoull",
+ "strtoumax",
+ "strtouq",
+ "strxfrm", # LC_COLLATE
+ "swprintf",
+ "to_lower", # boost::locale::to_lower
+ "to_title", # boost::locale::to_title
+ "to_upper", # boost::locale::to_upper
+ "tolower", # LC_CTYPE
+ "toupper", # LC_CTYPE
+ "towctrans",
+ "towlower", # LC_CTYPE
+ "towupper", # LC_CTYPE
+ "trim", # boost::algorithm::trim
+ "trim_left", # boost::algorithm::trim_left
+ "trim_right", # boost::algorithm::trim_right
+ "ungetwc",
+ "vasprintf",
+ "vdprintf",
+ "versionsort",
+ "vfprintf",
+ "vfscanf",
+ "vfwprintf",
+ "vprintf",
+ "vscanf",
+ "vsnprintf",
+ "vsprintf",
+ "vsscanf",
+ "vswprintf",
+ "vwprintf",
+ "wcrtomb",
+ "wcscasecmp",
+ "wcscoll", # LC_COLLATE
+ "wcsftime", # LC_TIME
+ "wcsncasecmp",
+ "wcsnrtombs",
+ "wcsrtombs",
+ "wcstod", # LC_NUMERIC
+ "wcstof",
+ "wcstoimax",
+ "wcstol", # LC_NUMERIC
+ "wcstold",
+ "wcstoll",
+ "wcstombs", # LC_CTYPE
+ "wcstoul", # LC_NUMERIC
+ "wcstoull",
+ "wcstoumax",
+ "wcswidth",
+ "wcsxfrm", # LC_COLLATE
+ "wctob",
+ "wctomb", # LC_CTYPE
+ "wctrans",
+ "wctype",
+ "wcwidth",
+ "wprintf"
+]
+
+
+def find_locale_dependent_function_uses():
+ regexp_locale_dependent_functions = "|".join(LOCALE_DEPENDENT_FUNCTIONS)
+ exclude_args = [":(exclude)" + excl for excl in REGEXP_EXTERNAL_DEPENDENCIES_EXCLUSIONS]
+ git_grep_command = ["git", "grep", "-E", "[^a-zA-Z0-9_\\`'\"<>](" + regexp_locale_dependent_functions + ")(_r|_s)?[^a-zA-Z0-9_\\`'\"<>]", "--", "*.cpp", "*.h"] + exclude_args
+ git_grep_output = list()
+
+ try:
+ git_grep_output = check_output(git_grep_command, universal_newlines=True, encoding="utf8").splitlines()
+ except CalledProcessError as e:
+ if e.returncode > 1:
+ raise e
+
+ return git_grep_output
+
+
+def main():
+ exit_code = 0
+
+ regexp_ignore_known_violations = "|".join(KNOWN_VIOLATIONS)
+ git_grep_output = find_locale_dependent_function_uses()
+
+ for locale_dependent_function in LOCALE_DEPENDENT_FUNCTIONS:
+ matches = [line for line in git_grep_output
+ if re.search("[^a-zA-Z0-9_\\`'\"<>]" + locale_dependent_function + "(_r|_s)?[^a-zA-Z0-9_\\`'\"<>]", line)
+ and not re.search("\\.(c|cpp|h):\\s*(//|\\*|/\\*|\").*" + locale_dependent_function, line)
+ and not re.search(regexp_ignore_known_violations, line)]
+ if matches:
+ print(f"The locale dependent function {locale_dependent_function}(...) appears to be used:")
+ for match in matches:
+ print(match)
+ print("")
+ exit_code = 1
+
+ if exit_code == 1:
+ print("Unnecessary locale depedence can cause bugs that are very tricky to isolate and fix. Please avoid using locale dependent functions if possible.\n")
+ print(f"Advice not applicable in this specific case? Add an exception by updating the ignore list in {sys.argv[0]}")
+
+ sys.exit(exit_code)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh
deleted file mode 100755
index 7d608eed6a..0000000000
--- a/test/lint/lint-locale-dependence.sh
+++ /dev/null
@@ -1,241 +0,0 @@
-#!/usr/bin/env bash
-# Copyright (c) 2018-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.
-
-export LC_ALL=C
-
-# Be aware that bitcoind and bitcoin-qt differ in terms of localization: Qt
-# opts in to POSIX localization by running setlocale(LC_ALL, "") on startup,
-# whereas no such call is made in bitcoind.
-#
-# Qt runs setlocale(LC_ALL, "") on initialization. This installs the locale
-# specified by the user's LC_ALL (or LC_*) environment variable as the new
-# C locale.
-#
-# In contrast, bitcoind does not opt in to localization -- no call to
-# setlocale(LC_ALL, "") is made and the environment variables LC_* are
-# thus ignored.
-#
-# This results in situations where bitcoind is guaranteed to be running
-# with the classic locale ("C") whereas the locale of bitcoin-qt will vary
-# depending on the user's environment variables.
-#
-# An example: Assuming the environment variable LC_ALL=de_DE then the
-# call std::to_string(1.23) will return "1.230000" in bitcoind but
-# "1,230000" in bitcoin-qt.
-#
-# From the Qt documentation:
-# "On Unix/Linux Qt is configured to use the system locale settings by default.
-# This can cause a conflict when using POSIX functions, for instance, when
-# converting between data types such as floats and strings, since the notation
-# may differ between locales. To get around this problem, call the POSIX function
-# setlocale(LC_NUMERIC,"C") right after initializing QApplication, QGuiApplication
-# or QCoreApplication to reset the locale that is used for number formatting to
-# "C"-locale."
-#
-# See https://doc.qt.io/qt-5/qcoreapplication.html#locale-settings and
-# https://stackoverflow.com/a/34878283 for more details.
-
-# TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent snprintf with strprintf.
-KNOWN_VIOLATIONS=(
- "src/dbwrapper.cpp:.*vsnprintf"
- "src/test/dbwrapper_tests.cpp:.*snprintf"
- "src/test/fuzz/locale.cpp"
- "src/test/fuzz/string.cpp"
- "src/test/util_tests.cpp"
-)
-
-REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="^src/(crypto/ctaes/|leveldb/|secp256k1/|minisketch/|tinyformat.h|univalue/)"
-
-LOCALE_DEPENDENT_FUNCTIONS=(
- alphasort # LC_COLLATE (via strcoll)
- asctime # LC_TIME (directly)
- asprintf # (via vasprintf)
- atof # LC_NUMERIC (via strtod)
- atoi # LC_NUMERIC (via strtol)
- atol # LC_NUMERIC (via strtol)
- atoll # (via strtoll)
- atoq
- btowc # LC_CTYPE (directly)
- ctime # (via asctime or localtime)
- dprintf # (via vdprintf)
- fgetwc
- fgetws
- fold_case # boost::locale::fold_case
- fprintf # (via vfprintf)
- fputwc
- fputws
- fscanf # (via __vfscanf)
- fwprintf # (via __vfwprintf)
- getdate # via __getdate_r => isspace // __localtime_r
- getwc
- getwchar
- is_digit # boost::algorithm::is_digit
- is_space # boost::algorithm::is_space
- isalnum # LC_CTYPE
- isalpha # LC_CTYPE
- isblank # LC_CTYPE
- iscntrl # LC_CTYPE
- isctype # LC_CTYPE
- isdigit # LC_CTYPE
- isgraph # LC_CTYPE
- islower # LC_CTYPE
- isprint # LC_CTYPE
- ispunct # LC_CTYPE
- isspace # LC_CTYPE
- isupper # LC_CTYPE
- iswalnum # LC_CTYPE
- iswalpha # LC_CTYPE
- iswblank # LC_CTYPE
- iswcntrl # LC_CTYPE
- iswctype # LC_CTYPE
- iswdigit # LC_CTYPE
- iswgraph # LC_CTYPE
- iswlower # LC_CTYPE
- iswprint # LC_CTYPE
- iswpunct # LC_CTYPE
- iswspace # LC_CTYPE
- iswupper # LC_CTYPE
- iswxdigit # LC_CTYPE
- isxdigit # LC_CTYPE
- localeconv # LC_NUMERIC + LC_MONETARY
- mblen # LC_CTYPE
- mbrlen
- mbrtowc
- mbsinit
- mbsnrtowcs
- mbsrtowcs
- mbstowcs # LC_CTYPE
- mbtowc # LC_CTYPE
- mktime
- normalize # boost::locale::normalize
- printf # LC_NUMERIC
- putwc
- putwchar
- scanf # LC_NUMERIC
- setlocale
- snprintf
- sprintf
- sscanf
- std::locale::global
- std::to_string
- stod
- stof
- stoi
- stol
- stold
- stoll
- stoul
- stoull
- strcasecmp
- strcasestr
- strcoll # LC_COLLATE
-# strerror
- strfmon
- strftime # LC_TIME
- strncasecmp
- strptime
- strtod # LC_NUMERIC
- strtof
- strtoimax
- strtol # LC_NUMERIC
- strtold
- strtoll
- strtoq
- strtoul # LC_NUMERIC
- strtoull
- strtoumax
- strtouq
- strxfrm # LC_COLLATE
- swprintf
- to_lower # boost::locale::to_lower
- to_title # boost::locale::to_title
- to_upper # boost::locale::to_upper
- tolower # LC_CTYPE
- toupper # LC_CTYPE
- towctrans
- towlower # LC_CTYPE
- towupper # LC_CTYPE
- trim # boost::algorithm::trim
- trim_left # boost::algorithm::trim_left
- trim_right # boost::algorithm::trim_right
- ungetwc
- vasprintf
- vdprintf
- versionsort
- vfprintf
- vfscanf
- vfwprintf
- vprintf
- vscanf
- vsnprintf
- vsprintf
- vsscanf
- vswprintf
- vwprintf
- wcrtomb
- wcscasecmp
- wcscoll # LC_COLLATE
- wcsftime # LC_TIME
- wcsncasecmp
- wcsnrtombs
- wcsrtombs
- wcstod # LC_NUMERIC
- wcstof
- wcstoimax
- wcstol # LC_NUMERIC
- wcstold
- wcstoll
- wcstombs # LC_CTYPE
- wcstoul # LC_NUMERIC
- wcstoull
- wcstoumax
- wcswidth
- wcsxfrm # LC_COLLATE
- wctob
- wctomb # LC_CTYPE
- wctrans
- wctype
- wcwidth
- wprintf
-)
-
-function join_array {
- local IFS="$1"
- shift
- echo "$*"
-}
-
-REGEXP_IGNORE_KNOWN_VIOLATIONS=$(join_array "|" "${KNOWN_VIOLATIONS[@]}")
-
-# Invoke "git grep" only once in order to minimize run-time
-REGEXP_LOCALE_DEPENDENT_FUNCTIONS=$(join_array "|" "${LOCALE_DEPENDENT_FUNCTIONS[@]}")
-GIT_GREP_OUTPUT=$(git grep -E "[^a-zA-Z0-9_\`'\"<>](${REGEXP_LOCALE_DEPENDENT_FUNCTIONS}(_r|_s)?)[^a-zA-Z0-9_\`'\"<>]" -- "*.cpp" "*.h")
-
-EXIT_CODE=0
-for LOCALE_DEPENDENT_FUNCTION in "${LOCALE_DEPENDENT_FUNCTIONS[@]}"; do
- MATCHES=$(grep -E "[^a-zA-Z0-9_\`'\"<>]${LOCALE_DEPENDENT_FUNCTION}(_r|_s)?[^a-zA-Z0-9_\`'\"<>]" <<< "${GIT_GREP_OUTPUT}" | \
- grep -vE "\.(c|cpp|h):\s*(//|\*|/\*|\").*${LOCALE_DEPENDENT_FUNCTION}")
- if [[ ${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES} != "" ]]; then
- MATCHES=$(grep -vE "${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES}" <<< "${MATCHES}")
- fi
- if [[ ${REGEXP_IGNORE_KNOWN_VIOLATIONS} != "" ]]; then
- MATCHES=$(grep -vE "${REGEXP_IGNORE_KNOWN_VIOLATIONS}" <<< "${MATCHES}")
- fi
- if [[ ${MATCHES} != "" ]]; then
- echo "The locale dependent function ${LOCALE_DEPENDENT_FUNCTION}(...) appears to be used:"
- echo "${MATCHES}"
- echo
- EXIT_CODE=1
- fi
-done
-if [[ ${EXIT_CODE} != 0 ]]; then
- echo "Unnecessary locale dependence can cause bugs that are very"
- echo "tricky to isolate and fix. Please avoid using locale dependent"
- echo "functions if possible."
- echo
- echo "Advice not applicable in this specific case? Add an exception"
- echo "by updating the ignore list in $0"
-fi
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-logs.py b/test/lint/lint-logs.py
index e6c4c068fb..aaf697467d 100755
--- a/test/lint/lint-logs.py
+++ b/test/lint/lint-logs.py
@@ -16,12 +16,12 @@ from subprocess import check_output
def main():
- logs_list = check_output(["git", "grep", "--extended-regexp", r"LogPrintf?\(", "--", "*.cpp"], universal_newlines=True, encoding="utf8").splitlines()
+ logs_list = check_output(["git", "grep", "--extended-regexp", r"(LogPrintLevel|LogPrintfCategory|LogPrintf?)\(", "--", "*.cpp"], universal_newlines=True, encoding="utf8").splitlines()
unterminated_logs = [line for line in logs_list if not re.search(r'(\\n"|/\* Continued \*/)', line)]
if unterminated_logs != []:
- print("All calls to LogPrintf() and LogPrint() should be terminated with \\n")
+ print("All calls to LogPrintf(), LogPrintfCategory(), LogPrint(), LogPrintLevel(), and WalletLogPrintf() should be terminated with \"\\n\".")
print("")
for line in unterminated_logs:
diff --git a/test/lint/lint-python-utf8-encoding.py b/test/lint/lint-python-utf8-encoding.py
new file mode 100755
index 0000000000..62fdc34d50
--- /dev/null
+++ b/test/lint/lint-python-utf8-encoding.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2018-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.
+#
+# Make sure we explicitly open all text files using UTF-8 (or ASCII) encoding to
+# avoid potential issues on the BSDs where the locale is not always set.
+
+import sys
+import re
+
+from subprocess import check_output, CalledProcessError
+
+EXCLUDED_DIRS = ["src/crc32c/"]
+
+
+def get_exclude_args():
+ return [":(exclude)" + dir for dir in EXCLUDED_DIRS]
+
+
+def check_fileopens():
+ fileopens = list()
+
+ try:
+ fileopens = check_output(["git", "grep", r" open(", "--", "*.py"] + get_exclude_args(), universal_newlines=True, encoding="utf8").splitlines()
+ except CalledProcessError as e:
+ if e.returncode > 1:
+ raise e
+
+ filtered_fileopens = [fileopen for fileopen in fileopens if not re.search(r"encoding=.(ascii|utf8|utf-8).|open\([^,]*, ['\"][^'\"]*b[^'\"]*['\"]", fileopen)]
+
+ return filtered_fileopens
+
+
+def check_checked_outputs():
+ checked_outputs = list()
+
+ try:
+ checked_outputs = check_output(["git", "grep", "check_output(", "--", "*.py"] + get_exclude_args(), universal_newlines=True, encoding="utf8").splitlines()
+ except CalledProcessError as e:
+ if e.returncode > 1:
+ raise e
+
+ filtered_checked_outputs = [checked_output for checked_output in checked_outputs if re.search(r"universal_newlines=True", checked_output) and not re.search(r"encoding=.(ascii|utf8|utf-8).", checked_output)]
+
+ return filtered_checked_outputs
+
+
+def main():
+ exit_code = 0
+
+ nonexplicit_utf8_fileopens = check_fileopens()
+ if nonexplicit_utf8_fileopens:
+ print("Python's open(...) seems to be used to open text files without explicitly specifying encoding='utf8':\n")
+ for fileopen in nonexplicit_utf8_fileopens:
+ print(fileopen)
+ exit_code = 1
+
+ nonexplicit_utf8_checked_outputs = check_checked_outputs()
+ if nonexplicit_utf8_checked_outputs:
+ if nonexplicit_utf8_fileopens:
+ print("\n")
+ print("Python's check_output(...) seems to be used to get program outputs without explicitly specifying encoding='utf8':\n")
+ for checked_output in nonexplicit_utf8_checked_outputs:
+ print(checked_output)
+ exit_code = 1
+
+ sys.exit(exit_code)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/lint/lint-python-utf8-encoding.sh b/test/lint/lint-python-utf8-encoding.sh
deleted file mode 100755
index 6e5b18fc23..0000000000
--- a/test/lint/lint-python-utf8-encoding.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018-2020 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#
-# Make sure we explicitly open all text files using UTF-8 (or ASCII) encoding to
-# avoid potential issues on the BSDs where the locale is not always set.
-
-export LC_ALL=C
-EXIT_CODE=0
-OUTPUT=$(git grep " open(" -- "*.py" ":(exclude)src/crc32c/" | grep -vE "encoding=.(ascii|utf8|utf-8)." | grep -vE "open\([^,]*, ['\"][^'\"]*b[^'\"]*['\"]")
-if [[ ${OUTPUT} != "" ]]; then
- echo "Python's open(...) seems to be used to open text files without explicitly"
- echo "specifying encoding=\"utf8\":"
- echo
- echo "${OUTPUT}"
- EXIT_CODE=1
-fi
-OUTPUT=$(git grep "check_output(" -- "*.py" ":(exclude)src/crc32c/"| grep "universal_newlines=True" | grep -vE "encoding=.(ascii|utf8|utf-8).")
-if [[ ${OUTPUT} != "" ]]; then
- echo "Python's check_output(...) seems to be used to get program outputs without explicitly"
- echo "specifying encoding=\"utf8\":"
- echo
- echo "${OUTPUT}"
- EXIT_CODE=1
-fi
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-shell-locale.py b/test/lint/lint-shell-locale.py
new file mode 100755
index 0000000000..309c426374
--- /dev/null
+++ b/test/lint/lint-shell-locale.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2018-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.
+
+"""
+Make sure all shell scripts are:
+a.) explicitly opt out of locale dependence using
+ "export LC_ALL=C" or "export LC_ALL=C.UTF-8", or
+b.) explicitly opt in to locale dependence using the annotation below.
+"""
+
+import subprocess
+import sys
+import re
+
+OPT_IN_LINE = '# This script is intentionally locale dependent by not setting \"export LC_ALL=C\"'
+
+OPT_OUT_LINES = [
+ 'export LC_ALL=C',
+ 'export LC_ALL=C.UTF-8',
+]
+
+def get_shell_files_list():
+ command = [
+ 'git',
+ 'ls-files',
+ '--',
+ '*.sh',
+ ]
+ try:
+ return subprocess.check_output(command, stderr = subprocess.STDOUT).decode('utf-8').splitlines()
+ except subprocess.CalledProcessError as e:
+ if e.returncode > 1: # return code is 1 when match is empty
+ print(e.output.decode('utf-8'), end='')
+ sys.exit(1)
+ return []
+
+def main():
+ exit_code = 0
+ shell_files = get_shell_files_list()
+ for file_path in shell_files:
+ if re.search('src/(secp256k1|minisketch)/', file_path):
+ continue
+
+ with open(file_path, 'r', encoding='utf-8') as file_obj:
+ contents = file_obj.read()
+
+ if OPT_IN_LINE in contents:
+ continue
+
+ non_comment_pattern = re.compile(r'^\s*((?!#).+)$', re.MULTILINE)
+ non_comment_lines = re.findall(non_comment_pattern, contents)
+ if not non_comment_lines:
+ continue
+
+ first_non_comment_line = non_comment_lines[0]
+ if first_non_comment_line not in OPT_OUT_LINES:
+ print(f'Missing "export LC_ALL=C" (to avoid locale dependence) as first non-comment non-empty line in {file_path}')
+ exit_code = 1
+
+ return sys.exit(exit_code)
+
+if __name__ == '__main__':
+ main()
+
diff --git a/test/lint/lint-shell-locale.sh b/test/lint/lint-shell-locale.sh
deleted file mode 100755
index 4c6b8a57e6..0000000000
--- a/test/lint/lint-shell-locale.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018-2020 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#
-# Make sure all shell scripts:
-# a.) explicitly opt out of locale dependence using
-# "export LC_ALL=C" or "export LC_ALL=C.UTF-8", or
-# b.) explicitly opt in to locale dependence using the annotation below.
-
-export LC_ALL=C
-
-EXIT_CODE=0
-for SHELL_SCRIPT in $(git ls-files -- "*.sh" | grep -vE "src/(secp256k1|minisketch|univalue)/"); do
- if grep -q "# This script is intentionally locale dependent by not setting \"export LC_ALL=C\"" "${SHELL_SCRIPT}"; then
- continue
- fi
- FIRST_NON_COMMENT_LINE=$(grep -vE '^(#.*)?$' "${SHELL_SCRIPT}" | head -1)
- if [[ ${FIRST_NON_COMMENT_LINE} != "export LC_ALL=C" && ${FIRST_NON_COMMENT_LINE} != "export LC_ALL=C.UTF-8" ]]; then
- echo "Missing \"export LC_ALL=C\" (to avoid locale dependence) as first non-comment non-empty line in ${SHELL_SCRIPT}"
- EXIT_CODE=1
- fi
-done
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-shell.py b/test/lint/lint-shell.py
new file mode 100755
index 0000000000..ed95024ef5
--- /dev/null
+++ b/test/lint/lint-shell.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2018-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.
+
+"""
+Check for shellcheck warnings in shell scripts.
+"""
+
+import subprocess
+import re
+import sys
+
+# Disabled warnings:
+DISABLED = [
+ 'SC2162', # read without -r will mangle backslashes.
+]
+
+def check_shellcheck_install():
+ try:
+ subprocess.run(['shellcheck', '--version'], stdout=subprocess.DEVNULL, check=True)
+ except FileNotFoundError:
+ print('Skipping shell linting since shellcheck is not installed.')
+ sys.exit(0)
+
+def get_files(command):
+ output = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True)
+ files = output.stdout.split('\n')
+
+ # remove whitespace element
+ files = list(filter(None, files))
+ return files
+
+def main():
+ check_shellcheck_install()
+
+ # build the `exclude` flag
+ exclude = '--exclude=' + ','.join(DISABLED)
+
+ # build the `sourced files` list
+ sourced_files_cmd = [
+ 'git',
+ 'grep',
+ '-El',
+ r'^# shellcheck shell=',
+ ]
+ sourced_files = get_files(sourced_files_cmd)
+
+ # build the `guix files` list
+ guix_files_cmd = [
+ 'git',
+ 'grep',
+ '-El',
+ r'^#!\/usr\/bin\/env bash',
+ '--',
+ 'contrib/guix',
+ 'contrib/shell',
+ ]
+ guix_files = get_files(guix_files_cmd)
+
+ # build the other script files list
+ files_cmd = [
+ 'git',
+ 'ls-files',
+ '--',
+ '*.sh',
+ ]
+ files = get_files(files_cmd)
+ # remove everything that doesn't match this regex
+ reg = re.compile(r'src/[leveldb,secp256k1,minisketch]')
+ files[:] = [file for file in files if not reg.match(file)]
+
+ # build the `shellcheck` command
+ shellcheck_cmd = [
+ 'shellcheck',
+ '--external-sources',
+ '--check-sourced',
+ '--source-path=SCRIPTDIR',
+ ]
+ shellcheck_cmd.append(exclude)
+ shellcheck_cmd.extend(sourced_files)
+ shellcheck_cmd.extend(guix_files)
+ shellcheck_cmd.extend(files)
+
+ # run the `shellcheck` command
+ try:
+ subprocess.check_call(shellcheck_cmd)
+ except subprocess.CalledProcessError:
+ sys.exit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh
deleted file mode 100755
index 5fa104fce6..0000000000
--- a/test/lint/lint-shell.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018-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.
-#
-# Check for shellcheck warnings in shell scripts.
-
-export LC_ALL=C
-
-# Disabled warnings:
-disabled=(
- SC2162 # read without -r will mangle backslashes.
-)
-
-EXIT_CODE=0
-
-if ! command -v shellcheck > /dev/null; then
- echo "Skipping shell linting since shellcheck is not installed."
- exit $EXIT_CODE
-fi
-
-SHELLCHECK_CMD=(shellcheck --external-sources --check-sourced --source-path=SCRIPTDIR)
-EXCLUDE="--exclude=$(IFS=','; echo "${disabled[*]}")"
-# Check shellcheck directive used for sourced files
-mapfile -t SOURCED_FILES < <(git ls-files | xargs gawk '/^# shellcheck shell=/ {print FILENAME} {nextfile}')
-mapfile -t GUIX_FILES < <(git ls-files contrib/guix contrib/shell | xargs gawk '/^#!\/usr\/bin\/env bash/ {print FILENAME} {nextfile}')
-mapfile -t FILES < <(git ls-files -- '*.sh' | grep -vE 'src/(leveldb|secp256k1|minisketch|univalue)/')
-if ! "${SHELLCHECK_CMD[@]}" "$EXCLUDE" "${SOURCED_FILES[@]}" "${GUIX_FILES[@]}" "${FILES[@]}"; then
- EXIT_CODE=1
-fi
-
-exit $EXIT_CODE
diff --git a/test/lint/lint-spelling.py b/test/lint/lint-spelling.py
index 5da1b243f7..14d7d13a75 100755
--- a/test/lint/lint-spelling.py
+++ b/test/lint/lint-spelling.py
@@ -12,7 +12,7 @@ Note: Will exit successfully regardless of spelling errors.
from subprocess import check_output, STDOUT, CalledProcessError
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/leveldb/", ":(exclude)src/crc32c/", ":(exclude)src/qt/locale/", ":(exclude)src/qt/*.qrc", ":(exclude)src/secp256k1/", ":(exclude)src/minisketch/", ":(exclude)src/univalue/", ":(exclude)contrib/builder-keys/keys.txt", ":(exclude)contrib/guix/patches"]
+FILES_ARGS = ['git', 'ls-files', '--', ":(exclude)build-aux/m4/", ":(exclude)contrib/seeds/*.txt", ":(exclude)depends/", ":(exclude)doc/release-notes/", ":(exclude)src/leveldb/", ":(exclude)src/crc32c/", ":(exclude)src/qt/locale/", ":(exclude)src/qt/*.qrc", ":(exclude)src/secp256k1/", ":(exclude)src/minisketch/", ":(exclude)contrib/builder-keys/keys.txt", ":(exclude)contrib/guix/patches"]
def check_codespell_install():
diff --git a/test/lint/lint-tests.py b/test/lint/lint-tests.py
new file mode 100755
index 0000000000..849ddcb961
--- /dev/null
+++ b/test/lint/lint-tests.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2018-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.
+
+"""
+Check the test suite naming conventions
+"""
+
+import re
+import subprocess
+import sys
+
+
+def grep_boost_fixture_test_suite():
+ command = [
+ "git",
+ "grep",
+ "-E",
+ r"^BOOST_FIXTURE_TEST_SUITE\(",
+ "--",
+ "src/test/**.cpp",
+ "src/wallet/test/**.cpp",
+ ]
+ return subprocess.check_output(command, universal_newlines=True, encoding="utf8")
+
+
+def check_matching_test_names(test_suite_list):
+ not_matching = [
+ x
+ for x in test_suite_list
+ if re.search(r"/(.*?)\.cpp:BOOST_FIXTURE_TEST_SUITE\(\1, .*\)", x) is None
+ ]
+ if len(not_matching) > 0:
+ not_matching = "\n".join(not_matching)
+ error_msg = (
+ "The test suite in file src/test/foo_tests.cpp should be named\n"
+ '"foo_tests". Please make sure the following test suites follow\n'
+ "that convention:\n\n"
+ f"{not_matching}\n"
+ )
+ print(error_msg)
+ return 1
+ return 0
+
+
+def get_duplicates(input_list):
+ """
+ From https://stackoverflow.com/a/9835819
+ """
+ seen = set()
+ dupes = set()
+ for x in input_list:
+ if x in seen:
+ dupes.add(x)
+ else:
+ seen.add(x)
+ return dupes
+
+
+def check_unique_test_names(test_suite_list):
+ output = [re.search(r"\((.*?),", x) for x in test_suite_list]
+ output = [x.group(1) for x in output if x is not None]
+ output = get_duplicates(output)
+ output = sorted(list(output))
+
+ if len(output) > 0:
+ output = "\n".join(output)
+ error_msg = (
+ "Test suite names must be unique. The following test suite names\n"
+ f"appear to be used more than once:\n\n{output}"
+ )
+ print(error_msg)
+ return 1
+ return 0
+
+
+def main():
+ test_suite_list = grep_boost_fixture_test_suite().splitlines()
+ exit_code = check_matching_test_names(test_suite_list)
+ exit_code |= check_unique_test_names(test_suite_list)
+ sys.exit(exit_code)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/lint/lint-tests.sh b/test/lint/lint-tests.sh
deleted file mode 100755
index 35d11023eb..0000000000
--- a/test/lint/lint-tests.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2018 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#
-# Check the test suite naming conventions
-
-export LC_ALL=C
-EXIT_CODE=0
-
-NAMING_INCONSISTENCIES=$(git grep -E '^BOOST_FIXTURE_TEST_SUITE\(' -- \
- "src/test/**.cpp" "src/wallet/test/**.cpp" | \
- grep -vE '/(.*?)\.cpp:BOOST_FIXTURE_TEST_SUITE\(\1, .*\)$')
-if [[ ${NAMING_INCONSISTENCIES} != "" ]]; then
- echo "The test suite in file src/test/foo_tests.cpp should be named"
- echo "\"foo_tests\". Please make sure the following test suites follow"
- echo "that convention:"
- echo
- echo "${NAMING_INCONSISTENCIES}"
- EXIT_CODE=1
-fi
-
-TEST_SUITE_NAME_COLLISSIONS=$(git grep -E '^BOOST_FIXTURE_TEST_SUITE\(' -- \
- "src/test/**.cpp" "src/wallet/test/**.cpp" | cut -f2 -d'(' | cut -f1 -d, | \
- sort | uniq -d)
-if [[ ${TEST_SUITE_NAME_COLLISSIONS} != "" ]]; then
- echo "Test suite names must be unique. The following test suite names"
- echo "appear to be used more than once:"
- echo
- echo "${TEST_SUITE_NAME_COLLISSIONS}"
- EXIT_CODE=1
-fi
-
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-whitespace.py b/test/lint/lint-whitespace.py
index d98fc8d9a2..3fb5b80013 100755
--- a/test/lint/lint-whitespace.py
+++ b/test/lint/lint-whitespace.py
@@ -22,7 +22,6 @@ EXCLUDED_DIRS = ["depends/patches/",
"src/crc32c/",
"src/secp256k1/",
"src/minisketch/",
- "src/univalue/",
"doc/release-notes/",
"src/qt/locale"]
diff --git a/test/lint/spelling.ignore-words.txt b/test/lint/spelling.ignore-words.txt
index afdb0692d8..0efd298408 100644
--- a/test/lint/spelling.ignore-words.txt
+++ b/test/lint/spelling.ignore-words.txt
@@ -3,6 +3,7 @@ ba
blockin
cachable
creat
+desig
fo
fpr
hights
@@ -11,6 +12,7 @@ inout
invokable
keypair
mor
+nd
nin
ser
unparseable
diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan
index e6cfe5f81a..67ef512895 100644
--- a/test/sanitizer_suppressions/ubsan
+++ b/test/sanitizer_suppressions/ubsan
@@ -1,10 +1,10 @@
# -fsanitize=undefined suppressions
# =================================
-# This would be `signed-integer-overflow:CTxMemPool::PrioritiseTransaction`,
+# The suppressions would be `sanitize-type:ClassName::MethodName`,
# however due to a bug in clang the symbolizer is disabled and thus no symbol
# names can be used.
# See https://github.com/google/sanitizers/issues/1364
-signed-integer-overflow:txmempool.cpp
+
# https://github.com/bitcoin/bitcoin/pull/21798#issuecomment-829180719
signed-integer-overflow:policy/feerate.cpp
diff --git a/test/util/test_runner.py b/test/util/test_runner.py
index a7fc3b1dc1..03db05c563 100755
--- a/test/util/test_runner.py
+++ b/test/util/test_runner.py
@@ -22,7 +22,8 @@ import sys
def main():
config = configparser.ConfigParser()
config.optionxform = str
- config.read_file(open(os.path.join(os.path.dirname(__file__), "../config.ini"), encoding="utf8"))
+ with open(os.path.join(os.path.dirname(__file__), "../config.ini"), encoding="utf8") as f:
+ config.read_file(f)
env_conf = dict(config.items('environment'))
parser = argparse.ArgumentParser(description=__doc__)
@@ -43,7 +44,8 @@ def main():
def bctester(testDir, input_basename, buildenv):
""" Loads and parses the input file, runs all tests and reports results"""
input_filename = os.path.join(testDir, input_basename)
- raw_data = open(input_filename, encoding="utf8").read()
+ with open(input_filename, encoding="utf8") as f:
+ raw_data = f.read()
input_data = json.loads(raw_data)
failed_testcases = []
@@ -80,7 +82,8 @@ def bctest(testDir, testObj, buildenv):
inputData = None
if "input" in testObj:
filename = os.path.join(testDir, testObj["input"])
- inputData = open(filename, encoding="utf8").read()
+ with open(filename, encoding="utf8") as f:
+ inputData = f.read()
stdinCfg = subprocess.PIPE
# Read the expected output data (if there is any)
@@ -91,7 +94,8 @@ def bctest(testDir, testObj, buildenv):
outputFn = testObj['output_cmp']
outputType = os.path.splitext(outputFn)[1][1:] # output type from file extension (determines how to compare)
try:
- outputData = open(os.path.join(testDir, outputFn), encoding="utf8").read()
+ with open(os.path.join(testDir, outputFn), encoding="utf8") as f:
+ outputData = f.read()
except:
logging.error("Output file " + outputFn + " cannot be opened")
raise