aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml35
-rw-r--r--.gitignore2
-rw-r--r--Makefile.am17
-rw-r--r--build-aux/m4/bitcoin_qt.m410
-rw-r--r--build_msvc/.gitignore1
-rw-r--r--build_msvc/README.md4
-rw-r--r--build_msvc/bitcoin_config.h.in4
-rw-r--r--build_msvc/common.init.vcxproj.in (renamed from build_msvc/common.init.vcxproj)4
-rw-r--r--build_msvc/libsecp256k1/libsecp256k1.vcxproj2
-rwxr-xr-xbuild_msvc/msvc-autogen.py23
-rwxr-xr-xci/lint/04_install.sh6
-rwxr-xr-xci/test/00_setup_env.sh1
-rwxr-xr-xci/test/00_setup_env_i686_multiprocess.sh1
-rwxr-xr-xci/test/00_setup_env_native_asan.sh2
-rwxr-xr-xci/test/00_setup_env_native_fuzz_with_msan.sh5
-rwxr-xr-xci/test/00_setup_env_native_fuzz_with_valgrind.sh3
-rwxr-xr-xci/test/00_setup_env_native_msan.sh5
-rwxr-xr-xci/test/00_setup_env_native_qt5.sh4
-rwxr-xr-xci/test/00_setup_env_native_tidy.sh19
-rwxr-xr-xci/test/00_setup_env_native_valgrind.sh3
-rwxr-xr-xci/test/04_install.sh6
-rwxr-xr-xci/test/06_script_a.sh7
-rwxr-xr-xci/test/06_script_b.sh7
-rw-r--r--ci/test/wrapped-cl.bat1
-rw-r--r--configure.ac145
-rw-r--r--contrib/builder-keys/keys.txt2
-rwxr-xr-xcontrib/devtools/security-check.py16
-rwxr-xr-xcontrib/devtools/symbol-check.py10
-rw-r--r--contrib/guix/README.md2
-rwxr-xr-xcontrib/guix/guix-attest22
-rwxr-xr-xcontrib/guix/guix-codesign4
-rwxr-xr-xcontrib/guix/libexec/build.sh24
-rwxr-xr-xcontrib/guix/libexec/codesign.sh2
-rw-r--r--contrib/guix/libexec/prelude.bash2
-rw-r--r--contrib/guix/manifest.scm19
-rw-r--r--contrib/guix/patches/vmov-alignment.patch267
-rwxr-xr-xcontrib/linearize/linearize-data.py46
-rwxr-xr-xcontrib/linearize/linearize-hashes.py7
-rwxr-xr-xcontrib/macdeploy/detached-sig-apply.sh27
-rwxr-xr-xcontrib/macdeploy/macdeployqtplus29
-rwxr-xr-xcontrib/signet/miner7
-rw-r--r--contrib/testgen/README.md4
-rw-r--r--contrib/testgen/base58.py115
-rwxr-xr-xcontrib/testgen/gen_key_io_test_vectors.py43
-rw-r--r--contrib/valgrind.supp10
-rw-r--r--depends/hosts/openbsd.mk10
-rw-r--r--depends/packages/libmultiprocess.mk2
-rw-r--r--depends/packages/native_libmultiprocess.mk2
-rw-r--r--depends/packages/qt.mk33
-rw-r--r--depends/patches/qt/dont_use_avx_android_x86_64.patch32
-rw-r--r--depends/patches/qt/duplicate_lcqpafonts.patch104
-rw-r--r--depends/patches/qt/fix_android_jni_static.patch2
-rw-r--r--depends/patches/qt/fix_bigsur_style.patch90
-rw-r--r--depends/patches/qt/fix_limits_header.patch49
-rw-r--r--depends/patches/qt/fix_no_printer.patch19
-rw-r--r--depends/patches/qt/mac-qmake.conf2
-rw-r--r--depends/patches/qt/use_android_ndk23.patch2
-rw-r--r--doc/README.md1
-rw-r--r--doc/REST-interface.md10
-rw-r--r--doc/build-freebsd.md82
-rw-r--r--doc/build-netbsd.md37
-rw-r--r--doc/build-openbsd.md132
-rw-r--r--doc/build-osx.md63
-rw-r--r--doc/build-unix.md33
-rw-r--r--doc/cjdns.md116
-rw-r--r--doc/dependencies.md95
-rw-r--r--doc/developer-notes.md65
-rw-r--r--doc/i2p.md10
-rw-r--r--doc/multisig-tutorial.md6
-rw-r--r--doc/policy/packages.md45
-rw-r--r--doc/release-notes-24098.md22
-rw-r--r--doc/release-notes-24118.md10
-rw-r--r--doc/release-notes-24494.md2
-rw-r--r--doc/release-notes-empty-template.md99
-rw-r--r--doc/release-notes.md54
-rw-r--r--doc/release-process.md53
-rw-r--r--doc/tor.md11
-rw-r--r--src/Makefile.am13
-rw-r--r--src/Makefile.bench.include31
-rw-r--r--src/Makefile.test.include25
-rw-r--r--src/addrdb.cpp6
-rw-r--r--src/addrman.cpp16
-rw-r--r--src/bench/addrman.cpp2
-rw-r--r--src/bench/coin_selection.cpp26
-rw-r--r--src/bench/logging.cpp48
-rw-r--r--src/bench/mempool_stress.cpp6
-rw-r--r--src/bench/rollingbloom.cpp13
-rw-r--r--src/bench/rpc_mempool.cpp4
-rw-r--r--src/bench/wallet_balance.cpp8
-rw-r--r--src/bitcoin-chainstate.cpp2
-rw-r--r--src/bitcoin-cli.cpp10
-rw-r--r--src/bitcoin-tx.cpp2
-rw-r--r--src/bitcoin-util.cpp2
-rw-r--r--src/chainparams.cpp11
-rw-r--r--src/compat.h4
-rw-r--r--src/compat/strnlen.cpp18
-rw-r--r--src/consensus/params.h11
-rw-r--r--src/core_io.h5
-rw-r--r--src/core_write.cpp36
-rw-r--r--src/fs.h18
-rw-r--r--src/httpserver.cpp53
-rw-r--r--src/httpserver.h33
-rw-r--r--src/index/coinstatsindex.cpp2
-rw-r--r--src/init.cpp21
-rw-r--r--src/init.h2
-rw-r--r--src/key.cpp2
-rw-r--r--src/logging.cpp2
-rw-r--r--src/logging.h2
-rw-r--r--src/net.cpp33
-rw-r--r--src/net.h71
-rw-r--r--src/net_processing.cpp308
-rw-r--r--src/net_processing.h2
-rw-r--r--src/node/blockstorage.cpp125
-rw-r--r--src/node/blockstorage.h16
-rw-r--r--src/node/chainstate.cpp10
-rw-r--r--src/node/interfaces.cpp8
-rw-r--r--src/node/miner.cpp20
-rw-r--r--src/node/miner.h4
-rw-r--r--src/qt/bitcoingui.cpp15
-rw-r--r--src/qt/bitcoingui.h2
-rw-r--r--src/qt/coincontroldialog.cpp9
-rw-r--r--src/qt/forms/debugwindow.ui12
-rw-r--r--src/qt/guiutil.cpp3
-rw-r--r--src/qt/intro.cpp6
-rw-r--r--src/qt/optionsmodel.cpp22
-rw-r--r--src/qt/peertablemodel.cpp2
-rw-r--r--src/qt/rpcconsole.cpp15
-rw-r--r--src/qt/sendcoinsdialog.cpp205
-rw-r--r--src/qt/sendcoinsdialog.h13
-rw-r--r--src/qt/signverifymessagedialog.cpp2
-rw-r--r--src/qt/test/optiontests.cpp37
-rw-r--r--src/qt/test/optiontests.h1
-rw-r--r--src/qt/transactiontablemodel.cpp10
-rw-r--r--src/qt/transactionview.cpp2
-rw-r--r--src/rest.cpp179
-rw-r--r--src/rest.h28
-rw-r--r--src/rpc/blockchain.cpp93
-rw-r--r--src/rpc/client.cpp4
-rw-r--r--src/rpc/external_signer.cpp2
-rw-r--r--src/rpc/mempool.cpp229
-rw-r--r--src/rpc/mining.cpp29
-rw-r--r--src/rpc/misc.cpp13
-rw-r--r--src/rpc/net.cpp14
-rw-r--r--src/rpc/rawtransaction.cpp535
-rw-r--r--src/rpc/register.h9
-rw-r--r--src/rpc/server.cpp2
-rw-r--r--src/rpc/txoutproof.cpp183
-rw-r--r--src/rpc/util.cpp58
-rw-r--r--src/rpc/util.h11
-rw-r--r--src/scheduler.cpp2
-rw-r--r--src/script/descriptor.cpp30
-rw-r--r--src/script/interpreter.cpp29
-rw-r--r--src/script/interpreter.h2
-rw-r--r--src/script/miniscript.cpp348
-rw-r--r--src/script/miniscript.h1652
-rw-r--r--src/script/script.cpp25
-rw-r--r--src/script/script.h29
-rw-r--r--src/script/sign.cpp2
-rw-r--r--src/script/standard.cpp5
-rw-r--r--src/script/standard.h5
-rw-r--r--src/secp256k1/.cirrus.yml98
-rw-r--r--src/secp256k1/.gitattributes4
-rw-r--r--src/secp256k1/.gitignore10
-rw-r--r--src/secp256k1/Makefile.am92
-rw-r--r--src/secp256k1/README.md15
-rw-r--r--src/secp256k1/build-aux/m4/bitcoin_secp.m413
-rwxr-xr-xsrc/secp256k1/ci/cirrus.sh4
-rw-r--r--src/secp256k1/ci/linux-debian.Dockerfile3
-rw-r--r--src/secp256k1/configure.ac144
-rw-r--r--src/secp256k1/doc/CHANGELOG.md12
-rw-r--r--src/secp256k1/doc/release-process.md14
-rw-r--r--src/secp256k1/examples/EXAMPLES_COPYING121
-rw-r--r--src/secp256k1/examples/ecdh.c127
-rw-r--r--src/secp256k1/examples/ecdsa.c137
-rw-r--r--src/secp256k1/examples/random.h73
-rw-r--r--src/secp256k1/examples/schnorr.c152
-rw-r--r--src/secp256k1/include/secp256k1.h22
-rw-r--r--src/secp256k1/include/secp256k1_extrakeys.h9
-rw-r--r--src/secp256k1/include/secp256k1_schnorrsig.h13
-rw-r--r--src/secp256k1/sage/group_prover.sage64
-rw-r--r--src/secp256k1/sage/prove_group_implementations.sage64
-rw-r--r--src/secp256k1/sage/weierstrass_prover.sage13
-rw-r--r--src/secp256k1/src/bench_internal.c10
-rw-r--r--src/secp256k1/src/ecmult_compute_table.h16
-rw-r--r--src/secp256k1/src/ecmult_compute_table_impl.h49
-rw-r--r--src/secp256k1/src/ecmult_const_impl.h86
-rw-r--r--src/secp256k1/src/ecmult_gen_compute_table.h (renamed from src/secp256k1/src/ecmult_gen_prec.h)8
-rw-r--r--src/secp256k1/src/ecmult_gen_compute_table_impl.h (renamed from src/secp256k1/src/ecmult_gen_prec_impl.h)10
-rw-r--r--src/secp256k1/src/ecmult_gen_impl.h2
-rw-r--r--src/secp256k1/src/ecmult_impl.h229
-rw-r--r--src/secp256k1/src/field.h15
-rw-r--r--src/secp256k1/src/field_10x26_impl.h109
-rw-r--r--src/secp256k1/src/field_5x52_impl.h100
-rw-r--r--src/secp256k1/src/field_impl.h2
-rw-r--r--src/secp256k1/src/gen_ecmult_static_pre_g.c131
-rw-r--r--src/secp256k1/src/group.h36
-rw-r--r--src/secp256k1/src/group_impl.h123
-rw-r--r--src/secp256k1/src/hash.h4
-rw-r--r--src/secp256k1/src/hash_impl.h61
-rw-r--r--src/secp256k1/src/modules/ecdh/tests_impl.h35
-rw-r--r--src/secp256k1/src/modules/schnorrsig/main_impl.h6
-rw-r--r--src/secp256k1/src/modules/schnorrsig/tests_impl.h43
-rw-r--r--src/secp256k1/src/precompute_ecmult.c96
-rw-r--r--src/secp256k1/src/precompute_ecmult_gen.c (renamed from src/secp256k1/src/gen_ecmult_gen_static_prec_table.c)43
-rw-r--r--src/secp256k1/src/precomputed_ecmult.c (renamed from src/secp256k1/src/ecmult_static_pre_g.h)231
-rw-r--r--src/secp256k1/src/precomputed_ecmult.h35
-rw-r--r--src/secp256k1/src/precomputed_ecmult_gen.c (renamed from src/secp256k1/src/ecmult_gen_static_prec_table.h)22
-rw-r--r--src/secp256k1/src/precomputed_ecmult_gen.h26
-rw-r--r--src/secp256k1/src/secp256k1.c8
-rw-r--r--src/secp256k1/src/testrand.h7
-rw-r--r--src/secp256k1/src/testrand_impl.h73
-rw-r--r--src/secp256k1/src/tests.c556
-rw-r--r--src/secp256k1/src/tests_exhaustive.c8
-rw-r--r--src/secp256k1/src/util.h41
-rw-r--r--src/secp256k1/src/valgrind_ctime_test.c2
-rw-r--r--src/support/lockedpool.cpp6
-rw-r--r--src/sync.h5
-rw-r--r--src/test/blockfilter_index_tests.cpp11
-rw-r--r--src/test/checkqueue_tests.cpp10
-rw-r--r--src/test/denialofservice_tests.cpp17
-rw-r--r--src/test/descriptor_tests.cpp30
-rw-r--r--src/test/fuzz/fuzz.cpp72
-rw-r--r--src/test/fuzz/http_request.cpp15
-rw-r--r--src/test/fuzz/miniscript_decode.cpp72
-rw-r--r--src/test/fuzz/net.cpp10
-rw-r--r--src/test/fuzz/node_eviction.cpp2
-rw-r--r--src/test/fuzz/script_format.cpp4
-rw-r--r--src/test/fuzz/transaction.cpp4
-rw-r--r--src/test/fuzz/tx_pool.cpp14
-rw-r--r--src/test/fuzz/util.cpp11
-rw-r--r--src/test/httpserver_tests.cpp38
-rw-r--r--src/test/miniscript_tests.cpp284
-rw-r--r--src/test/net_peer_eviction_tests.cpp8
-rw-r--r--src/test/net_tests.cpp5
-rw-r--r--src/test/rest_tests.cpp48
-rw-r--r--src/test/script_segwit_tests.cpp164
-rw-r--r--src/test/system_tests.cpp9
-rw-r--r--src/test/txpackage_tests.cpp333
-rw-r--r--src/test/util/net.cpp2
-rw-r--r--src/test/util_tests.cpp33
-rw-r--r--src/torcontrol.cpp88
-rw-r--r--src/torcontrol.h2
-rw-r--r--src/txdb.cpp138
-rw-r--r--src/txdb.h4
-rw-r--r--src/txmempool.cpp19
-rw-r--r--src/txmempool.h8
-rw-r--r--src/util/check.cpp14
-rw-r--r--src/util/check.h24
-rw-r--r--src/util/syscall_sandbox.cpp1
-rw-r--r--src/util/syscall_sandbox.h3
-rw-r--r--src/util/system.cpp9
-rw-r--r--src/util/threadnames.cpp4
-rw-r--r--src/validation.cpp254
-rw-r--r--src/validation.h12
-rw-r--r--src/wallet/bdb.cpp17
-rw-r--r--src/wallet/bdb.h12
-rw-r--r--src/wallet/coinselection.cpp152
-rw-r--r--src/wallet/coinselection.h174
-rw-r--r--src/wallet/db.cpp10
-rw-r--r--src/wallet/db.h9
-rw-r--r--src/wallet/dump.cpp11
-rw-r--r--src/wallet/dump.h5
-rw-r--r--src/wallet/feebumper.cpp8
-rw-r--r--src/wallet/init.cpp4
-rw-r--r--src/wallet/interfaces.cpp17
-rw-r--r--src/wallet/load.cpp5
-rw-r--r--src/wallet/receive.cpp4
-rw-r--r--src/wallet/rpc/addresses.cpp4
-rw-r--r--src/wallet/rpc/backup.cpp6
-rw-r--r--src/wallet/rpc/coins.cpp28
-rw-r--r--src/wallet/rpc/spend.cpp447
-rw-r--r--src/wallet/rpc/transactions.cpp2
-rw-r--r--src/wallet/rpc/wallet.cpp7
-rw-r--r--src/wallet/salvage.cpp3
-rw-r--r--src/wallet/salvage.h3
-rw-r--r--src/wallet/spend.cpp128
-rw-r--r--src/wallet/spend.h57
-rw-r--r--src/wallet/sqlite.cpp8
-rw-r--r--src/wallet/sqlite.h3
-rw-r--r--src/wallet/test/coinselector_tests.cpp248
-rw-r--r--src/wallet/test/db_tests.cpp10
-rw-r--r--src/wallet/test/fuzz/coinselection.cpp99
-rw-r--r--src/wallet/test/init_test_fixture.cpp9
-rw-r--r--src/wallet/test/init_tests.cpp8
-rw-r--r--src/wallet/test/psbt_wallet_tests.cpp1
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp1
-rw-r--r--src/wallet/test/wallet_test_fixture.h2
-rw-r--r--src/wallet/test/wallet_tests.cpp27
-rw-r--r--src/wallet/wallet.cpp20
-rw-r--r--src/wallet/wallet.h5
-rw-r--r--src/wallet/walletdb.cpp9
-rw-r--r--src/wallet/wallettool.cpp9
-rw-r--r--test/README.md35
-rw-r--r--test/config.ini.in1
-rwxr-xr-xtest/functional/feature_blockfilterindex_prune.py10
-rwxr-xr-xtest/functional/feature_coinstatsindex.py60
-rwxr-xr-xtest/functional/feature_fee_estimation.py105
-rwxr-xr-xtest/functional/feature_proxy.py64
-rwxr-xr-xtest/functional/feature_pruning.py4
-rwxr-xr-xtest/functional/feature_segwit.py38
-rwxr-xr-xtest/functional/feature_taproot.py10
-rwxr-xr-xtest/functional/feature_unsupported_utxo_db.py61
-rwxr-xr-xtest/functional/feature_utxo_set_hash.py4
-rwxr-xr-xtest/functional/interface_rest.py49
-rwxr-xr-xtest/functional/interface_usdt_net.py171
-rwxr-xr-xtest/functional/interface_usdt_utxocache.py407
-rwxr-xr-xtest/functional/interface_usdt_validation.py136
-rwxr-xr-xtest/functional/interface_zmq.py278
-rwxr-xr-xtest/functional/mempool_package_onemore.py58
-rwxr-xr-xtest/functional/mempool_unbroadcast.py45
-rwxr-xr-xtest/functional/mining_prioritisetransaction.py83
-rwxr-xr-xtest/functional/p2p_blockfilters.py6
-rwxr-xr-xtest/functional/p2p_blocksonly.py2
-rwxr-xr-xtest/functional/rpc_createmultisig.py103
-rwxr-xr-xtest/functional/rpc_dumptxoutset.py6
-rwxr-xr-xtest/functional/rpc_generate.py91
-rwxr-xr-xtest/functional/rpc_generateblock.py100
-rwxr-xr-xtest/functional/rpc_misc.py3
-rw-r--r--test/functional/test_framework/address.py16
-rwxr-xr-xtest/functional/test_framework/test_framework.py28
-rw-r--r--test/functional/test_framework/wallet.py63
-rwxr-xr-xtest/functional/test_runner.py19
-rwxr-xr-xtest/functional/wallet_balance.py4
-rwxr-xr-xtest/functional/wallet_basic.py6
-rwxr-xr-xtest/functional/wallet_bumpfee.py4
-rwxr-xr-xtest/functional/wallet_disable.py6
-rwxr-xr-xtest/functional/wallet_resendwallettransactions.py2
-rwxr-xr-xtest/functional/wallet_sendall.py316
-rwxr-xr-xtest/functional/wallet_signer.py6
-rwxr-xr-xtest/get_previous_releases.py6
-rwxr-xr-xtest/lint/lint-all.sh4
-rwxr-xr-xtest/lint/lint-cpp.sh21
-rwxr-xr-xtest/lint/lint-files.py3
-rwxr-xr-xtest/lint/lint-files.sh10
-rwxr-xr-xtest/lint/lint-format-strings.sh4
-rwxr-xr-xtest/lint/lint-python-dead-code.py41
-rwxr-xr-xtest/lint/lint-python-dead-code.sh22
-rwxr-xr-xtest/lint/lint-python-mutable-default-parameters.py72
-rwxr-xr-xtest/lint/lint-python-mutable-default-parameters.sh52
-rwxr-xr-xtest/lint/lint-qt.sh20
-rwxr-xr-xtest/lint/lint-spelling.py40
-rwxr-xr-xtest/lint/lint-spelling.sh21
-rwxr-xr-xtest/lint/run-lint-format-strings.py (renamed from test/lint/lint-format-strings.py)0
-rw-r--r--test/lint/spelling.ignore-words.txt (renamed from test/lint/lint-spelling.ignore-words.txt)0
344 files changed, 11797 insertions, 4878 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index e07ff9796e..5bfdcbc9b1 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -2,6 +2,7 @@ env: # Global defaults
PACKAGE_MANAGER_INSTALL: "apt-get update && apt-get install -y"
MAKEJOBS: "-j10"
TEST_RUNNER_PORT_MIN: "14000" # Must be larger than 12321, which is used for the http cache. See https://cirrus-ci.org/guide/writing-tasks/#http-cache
+ CI_FAILFAST_TEST_LEAVE_DANGLING: "1" # Cirrus CI does not care about dangling process and setting this variable avoids killing the CI script itself on error
CCACHE_SIZE: "200M"
CCACHE_DIR: "/tmp/ccache_dir"
CCACHE_NOHASHDIR: "1" # Debug info might contain a stale path if the build dir changes, but this is fine
@@ -72,6 +73,19 @@ task:
<< : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV
task:
+ name: 'tidy [jammy]'
+ << : *GLOBAL_TASK_TEMPLATE
+ container:
+ image: ubuntu:jammy
+ cpu: 2
+ memory: 5G
+ # For faster CI feedback, immediately schedule the linters
+ << : *CREDITS_TEMPLATE
+ env:
+ << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV
+ FILE_ENV: "./ci/test/00_setup_env_native_tidy.sh"
+
+task:
name: "Win64 native [msvc]"
<< : *FILTER_TEMPLATE
windows_container:
@@ -85,9 +99,11 @@ task:
CI_VCPKG_TAG: '2022.02.23'
VCPKG_DOWNLOADS: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\downloads'
VCPKG_DEFAULT_BINARY_CACHE: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\archives'
- QT_DOWNLOAD_URL: 'https://download.qt.io/official_releases/qt/5.15/5.15.2/single/qt-everywhere-src-5.15.2.zip'
- QT_LOCAL_PATH: 'C:\qt-everywhere-src-5.15.2.zip'
- QT_SOURCE_DIR: 'C:\qt-everywhere-src-5.15.2'
+ CCACHE_DIR: 'C:\Users\ContainerAdministrator\AppData\Local\ccache'
+ WRAPPED_CL: 'C:\Users\ContainerAdministrator\AppData\Local\Temp\cirrus-ci-build\ci\test\wrapped-cl.bat'
+ QT_DOWNLOAD_URL: 'https://download.qt.io/official_releases/qt/5.15/5.15.3/single/qt-everywhere-opensource-src-5.15.3.zip'
+ QT_LOCAL_PATH: 'C:\qt-everywhere-opensource-src-5.15.3.zip'
+ QT_SOURCE_DIR: 'C:\qt-everywhere-src-5.15.3'
QTBASEDIR: 'C:\Qt_static'
x64_NATIVE_TOOLS: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"'
IgnoreWarnIntDirInTempDetected: 'true'
@@ -134,9 +150,13 @@ task:
- msbuild -version
populate_script:
- mkdir %VCPKG_DEFAULT_BINARY_CACHE%
- install_python_script:
+ ccache_cache:
+ folder: '%CCACHE_DIR%'
+ install_tools_script:
+ - choco install --yes --no-progress ccache
- choco install --yes --no-progress python3 --version=3.9.6
- pip install zmq
+ - ccache --version
- python -VV
install_vcpkg_script:
- cd ..
@@ -148,9 +168,12 @@ task:
- .\vcpkg integrate install
- .\vcpkg version
build_script:
+ - '%x64_NATIVE_TOOLS%'
- cd %CIRRUS_WORKING_DIR%
+ - ccache --zero-stats
- python build_msvc\msvc-autogen.py
- - msbuild build_msvc\bitcoin.sln -property:Configuration=Release -maxCpuCount -verbosity:minimal -noLogo
+ - msbuild build_msvc\bitcoin.sln -property:CLToolExe=%WRAPPED_CL% -property:Configuration=Release -maxCpuCount -verbosity:minimal -noLogo
+ - ccache --show-stats
unit_tests_script:
- src\test_bitcoin.exe -l test_suite
- src\bench_bitcoin.exe > NUL
@@ -288,7 +311,7 @@ task:
<< : *GLOBAL_TASK_TEMPLATE
macos_instance:
# Use latest image, but hardcode version to avoid silent upgrades (and breaks)
- image: monterey-xcode-13.2 # https://cirrus-ci.org/guide/macOS
+ image: monterey-xcode-13.3 # https://cirrus-ci.org/guide/macOS
env:
<< : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV
CI_USE_APT_INSTALL: "no"
diff --git a/.gitignore b/.gitignore
index 0f07a86401..cde517c986 100644
--- a/.gitignore
+++ b/.gitignore
@@ -151,3 +151,5 @@ osx_volname
dist/
/guix-build-*
+
+/ci/scratch/
diff --git a/Makefile.am b/Makefile.am
index 1412624d54..54e9a6865f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -38,7 +38,6 @@ OSX_APP=Bitcoin-Qt.app
OSX_VOLNAME = $(subst $(space),-,$(PACKAGE_NAME))
OSX_DMG = $(OSX_VOLNAME).dmg
OSX_TEMP_ISO = $(OSX_DMG:.dmg=).temp.iso
-OSX_BACKGROUND_IMAGE=$(top_srcdir)/contrib/macdeploy/background.tiff
OSX_DEPLOY_SCRIPT=$(top_srcdir)/contrib/macdeploy/macdeployqtplus
OSX_INSTALLER_ICONS=$(top_srcdir)/src/qt/res/icons/bitcoin.icns
OSX_PLIST=$(top_builddir)/share/qt/Info.plist #not installed
@@ -64,7 +63,6 @@ WINDOWS_PACKAGING = $(top_srcdir)/share/pixmaps/bitcoin.ico \
$(top_srcdir)/doc/README_windows.txt
OSX_PACKAGING = $(OSX_DEPLOY_SCRIPT) $(OSX_INSTALLER_ICONS) \
- $(top_srcdir)/contrib/macdeploy/detached-sig-apply.sh \
$(top_srcdir)/contrib/macdeploy/detached-sig-create.sh
COVERAGE_INFO = $(COV_TOOL_WRAPPER) baseline.info \
@@ -130,28 +128,17 @@ $(OSX_DMG): $(OSX_APP_BUILT) $(OSX_PACKAGING)
deploydir: $(OSX_DMG)
else !BUILD_DARWIN
APP_DIST_DIR=$(top_builddir)/dist
-APP_DIST_EXTRAS=$(APP_DIST_DIR)/.background/background.tiff $(APP_DIST_DIR)/.DS_Store $(APP_DIST_DIR)/Applications
-$(APP_DIST_DIR)/Applications:
- @rm -f $@
- @cd $(@D); $(LN_S) /Applications $(@F)
-
-$(APP_DIST_EXTRAS): $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt
-
-$(OSX_TEMP_ISO): $(APP_DIST_EXTRAS)
+$(OSX_TEMP_ISO): $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt
$(XORRISOFS) -D -l -V "$(OSX_VOLNAME)" -no-pad -r -dir-mode 0755 -o $@ $(APP_DIST_DIR) -- $(if $(SOURCE_DATE_EPOCH),-volume_date all_file_dates =$(SOURCE_DATE_EPOCH))
$(OSX_DMG): $(OSX_TEMP_ISO)
$(DMG) dmg "$<" "$@"
-$(APP_DIST_DIR)/.background/background.tiff:
- $(MKDIR_P) $(@D)
- cp $(OSX_BACKGROUND_IMAGE) $@
-
$(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt: $(OSX_APP_BUILT) $(OSX_PACKAGING)
INSTALLNAMETOOL=$(INSTALLNAMETOOL) OTOOL=$(OTOOL) STRIP=$(STRIP) $(PYTHON) $(OSX_DEPLOY_SCRIPT) $(OSX_APP) $(OSX_VOLNAME) -translations-dir=$(QT_TRANSLATION_DIR)
-deploydir: $(APP_DIST_EXTRAS)
+deploydir: $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt
endif !BUILD_DARWIN
appbundle: $(OSX_APP_BUILT)
diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4
index 1454e33f6e..a716cd9a27 100644
--- a/build-aux/m4/bitcoin_qt.m4
+++ b/build-aux/m4/bitcoin_qt.m4
@@ -116,8 +116,8 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[
BITCOIN_QT_CHECK([
TEMP_CPPFLAGS=$CPPFLAGS
TEMP_CXXFLAGS=$CXXFLAGS
- CPPFLAGS="$QT_INCLUDES $CPPFLAGS"
- CXXFLAGS="$PIC_FLAGS $CXXFLAGS"
+ CPPFLAGS="$QT_INCLUDES $CORE_CPPFLAGS $CPPFLAGS"
+ CXXFLAGS="$PIC_FLAGS $CORE_CXXFLAGS $CXXFLAGS"
_BITCOIN_QT_IS_STATIC
if test "$bitcoin_cv_static_qt" = "yes"; then
_BITCOIN_QT_CHECK_STATIC_LIBS
@@ -178,8 +178,8 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[
AC_MSG_CHECKING([whether -fPIE can be used with this Qt config])
TEMP_CPPFLAGS=$CPPFLAGS
TEMP_CXXFLAGS=$CXXFLAGS
- CPPFLAGS="$QT_INCLUDES $CPPFLAGS"
- CXXFLAGS="$PIE_FLAGS $CXXFLAGS"
+ CPPFLAGS="$QT_INCLUDES $CORE_CPPFLAGS $CPPFLAGS"
+ CXXFLAGS="$PIE_FLAGS $CORE_CXXFLAGS $CXXFLAGS"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <QtCore/qconfig.h>
#ifndef QT_VERSION
@@ -201,7 +201,7 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[
BITCOIN_QT_CHECK([
AC_MSG_CHECKING([whether -fPIC is needed with this Qt config])
TEMP_CPPFLAGS=$CPPFLAGS
- CPPFLAGS="$QT_INCLUDES $CPPFLAGS"
+ CPPFLAGS="$QT_INCLUDES $CORE_CPPFLAGS $CPPFLAGS"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <QtCore/qconfig.h>
#ifndef QT_VERSION
diff --git a/build_msvc/.gitignore b/build_msvc/.gitignore
index b0e557bc0c..b2eb9313a0 100644
--- a/build_msvc/.gitignore
+++ b/build_msvc/.gitignore
@@ -22,6 +22,7 @@ bench_bitcoin/bench_bitcoin.vcxproj
libtest_util/libtest_util.vcxproj
/bitcoin_config.h
+/common.init.vcxproj
*/Win32
libbitcoin_qt/QtGeneratedFiles/*
diff --git a/build_msvc/README.md b/build_msvc/README.md
index cabe4d55ec..7feee6b766 100644
--- a/build_msvc/README.md
+++ b/build_msvc/README.md
@@ -28,7 +28,7 @@ Qt
---------------------
To build Bitcoin Core with the GUI, a static build of Qt is required.
-1. Download a single ZIP archive of Qt source code from https://download.qt.io/official_releases/qt/ (e.g., [`qt-everywhere-src-5.15.2.zip`](https://download.qt.io/official_releases/qt/5.15/5.15.2/single/qt-everywhere-src-5.15.2.zip)), and expand it into a dedicated folder. The following instructions assume that this folder is `C:\dev\qt-source`.
+1. Download a single ZIP archive of Qt source code from https://download.qt.io/official_releases/qt/ (e.g., [`qt-everywhere-opensource-src-5.15.3.zip`](https://download.qt.io/official_releases/qt/5.15/5.15.3/single/qt-everywhere-opensource-src-5.15.3.zip)), and expand it into a dedicated folder. The following instructions assume that this folder is `C:\dev\qt-source`.
2. Open "x64 Native Tools Command Prompt for VS 2019", and input the following commands:
```cmd
@@ -83,4 +83,4 @@ If is it enabled then in the output `Dynamic base` will be listed in the `DLL ch
Terminal Server Aware
```
-This may not disable all stack randomization as versions of windows employ additional stack randomization protections. These protections must be turned off in the OS configuration. \ No newline at end of file
+This may not disable all stack randomization as versions of windows employ additional stack randomization protections. These protections must be turned off in the OS configuration.
diff --git a/build_msvc/bitcoin_config.h.in b/build_msvc/bitcoin_config.h.in
index e25024e871..b37d536947 100644
--- a/build_msvc/bitcoin_config.h.in
+++ b/build_msvc/bitcoin_config.h.in
@@ -125,10 +125,6 @@
don't. */
#define HAVE_DECL_STRERROR_R 0
-/* Define to 1 if you have the declaration of `strnlen', and to 0 if you
- don't. */
-#define HAVE_DECL_STRNLEN 1
-
/* Define if the dllexport attribute is supported. */
#define HAVE_DLLEXPORT_ATTRIBUTE 1
diff --git a/build_msvc/common.init.vcxproj b/build_msvc/common.init.vcxproj.in
index 0cbe2effd5..182efff233 100644
--- a/build_msvc/common.init.vcxproj
+++ b/build_msvc/common.init.vcxproj.in
@@ -39,7 +39,7 @@
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<LinkIncremental>false</LinkIncremental>
<UseDebugLibraries>false</UseDebugLibraries>
- <PlatformToolset>v142</PlatformToolset>
+ <PlatformToolset>@TOOLSET@</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>No</GenerateManifest>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
@@ -49,7 +49,7 @@
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<LinkIncremental>true</LinkIncremental>
<UseDebugLibraries>true</UseDebugLibraries>
- <PlatformToolset>v142</PlatformToolset>
+ <PlatformToolset>@TOOLSET@</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
diff --git a/build_msvc/libsecp256k1/libsecp256k1.vcxproj b/build_msvc/libsecp256k1/libsecp256k1.vcxproj
index f9b0a7975c..16ee32d87e 100644
--- a/build_msvc/libsecp256k1/libsecp256k1.vcxproj
+++ b/build_msvc/libsecp256k1/libsecp256k1.vcxproj
@@ -8,6 +8,8 @@
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup>
+ <ClCompile Include="..\..\src\secp256k1\src\precomputed_ecmult.c" />
+ <ClCompile Include="..\..\src\secp256k1\src\precomputed_ecmult_gen.c" />
<ClCompile Include="..\..\src\secp256k1\src\secp256k1.c" />
</ItemGroup>
<ItemDefinitionGroup>
diff --git a/build_msvc/msvc-autogen.py b/build_msvc/msvc-autogen.py
index 2a70cd9332..819fe1b7ae 100755
--- a/build_msvc/msvc-autogen.py
+++ b/build_msvc/msvc-autogen.py
@@ -50,13 +50,6 @@ def parse_makefile(makefile):
lib_sources[current_lib] = []
break
-def set_common_properties(toolset):
- with open(os.path.join(SOURCE_DIR, '../build_msvc/common.init.vcxproj'), 'r', encoding='utf-8') as rfile:
- s = rfile.read()
- s = re.sub('<PlatformToolset>.*?</PlatformToolset>', '<PlatformToolset>'+toolset+'</PlatformToolset>', s)
- with open(os.path.join(SOURCE_DIR, '../build_msvc/common.init.vcxproj'), 'w', encoding='utf-8',newline='\n') as wfile:
- wfile.write(s)
-
def parse_config_into_btc_config():
def find_between( s, first, last ):
try:
@@ -92,13 +85,18 @@ def parse_config_into_btc_config():
with open(os.path.join(SOURCE_DIR,'../build_msvc/bitcoin_config.h'), "w", encoding="utf8") as btc_config:
btc_config.writelines(template)
+def set_properties(vcxproj_filename, placeholder, content):
+ with open(vcxproj_filename + '.in', 'r', encoding='utf-8') as vcxproj_in_file:
+ with open(vcxproj_filename, 'w', encoding='utf-8') as vcxproj_file:
+ vcxproj_file.write(vcxproj_in_file.read().replace(placeholder, content))
+
def main():
parser = argparse.ArgumentParser(description='Bitcoin-core msbuild configuration initialiser.')
- parser.add_argument('-toolset', nargs='?',help='Optionally sets the msbuild platform toolset, e.g. v142 for Visual Studio 2019.'
+ parser.add_argument('-toolset', nargs='?', default=DEFAULT_PLATFORM_TOOLSET,
+ help='Optionally sets the msbuild platform toolset, e.g. v142 for Visual Studio 2019.'
' default is %s.'%DEFAULT_PLATFORM_TOOLSET)
args = parser.parse_args()
- if args.toolset:
- set_common_properties(args.toolset)
+ set_properties(os.path.join(SOURCE_DIR, '../build_msvc/common.init.vcxproj'), '@TOOLSET@', args.toolset)
for makefile_name in os.listdir(SOURCE_DIR):
if 'Makefile' in makefile_name:
@@ -110,10 +108,7 @@ def main():
content += ' <ClCompile Include="..\\..\\src\\' + source_filename + '">\n'
content += ' <ObjectFileName>$(IntDir)' + object_filename + '</ObjectFileName>\n'
content += ' </ClCompile>\n'
- with open(vcxproj_filename + '.in', 'r', encoding='utf-8') as vcxproj_in_file:
- with open(vcxproj_filename, 'w', encoding='utf-8') as vcxproj_file:
- vcxproj_file.write(vcxproj_in_file.read().replace(
- '@SOURCE_FILES@\n', content))
+ set_properties(vcxproj_filename, '@SOURCE_FILES@\n', content)
parse_config_into_btc_config()
copyfile(os.path.join(SOURCE_DIR,'../build_msvc/bitcoin_config.h'), os.path.join(SOURCE_DIR, 'config/bitcoin-config.h'))
copyfile(os.path.join(SOURCE_DIR,'../build_msvc/libsecp256k1_config.h'), os.path.join(SOURCE_DIR, 'secp256k1/src/libsecp256k1-config.h'))
diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh
index 1518c033f4..8330df87eb 100755
--- a/ci/lint/04_install.sh
+++ b/ci/lint/04_install.sh
@@ -11,9 +11,9 @@ ${CI_RETRY_EXE} apt-get install -y clang-format-9 python3-pip curl git gawk jq
update-alternatives --install /usr/bin/clang-format clang-format "$(which clang-format-9 )" 100
update-alternatives --install /usr/bin/clang-format-diff clang-format-diff "$(which clang-format-diff-9)" 100
-${CI_RETRY_EXE} pip3 install codespell==2.0.0
-${CI_RETRY_EXE} pip3 install flake8==3.8.3
-${CI_RETRY_EXE} pip3 install mypy==0.910
+${CI_RETRY_EXE} pip3 install codespell==2.1.0
+${CI_RETRY_EXE} pip3 install flake8==4.0.1
+${CI_RETRY_EXE} pip3 install mypy==0.942
${CI_RETRY_EXE} pip3 install pyzmq==22.3.0
${CI_RETRY_EXE} pip3 install vulture==2.3
diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh
index e806683128..5a150d5f80 100755
--- a/ci/test/00_setup_env.sh
+++ b/ci/test/00_setup_env.sh
@@ -37,6 +37,7 @@ export USE_BUSY_BOX=${USE_BUSY_BOX:-false}
export RUN_UNIT_TESTS=${RUN_UNIT_TESTS:-true}
export RUN_FUNCTIONAL_TESTS=${RUN_FUNCTIONAL_TESTS:-true}
+export RUN_TIDY=${RUN_TIDY:-false}
export RUN_SECURITY_TESTS=${RUN_SECURITY_TESTS:-false}
# By how much to scale the test_runner timeouts (option --timeout-factor).
# This is needed because some ci machines have slow CPU or disk, so sanitizers
diff --git a/ci/test/00_setup_env_i686_multiprocess.sh b/ci/test/00_setup_env_i686_multiprocess.sh
index b333635759..766424769d 100755
--- a/ci/test/00_setup_env_i686_multiprocess.sh
+++ b/ci/test/00_setup_env_i686_multiprocess.sh
@@ -15,4 +15,3 @@ export GOAL="install"
export BITCOIN_CONFIG="--enable-debug CC='clang -m32' CXX='clang++ -m32' LDFLAGS='--rtlib=compiler-rt -lgcc_s'"
export TEST_RUNNER_ENV="BITCOIND=bitcoin-node"
export TEST_RUNNER_EXTRA="--nosandbox"
-export PIP_PACKAGES="lief"
diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh
index b03a7edb54..69883e3609 100755
--- a/ci/test/00_setup_env_native_asan.sh
+++ b/ci/test/00_setup_env_native_asan.sh
@@ -11,4 +11,4 @@ export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-
export DOCKER_NAME_TAG=ubuntu:22.04
export NO_DEPENDS=1
export GOAL="install"
-export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++"
+export BITCOIN_CONFIG="--enable-c++20 --enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++"
diff --git a/ci/test/00_setup_env_native_fuzz_with_msan.sh b/ci/test/00_setup_env_native_fuzz_with_msan.sh
index 619acc4677..071bac8fb3 100755
--- a/ci/test/00_setup_env_native_fuzz_with_msan.sh
+++ b/ci/test/00_setup_env_native_fuzz_with_msan.sh
@@ -13,10 +13,11 @@ LIBCXX_FLAGS="-nostdinc++ -stdlib=libc++ -L${LIBCXX_DIR}lib -lc++abi -I${LIBCXX_
export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}"
export CONTAINER_NAME="ci_native_msan"
-export PACKAGES="clang-9 llvm-9 cmake"
+export PACKAGES="clang-12 llvm-12 cmake"
+# BDB generates false-positives and will be removed in future
export DEP_OPTS="NO_BDB=1 NO_QT=1 CC='clang' CXX='clang++' CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}' libevent_cflags='${MSAN_FLAGS}' sqlite_cflags='${MSAN_FLAGS}' zeromq_cxxflags='-std=c++17 ${MSAN_AND_LIBCXX_FLAGS}'"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,memory --with-asm=no --prefix=${DEPENDS_DIR}/x86_64-pc-linux-gnu/ CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'"
+export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,memory --disable-hardening --with-asm=no --prefix=${DEPENDS_DIR}/x86_64-pc-linux-gnu/ CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'"
export USE_MEMORY_SANITIZER="true"
export RUN_UNIT_TESTS="false"
export RUN_FUNCTIONAL_TESTS="false"
diff --git a/ci/test/00_setup_env_native_fuzz_with_valgrind.sh b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh
index a7715a6ded..9477fb2d9f 100755
--- a/ci/test/00_setup_env_native_fuzz_with_valgrind.sh
+++ b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh
@@ -15,5 +15,6 @@ export RUN_FUNCTIONAL_TESTS=false
export RUN_FUZZ_TESTS=true
export FUZZ_TESTS_CONFIG="--valgrind"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer CC=clang CXX=clang++"
+# Temporarily pin dwarf 4, until valgrind can understand clang's dwarf 5
+export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer CC=clang CXX=clang++ CXXFLAGS='-fdebug-default-version=4'"
export CCACHE_SIZE=200M
diff --git a/ci/test/00_setup_env_native_msan.sh b/ci/test/00_setup_env_native_msan.sh
index 39ecc64f30..34a792ec8f 100755
--- a/ci/test/00_setup_env_native_msan.sh
+++ b/ci/test/00_setup_env_native_msan.sh
@@ -13,10 +13,11 @@ LIBCXX_FLAGS="-nostdinc++ -stdlib=libc++ -L${LIBCXX_DIR}lib -lc++abi -I${LIBCXX_
export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}"
export CONTAINER_NAME="ci_native_msan"
-export PACKAGES="clang-9 llvm-9 cmake"
+export PACKAGES="clang-12 llvm-12 cmake"
+# BDB generates false-positives and will be removed in future
export DEP_OPTS="NO_BDB=1 NO_QT=1 CC='clang' CXX='clang++' CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}' libevent_cflags='${MSAN_FLAGS}' sqlite_cflags='${MSAN_FLAGS}' zeromq_cxxflags='-std=c++17 ${MSAN_AND_LIBCXX_FLAGS}'"
export GOAL="install"
-export BITCOIN_CONFIG="--enable-wallet --with-sanitizers=memory --with-asm=no --prefix=${DEPENDS_DIR}/x86_64-pc-linux-gnu/ CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'"
+export BITCOIN_CONFIG="--with-sanitizers=memory --disable-hardening --with-asm=no --prefix=${DEPENDS_DIR}/x86_64-pc-linux-gnu/ CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'"
export USE_MEMORY_SANITIZER="true"
export RUN_FUNCTIONAL_TESTS="false"
export CCACHE_SIZE=250M
diff --git a/ci/test/00_setup_env_native_qt5.sh b/ci/test/00_setup_env_native_qt5.sh
index 53f933f514..7fb5186a87 100755
--- a/ci/test/00_setup_env_native_qt5.sh
+++ b/ci/test/00_setup_env_native_qt5.sh
@@ -14,6 +14,6 @@ export TEST_RUNNER_EXTRA="--previous-releases --coverage --extended --exclude fe
export RUN_UNIT_TESTS_SEQUENTIAL="true"
export RUN_UNIT_TESTS="false"
export GOAL="install"
-export PREVIOUS_RELEASES_TO_DOWNLOAD="v0.15.2 v0.16.3 v0.17.2 v0.18.1 v0.19.1 v0.20.1 v0.21.0 v22.0"
+export PREVIOUS_RELEASES_TO_DOWNLOAD="v0.14.3 v0.15.2 v0.16.3 v0.17.2 v0.18.1 v0.19.1 v0.20.1 v0.21.0 v22.0"
export BITCOIN_CONFIG="--enable-zmq --with-libs=no --with-gui=qt5 --enable-reduce-exports \
---enable-debug --disable-fuzz-binary CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\" CC=gcc-8 CXX=g++-8"
+--enable-debug CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\" CC=gcc-8 CXX=g++-8"
diff --git a/ci/test/00_setup_env_native_tidy.sh b/ci/test/00_setup_env_native_tidy.sh
new file mode 100755
index 0000000000..87dd315e2e
--- /dev/null
+++ b/ci/test/00_setup_env_native_tidy.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+#
+# 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.
+
+export LC_ALL=C.UTF-8
+
+export DOCKER_NAME_TAG="ubuntu:22.04"
+export CONTAINER_NAME=ci_native_tidy
+export PACKAGES="clang llvm clang-tidy bear libevent-dev libboost-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev systemtap-sdt-dev libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools libqrencode-dev libsqlite3-dev libdb++-dev"
+export NO_DEPENDS=1
+export RUN_UNIT_TESTS=false
+export RUN_FUNCTIONAL_TESTS=false
+export RUN_FUZZ_TESTS=false
+export RUN_TIDY=true
+export GOAL="install"
+export BITCOIN_CONFIG="CC=clang CXX=clang++ --with-incompatible-bdb --disable-hardening CFLAGS='-O0 -g0' CXXFLAGS='-O0 -g0'"
+export CCACHE_SIZE=200M
diff --git a/ci/test/00_setup_env_native_valgrind.sh b/ci/test/00_setup_env_native_valgrind.sh
index 646070a84e..7b714dff5c 100755
--- a/ci/test/00_setup_env_native_valgrind.sh
+++ b/ci/test/00_setup_env_native_valgrind.sh
@@ -13,4 +13,5 @@ export USE_VALGRIND=1
export NO_DEPENDS=1
export TEST_RUNNER_EXTRA="--nosandbox --exclude feature_init,rpc_bind,feature_bind_extra" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547
export GOAL="install"
-export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC=clang CXX=clang++" # TODO enable GUI
+# Temporarily pin dwarf 4, until valgrind can understand clang's dwarf 5
+export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC=clang CXX=clang++ CXXFLAGS='-fdebug-default-version=4'" # TODO enable GUI
diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh
index e409df62eb..c1324a0b14 100755
--- a/ci/test/04_install.sh
+++ b/ci/test/04_install.sh
@@ -103,11 +103,11 @@ fi
CI_EXEC mkdir -p "${BASE_SCRATCH_DIR}/sanitizer-output/"
if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then
- CI_EXEC "update-alternatives --install /usr/bin/clang++ clang++ \$(which clang++-9) 100"
- CI_EXEC "update-alternatives --install /usr/bin/clang clang \$(which clang-9) 100"
+ CI_EXEC "update-alternatives --install /usr/bin/clang++ clang++ \$(which clang++-12) 100"
+ CI_EXEC "update-alternatives --install /usr/bin/clang clang \$(which clang-12) 100"
CI_EXEC "mkdir -p ${BASE_SCRATCH_DIR}/msan/build/"
CI_EXEC "git clone --depth=1 https://github.com/llvm/llvm-project -b llvmorg-12.0.0 ${BASE_SCRATCH_DIR}/msan/llvm-project"
- CI_EXEC "cd ${BASE_SCRATCH_DIR}/msan/build/ && cmake -DLLVM_ENABLE_PROJECTS='libcxx;libcxxabi' -DCMAKE_BUILD_TYPE=Release -DLLVM_USE_SANITIZER=Memory -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DLLVM_TARGETS_TO_BUILD=X86 ../llvm-project/llvm/"
+ CI_EXEC "cd ${BASE_SCRATCH_DIR}/msan/build/ && cmake -DLLVM_ENABLE_PROJECTS='libcxx;libcxxabi' -DCMAKE_BUILD_TYPE=Release -DLLVM_USE_SANITIZER=MemoryWithOrigins -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DLLVM_TARGETS_TO_BUILD=X86 ../llvm-project/llvm/"
CI_EXEC "cd ${BASE_SCRATCH_DIR}/msan/build/ && make $MAKEJOBS cxx"
fi
diff --git a/ci/test/06_script_a.sh b/ci/test/06_script_a.sh
index 7158b69b4e..4742cb02ea 100755
--- a/ci/test/06_script_a.sh
+++ b/ci/test/06_script_a.sh
@@ -48,7 +48,12 @@ if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then
CI_EXEC 'grep -v HAVE_SYS_GETRANDOM src/config/bitcoin-config.h > src/config/bitcoin-config.h.tmp && mv src/config/bitcoin-config.h.tmp src/config/bitcoin-config.h'
fi
-CI_EXEC make "$MAKEJOBS" "$GOAL" || ( echo "Build failure. Verbose build follows." && CI_EXEC make "$GOAL" V=1 ; false )
+if [[ "${RUN_TIDY}" == "true" ]]; then
+ MAYBE_BEAR="bear"
+ MAYBE_TOKEN="--"
+fi
+
+CI_EXEC "${MAYBE_BEAR}" "${MAYBE_TOKEN}" make "$MAKEJOBS" "$GOAL" || ( echo "Build failure. Verbose build follows." && CI_EXEC make "$GOAL" V=1 ; false )
CI_EXEC "ccache --version | head -n 1 && ccache --show-stats"
CI_EXEC du -sh "${DEPENDS_DIR}"/*/
diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh
index de42aa6eb1..30788f1543 100755
--- a/ci/test/06_script_b.sh
+++ b/ci/test/06_script_b.sh
@@ -27,13 +27,18 @@ if [ "$RUN_UNIT_TESTS" = "true" ]; then
fi
if [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then
- CI_EXEC "${TEST_RUNNER_ENV}" DIR_UNIT_TEST_DATA="${DIR_UNIT_TEST_DATA}" LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" "${BASE_BUILD_DIR}/bitcoin-*/src/test/test_bitcoin*" --catch_system_errors=no -l test_suite
+ CI_EXEC "${TEST_RUNNER_ENV}" DIR_UNIT_TEST_DATA="${DIR_UNIT_TEST_DATA}" LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" "${BASE_OUTDIR}/bin/test_bitcoin" --catch_system_errors=no -l test_suite
fi
if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then
CI_EXEC LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" "${TEST_RUNNER_ENV}" test/functional/test_runner.py --ci "$MAKEJOBS" --tmpdirprefix "${BASE_SCRATCH_DIR}/test_runner/" --ansi --combinedlogslen=4000 --timeout-factor="${TEST_RUNNER_TIMEOUT_FACTOR}" "${TEST_RUNNER_EXTRA}" --quiet --failfast
fi
+if [ "${RUN_TIDY}" = "true" ]; then
+ export P_CI_DIR="${BASE_BUILD_DIR}/bitcoin-$HOST/src/"
+ CI_EXEC run-clang-tidy "${MAKEJOBS}"
+fi
+
if [ "$RUN_SECURITY_TESTS" = "true" ]; then
CI_EXEC make test-security-check
fi
diff --git a/ci/test/wrapped-cl.bat b/ci/test/wrapped-cl.bat
new file mode 100644
index 0000000000..fc2a604c58
--- /dev/null
+++ b/ci/test/wrapped-cl.bat
@@ -0,0 +1 @@
+ccache cl %*
diff --git a/configure.ac b/configure.ac
index 63ff6a1ebd..ee5f0e8d06 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,14 +42,9 @@ AH_TOP([#ifndef BITCOIN_CONFIG_H])
AH_TOP([#define BITCOIN_CONFIG_H])
AH_BOTTOM([#endif //BITCOIN_CONFIG_H])
-dnl faketime breaks configure and is only needed for make. Disable it here.
-unset FAKETIME
-
dnl Automake init set-up and checks
AM_INIT_AUTOMAKE([1.13 no-define subdir-objects foreign])
-dnl faketime messes with timestamps and causes configure to be re-run.
-dnl --disable-maintainer-mode can be used to bypass this.
AM_MAINTAINER_MODE([enable])
dnl make the compilation flags quiet unless V=1 is used
@@ -78,8 +73,18 @@ AC_ARG_WITH([seccomp],
[seccomp_found=$withval],
[seccomp_found=auto])
+AC_ARG_ENABLE([c++20],
+ [AS_HELP_STRING([--enable-c++20],
+ [enable compilation in c++20 mode (disabled by default)])],
+ [use_cxx20=$enableval],
+ [use_cxx20=no])
+
dnl Require C++17 compiler (no GNU extensions)
+if test "$use_cxx20" = "no"; then
AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])
+else
+AX_CXX_COMPILE_STDCXX([20], [noext], [mandatory])
+fi
dnl Check if -latomic is required for <std::atomic>
CHECK_ATOMIC
@@ -96,10 +101,8 @@ fi
AC_PROG_OBJCXX
])
-dnl Since libtool 1.5.2 (released 2004-01-25), on Linux libtool no longer
-dnl sets RPATH for any directories in the dynamic linker search path.
-dnl See more: https://wiki.debian.org/RpathIssue
-LT_PREREQ([1.5.2])
+dnl OpenBSD ships with 2.4.2
+LT_PREREQ([2.4.2])
dnl Libtool init checks.
LT_INIT([pic-only win32-dll])
@@ -358,7 +361,9 @@ case $host in
esac
if test "$enable_debug" = "yes"; then
- dnl Clear default -g -O2 flags
+ dnl If debugging is enabled, and the user hasn't overridden CXXFLAGS, clear
+ dnl them, to prevent autoconfs "-g -O2" being added. Otherwise we'd end up
+ dnl with "-O0 -g3 -g -O2".
if test "$CXXFLAGS_overridden" = "no"; then
CXXFLAGS=""
fi
@@ -465,7 +470,7 @@ if test "$CXXFLAGS_overridden" = "no"; then
fi
dnl Don't allow extended (non-ASCII) symbols in identifiers. This is easier for code review.
-AX_CHECK_COMPILE_FLAG([-fno-extended-identifiers], [CXXFLAGS="$CXXFLAGS -fno-extended-identifiers"], [], [$CXXFLAG_WERROR])
+AX_CHECK_COMPILE_FLAG([-fno-extended-identifiers], [CORE_CXXFLAGS="$CORE_CXXFLAGS -fno-extended-identifiers"], [], [$CXXFLAG_WERROR])
enable_sse42=no
enable_sse41=no
@@ -614,7 +619,7 @@ CXXFLAGS="$TEMP_CXXFLAGS"
fi
-CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO"
+CORE_CPPFLAGS="$CORE_CPPFLAGS -DHAVE_BUILD_INFO"
AC_ARG_WITH([utils],
[AS_HELP_STRING([--with-utils],
@@ -696,7 +701,7 @@ case $host in
AC_MSG_ERROR([windres not found])
fi
- CPPFLAGS="$CPPFLAGS -D_MT -DWIN32 -D_WINDOWS -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0501 -DWIN32_LEAN_AND_MEAN"
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -D_MT -DWIN32 -D_WINDOWS -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0501 -DWIN32_LEAN_AND_MEAN"
dnl libtool insists upon adding -nostdlib and a list of objects/libs to link against.
dnl That breaks our ability to build dll's with static libgcc/libstdc++/libssp. Override
@@ -707,7 +712,7 @@ case $host in
postdeps_CXX=
dnl We require Windows 7 (NT 6.1) or later
- AX_CHECK_LINK_FLAG([-Wl,--major-subsystem-version -Wl,6 -Wl,--minor-subsystem-version -Wl,1], [LDFLAGS="$LDFLAGS -Wl,--major-subsystem-version -Wl,6 -Wl,--minor-subsystem-version -Wl,1"], [], [$LDFLAG_WERROR])
+ AX_CHECK_LINK_FLAG([-Wl,--major-subsystem-version -Wl,6 -Wl,--minor-subsystem-version -Wl,1], [CORE_LDFLAGS="$CORE_LDFLAGS -Wl,--major-subsystem-version -Wl,6 -Wl,--minor-subsystem-version -Wl,1"], [], [$LDFLAG_WERROR])
;;
*darwin*)
TARGET_OS=darwin
@@ -745,20 +750,20 @@ case $host in
if test "$use_upnp" != "no" && $BREW list --versions miniupnpc >/dev/null; then
miniupnpc_prefix=$($BREW --prefix miniupnpc 2>/dev/null)
if test "$suppress_external_warnings" != "no"; then
- CPPFLAGS="$CPPFLAGS -isystem $miniupnpc_prefix/include"
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -isystem $miniupnpc_prefix/include"
else
- CPPFLAGS="$CPPFLAGS -I$miniupnpc_prefix/include"
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -I$miniupnpc_prefix/include"
fi
- LDFLAGS="$LDFLAGS -L$miniupnpc_prefix/lib"
+ CORE_LDFLAGS="$CORE_LDFLAGS -L$miniupnpc_prefix/lib"
fi
if test "$use_natpmp" != "no" && $BREW list --versions libnatpmp >/dev/null; then
libnatpmp_prefix=$($BREW --prefix libnatpmp 2>/dev/null)
if test "$suppress_external_warnings" != "no"; then
- CPPFLAGS="$CPPFLAGS -isystem $libnatpmp_prefix/include"
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -isystem $libnatpmp_prefix/include"
else
- CPPFLAGS="$CPPFLAGS -I$libnatpmp_prefix/include"
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -I$libnatpmp_prefix/include"
fi
- LDFLAGS="$LDFLAGS -L$libnatpmp_prefix/lib"
+ CORE_LDFLAGS="$CORE_LDFLAGS -L$libnatpmp_prefix/lib"
fi
;;
esac
@@ -784,8 +789,8 @@ case $host in
esac
fi
- AX_CHECK_LINK_FLAG([-Wl,-headerpad_max_install_names], [LDFLAGS="$LDFLAGS -Wl,-headerpad_max_install_names"], [], [$LDFLAG_WERROR])
- CPPFLAGS="$CPPFLAGS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0"
+ AX_CHECK_LINK_FLAG([-Wl,-headerpad_max_install_names], [CORE_LDFLAGS="$CORE_LDFLAGS -Wl,-headerpad_max_install_names"], [], [$LDFLAG_WERROR])
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0"
OBJCXXFLAGS="$CXXFLAGS"
;;
*android*)
@@ -848,11 +853,17 @@ if test "$use_lcov" = "yes"; then
AC_SUBST(COV_TOOL_WRAPPER, "cov_tool_wrapper.sh")
LCOV="$LCOV --gcov-tool $(pwd)/$COV_TOOL_WRAPPER"
- AX_CHECK_LINK_FLAG([--coverage], [LDFLAGS="$LDFLAGS --coverage"],
+ AX_CHECK_LINK_FLAG([--coverage], [CORE_LDFLAGS="$CORE_LDFLAGS --coverage"],
[AC_MSG_ERROR([lcov testing requested but --coverage linker flag does not work])])
- AX_CHECK_COMPILE_FLAG([--coverage],[CXXFLAGS="$CXXFLAGS --coverage"],
+ AX_CHECK_COMPILE_FLAG([--coverage],[CORE_CXXFLAGS="$CORE_CXXFLAGS --coverage"],
[AC_MSG_ERROR([lcov testing requested but --coverage flag does not work])])
- CXXFLAGS="$CXXFLAGS -Og"
+ dnl If coverage is enabled, and the user hasn't overridden CXXFLAGS, clear
+ dnl them, to prevent autoconfs "-g -O2" being added. Otherwise we'd end up
+ dnl with "--coverage -Og -O0 -g -O2".
+ if test "$CXXFLAGS_overridden" = "no"; then
+ CXXFLAGS=""
+ fi
+ CORE_CXXFLAGS="$CORE_CXXFLAGS -Og -O0"
fi
if test "$use_lcov_branch" != "no"; then
@@ -875,13 +886,13 @@ AC_FUNC_STRERROR_R
if test "$ac_cv_sys_file_offset_bits" != "" &&
test "$ac_cv_sys_file_offset_bits" != "no" &&
test "$ac_cv_sys_file_offset_bits" != "unknown"; then
- CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits"
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits"
fi
if test "$ac_cv_sys_large_files" != "" &&
test "$ac_cv_sys_large_files" != "no" &&
test "$ac_cv_sys_large_files" != "unknown"; then
- CPPFLAGS="$CPPFLAGS -D_LARGE_FILES=$ac_cv_sys_large_files"
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -D_LARGE_FILES=$ac_cv_sys_large_files"
fi
AC_SEARCH_LIBS([clock_gettime],[rt])
@@ -965,8 +976,8 @@ dnl These flags are specific to ld64, and may cause issues with other linkers.
dnl For example: GNU ld will interpret -dead_strip as -de and then try and use
dnl "ad_strip" as the symbol for the entry point.
if test "$TARGET_OS" = "darwin"; then
- AX_CHECK_LINK_FLAG([-Wl,-dead_strip], [LDFLAGS="$LDFLAGS -Wl,-dead_strip"], [], [$LDFLAG_WERROR])
- AX_CHECK_LINK_FLAG([-Wl,-dead_strip_dylibs], [LDFLAGS="$LDFLAGS -Wl,-dead_strip_dylibs"], [], [$LDFLAG_WERROR])
+ AX_CHECK_LINK_FLAG([-Wl,-dead_strip], [CORE_LDFLAGS="$CORE_LDFLAGS -Wl,-dead_strip"], [], [$LDFLAG_WERROR])
+ AX_CHECK_LINK_FLAG([-Wl,-dead_strip_dylibs], [CORE_LDFLAGS="$CORE_LDFLAGS -Wl,-dead_strip_dylibs"], [], [$LDFLAG_WERROR])
AX_CHECK_LINK_FLAG([-Wl,-bind_at_load], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-bind_at_load"], [], [$LDFLAG_WERROR])
fi
@@ -976,7 +987,6 @@ AC_CHECK_DECLS([getifaddrs, freeifaddrs],[CHECK_SOCKET],,
[#include <sys/types.h>
#include <ifaddrs.h>]
)
-AC_CHECK_DECLS([strnlen])
dnl These are used for daemonization in bitcoind
AC_CHECK_DECLS([fork])
@@ -1266,7 +1276,7 @@ dnl Do not change "-I/usr/include" to "-isystem /usr/include" because that
dnl is not necessary (/usr/include is already a system directory) and because
dnl it would break GCC's #include_next.
AC_DEFUN([SUPPRESS_WARNINGS],
- [$(echo $1 |${SED} -E -e 's/(^| )-I/\1-isystem /g' -e 's;-isystem /usr/include([/ ]|$);-I/usr/include\1;g')])
+ [[$(echo $1 |${SED} -E -e 's/(^| )-I/\1-isystem /g' -e 's;-isystem /usr/include/*( |$);-I/usr/include\1;g')]])
dnl enable-fuzz should disable all other targets
if test "$enable_fuzz" = "yes"; then
@@ -1290,21 +1300,6 @@ if test "$enable_fuzz" = "yes"; then
enable_fuzz_binary=yes
AX_CHECK_PREPROC_FLAG([-DABORT_ON_FAILED_ASSUME], [DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DABORT_ON_FAILED_ASSUME"], [], [$CXXFLAG_WERROR])
-
- AC_MSG_CHECKING([whether main function is needed for fuzz binary])
- AX_CHECK_LINK_FLAG(
- [-fsanitize=$use_sanitizers],
- [AC_MSG_RESULT([no])],
- [AC_MSG_RESULT([yes]); CPPFLAGS="$CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"],
- [],
- [AC_LANG_PROGRAM([[
- #include <cstdint>
- #include <cstddef>
- extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { return 0; }
- /* comment to remove the main function ...
- ]],[[
- */ int not_main() {
- ]])])
else
BITCOIN_QT_INIT
@@ -1318,8 +1313,25 @@ else
QT_DBUS_INCLUDES=SUPPRESS_WARNINGS($QT_DBUS_INCLUDES)
QT_TEST_INCLUDES=SUPPRESS_WARNINGS($QT_TEST_INCLUDES)
fi
+fi
- CPPFLAGS="$CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"
+if test "$enable_fuzz_binary" = "yes"; then
+ AC_MSG_CHECKING([whether main function is needed for fuzz binary])
+ AX_CHECK_LINK_FLAG(
+ [],
+ [AC_MSG_RESULT([no])],
+ [AC_MSG_RESULT([yes]); CORE_CPPFLAGS="$CORE_CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"],
+ [$SANITIZER_LDFLAGS],
+ [AC_LANG_PROGRAM([[
+ #include <cstdint>
+ #include <cstddef>
+ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return 0; }
+ /* comment to remove the main function ...
+ ]],[[
+ */ int not_main() {
+ ]])])
+
+ CHECK_RUNTIME_LIB
fi
if test "$enable_wallet" != "no"; then
@@ -1371,6 +1383,7 @@ if test "$use_usdt" != "no"; then
[AC_MSG_RESULT([no]); use_usdt=no;]
)
fi
+AM_CONDITIONAL([ENABLE_USDT_TRACEPOINTS], [test "$use_usdt" = "yes"])
if test "$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_util$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests" = "nonononononono"; then
use_upnp=no
@@ -1428,6 +1441,9 @@ if test "$use_boost" = "yes"; then
AC_MSG_ERROR([only libbitcoinconsensus can be built without Boost])
fi
+ dnl we don't use multi_index serialization
+ BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION"
+
if test "$suppress_external_warnings" != "no"; then
BOOST_CPPFLAGS=SUPPRESS_WARNINGS($BOOST_CPPFLAGS)
fi
@@ -1446,6 +1462,10 @@ if test "$use_external_signer" != "no"; then
;;
*)
AC_MSG_CHECKING([whether Boost.Process can be used])
+ TEMP_CXXFLAGS="$CXXFLAGS"
+ dnl Boost 1.78 requires the following workaround.
+ dnl See: https://github.com/boostorg/process/issues/235
+ CXXFLAGS="$CXXFLAGS -Wno-error=narrowing"
TEMP_CPPFLAGS="$CPPFLAGS"
CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
TEMP_LDFLAGS="$LDFLAGS"
@@ -1456,6 +1476,7 @@ if test "$use_external_signer" != "no"; then
[have_boost_process="no"])
LDFLAGS="$TEMP_LDFLAGS"
CPPFLAGS="$TEMP_CPPFLAGS"
+ CXXFLAGS="$TEMP_CXXFLAGS"
AC_MSG_RESULT([$have_boost_process])
if test "$have_boost_process" = "yes"; then
use_external_signer="yes"
@@ -1503,7 +1524,7 @@ AM_CONDITIONAL([ENABLE_SYSCALL_SANDBOX], [test "$use_syscall_sandbox" != "no"])
dnl Check for reduced exports
if test "$use_reduce_exports" = "yes"; then
- AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [CXXFLAGS="$CXXFLAGS -fvisibility=hidden"],
+ AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [CORE_CXXFLAGS="$CORE_CXXFLAGS -fvisibility=hidden"],
[AC_MSG_ERROR([Cannot set hidden symbol visibility. Use --disable-reduce-exports.])], [$CXXFLAG_WERROR])
AX_CHECK_LINK_FLAG([-Wl,--exclude-libs,ALL], [RELDFLAGS="-Wl,--exclude-libs,ALL"], [], [$LDFLAG_WERROR])
fi
@@ -1518,9 +1539,9 @@ fi
dnl libevent check
if test "$build_bitcoin_cli$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench" != "nonononono"; then
- PKG_CHECK_MODULES([EVENT], [libevent >= 2.0.21], [use_libevent=yes], [AC_MSG_ERROR([libevent version 2.0.21 or greater not found.])])
+ PKG_CHECK_MODULES([EVENT], [libevent >= 2.1.8], [use_libevent=yes], [AC_MSG_ERROR([libevent version 2.1.8 or greater not found.])])
if test "$TARGET_OS" != "windows"; then
- PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads >= 2.0.21],, [AC_MSG_ERROR([libevent_pthreads version 2.0.21 or greater not found.])])
+ PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads >= 2.1.8], [], [AC_MSG_ERROR([libevent_pthreads version 2.1.8 or greater not found.])])
fi
if test "$suppress_external_warnings" != "no"; then
@@ -1800,10 +1821,6 @@ if test "$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_
AC_MSG_ERROR([No targets! Please specify at least one of: --with-utils --with-libs --with-daemon --with-gui --enable-bench or --enable-tests])
fi
-if test "$enable_fuzz_binary" = "yes"; then
- CHECK_RUNTIME_LIB
-fi
-
AM_CONDITIONAL([TARGET_DARWIN], [test "$TARGET_OS" = "darwin"])
AM_CONDITIONAL([BUILD_DARWIN], [test "$BUILD_OS" = "darwin"])
AM_CONDITIONAL([TARGET_LINUX], [test "$TARGET_OS" = "linux"])
@@ -1864,6 +1881,9 @@ AC_SUBST(BITCOIN_MP_NODE_NAME)
AC_SUBST(BITCOIN_MP_GUI_NAME)
AC_SUBST(RELDFLAGS)
+AC_SUBST(CORE_LDFLAGS)
+AC_SUBST(CORE_CPPFLAGS)
+AC_SUBST(CORE_CXXFLAGS)
AC_SUBST(DEBUG_CPPFLAGS)
AC_SUBST(WARN_CXXFLAGS)
AC_SUBST(NOWARN_CXXFLAGS)
@@ -1916,6 +1936,7 @@ AC_CONFIG_LINKS([contrib/devtools/test-security-check.py:contrib/devtools/test-s
AC_CONFIG_LINKS([contrib/devtools/test-symbol-check.py:contrib/devtools/test-symbol-check.py])
AC_CONFIG_LINKS([contrib/filter-lcov.py:contrib/filter-lcov.py])
AC_CONFIG_LINKS([contrib/macdeploy/background.tiff:contrib/macdeploy/background.tiff])
+AC_CONFIG_LINKS([src/.clang-tidy:src/.clang-tidy])
AC_CONFIG_LINKS([test/functional/test_runner.py:test/functional/test_runner.py])
AC_CONFIG_LINKS([test/fuzz/test_runner.py:test/fuzz/test_runner.py])
AC_CONFIG_LINKS([test/util/test_runner.py:test/util/test_runner.py])
@@ -1936,15 +1957,7 @@ LIBS_TEMP="$LIBS"
unset LIBS
LIBS="$LIBS_TEMP"
-PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH"
-unset PKG_CONFIG_PATH
-PKG_CONFIG_PATH="$PKGCONFIG_PATH_TEMP"
-
-PKGCONFIG_LIBDIR_TEMP="$PKG_CONFIG_LIBDIR"
-unset PKG_CONFIG_LIBDIR
-PKG_CONFIG_LIBDIR="$PKGCONFIG_LIBDIR_TEMP"
-
-ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-schnorrsig --enable-experimental"
+ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-schnorrsig"
AC_CONFIG_SUBDIRS([src/secp256k1])
AC_OUTPUT
@@ -1995,9 +2008,9 @@ echo " build os = $build_os"
echo
echo " CC = $CC"
echo " CFLAGS = $PTHREAD_CFLAGS $CFLAGS"
-echo " CPPFLAGS = $DEBUG_CPPFLAGS $HARDENED_CPPFLAGS $CPPFLAGS"
+echo " CPPFLAGS = $DEBUG_CPPFLAGS $HARDENED_CPPFLAGS $CORE_CPPFLAGS $CPPFLAGS"
echo " CXX = $CXX"
-echo " CXXFLAGS = $LTO_CXXFLAGS $DEBUG_CXXFLAGS $HARDENED_CXXFLAGS $WARN_CXXFLAGS $NOWARN_CXXFLAGS $ERROR_CXXFLAGS $GPROF_CXXFLAGS $CXXFLAGS"
-echo " LDFLAGS = $LTO_LDFLAGS $PTHREAD_LIBS $HARDENED_LDFLAGS $GPROF_LDFLAGS $LDFLAGS"
+echo " CXXFLAGS = $LTO_CXXFLAGS $DEBUG_CXXFLAGS $HARDENED_CXXFLAGS $WARN_CXXFLAGS $NOWARN_CXXFLAGS $ERROR_CXXFLAGS $GPROF_CXXFLAGS $CORE_CXXFLAGS $CXXFLAGS"
+echo " LDFLAGS = $LTO_LDFLAGS $PTHREAD_LIBS $HARDENED_LDFLAGS $GPROF_LDFLAGS $CORE_LDFLAGS $LDFLAGS"
echo " ARFLAGS = $ARFLAGS"
echo
diff --git a/contrib/builder-keys/keys.txt b/contrib/builder-keys/keys.txt
index e8032f66ee..c70069b440 100644
--- a/contrib/builder-keys/keys.txt
+++ b/contrib/builder-keys/keys.txt
@@ -13,6 +13,7 @@ F20F56EF6A067F70E8A5C99FFF95FAA971697405 centaur (centaur)
C060A6635913D98A3587D7DB1C2491FFEB0EF770 Cory Fields (cfields)
BF6273FAEF7CC0BA1F562E50989F6B3048A116B5 Dev Random (devrandom)
6D3170C1DC2C6FD0AEEBCA6743811D1A26623924 Douglas Roark (droark)
+948444FCE03B05BA5AB0591EC37B1C1D44C786EE Duncan Dean (dunxen)
1C6621605EC50319C463D56C7F81D87985D61612 Emanuele Cisbani (cisba)
9A1689B60D1B3CCE9262307A2F40A9BF167FBA47 Erik Mossberg (erkmos)
D35176BE9264832E4ACA8986BF0792FBE95DC863 fivepiece (fivepiece)
@@ -50,5 +51,6 @@ ED9BDF7AD6A55E232E84524257FF9BDBCC301009 Sjors Provoost (sjors)
9EDAFF80E080659604F4A76B2EBB056FD847F8A7 Stephan Oeste (Emzy)
6DEEF79B050C4072509B743F8C275BC595448867 Tomas Kanocz (KanoczTomas)
AEC1884398647C47413C1C3FB1179EB7347DC10D Warren Togami (wtogami)
+74E2DEF5D77260B98BC19438099BAD163C70FBFA Will Clark (will8clark)
79D00BAC68B56D422F945A8F8E3A8F3247DBCBBF Willy Ko (willyko)
71A3B16735405025D447E8F274810B012346C9A6 Wladimir J. van der Laan (laanwj)
diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py
index e6a29b73b9..05c0af029e 100755
--- a/contrib/devtools/security-check.py
+++ b/contrib/devtools/security-check.py
@@ -12,10 +12,6 @@ from typing import List
import lief #type:ignore
-# temporary constant, to be replaced with lief.ELF.ARCH.RISCV
-# https://github.com/lief-project/LIEF/pull/562
-LIEF_ELF_ARCH_RISCV = lief.ELF.ARCH(243)
-
def check_ELF_RELRO(binary) -> bool:
'''
Check for read-only relocations.
@@ -101,7 +97,6 @@ def check_ELF_separate_code(binary):
for segment in binary.segments:
if segment.type == lief.ELF.SEGMENT_TYPES.LOAD:
for section in segment.sections:
- assert(section.name not in flags_per_section)
flags_per_section[section.name] = segment.flags
# Spot-check ELF LOAD program header flags per section
# If these sections exist, check them against the expected R/W/E flags
@@ -222,7 +217,7 @@ CHECKS = {
lief.ARCHITECTURES.ARM: BASE_ELF,
lief.ARCHITECTURES.ARM64: BASE_ELF,
lief.ARCHITECTURES.PPC: BASE_ELF,
- LIEF_ELF_ARCH_RISCV: BASE_ELF,
+ lief.ARCHITECTURES.RISCV: BASE_ELF,
},
lief.EXE_FORMATS.PE: {
lief.ARCHITECTURES.X86: BASE_PE,
@@ -250,12 +245,9 @@ if __name__ == '__main__':
continue
if arch == lief.ARCHITECTURES.NONE:
- if binary.header.machine_type == LIEF_ELF_ARCH_RISCV:
- arch = LIEF_ELF_ARCH_RISCV
- else:
- print(f'{filename}: unknown architecture')
- retval = 1
- continue
+ print(f'{filename}: unknown architecture')
+ retval = 1
+ continue
failed: List[str] = []
for (name, func) in CHECKS[etype][arch]:
diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py
index 461132ae63..a419e392ee 100755
--- a/contrib/devtools/symbol-check.py
+++ b/contrib/devtools/symbol-check.py
@@ -15,10 +15,6 @@ from typing import List, Dict
import lief #type:ignore
-# temporary constant, to be replaced with lief.ELF.ARCH.RISCV
-# https://github.com/lief-project/LIEF/pull/562
-LIEF_ELF_ARCH_RISCV = lief.ELF.ARCH(243)
-
# Debian 9 (Stretch) EOL: 2022. https://wiki.debian.org/DebianReleases#Production_Releases
#
# - g++ version 6.3.0 (https://packages.debian.org/search?suite=stretch&arch=any&searchon=names&keywords=g%2B%2B)
@@ -44,7 +40,7 @@ MAX_VERSIONS = {
lief.ELF.ARCH.ARM: (2,18),
lief.ELF.ARCH.AARCH64:(2,18),
lief.ELF.ARCH.PPC64: (2,18),
- LIEF_ELF_ARCH_RISCV: (2,27),
+ lief.ELF.ARCH.RISCV: (2,27),
},
'LIBATOMIC': (1,0),
'V': (0,5,0), # xkb (bitcoin-qt only)
@@ -78,7 +74,7 @@ ELF_INTERPRETER_NAMES: Dict[lief.ELF.ARCH, Dict[lief.ENDIANNESS, str]] = {
lief.ENDIANNESS.BIG: "/lib64/ld64.so.1",
lief.ENDIANNESS.LITTLE: "/lib64/ld64.so.2",
},
- LIEF_ELF_ARCH_RISCV: {
+ lief.ELF.ARCH.RISCV: {
lief.ENDIANNESS.LITTLE: "/lib/ld-linux-riscv64-lp64d.so.1",
},
}
@@ -200,7 +196,7 @@ def check_exported_symbols(binary) -> bool:
if not symbol.exported:
continue
name = symbol.name
- if binary.header.machine_type == LIEF_ELF_ARCH_RISCV or name in IGNORE_EXPORTS:
+ if binary.header.machine_type == lief.ELF.ARCH.RISCV or name in IGNORE_EXPORTS:
continue
print(f'{binary.name}: export of symbol {name} not allowed!')
ok = False
diff --git a/contrib/guix/README.md b/contrib/guix/README.md
index 90289f9d40..af5607c710 100644
--- a/contrib/guix/README.md
+++ b/contrib/guix/README.md
@@ -75,7 +75,7 @@ crucial differences:
1. Since only Windows and macOS build outputs require codesigning, the `HOSTS`
environment variable will have a sane default value of `x86_64-w64-mingw32
- x86_64-apple-darwin` instead of all the platforms.
+ x86_64-apple-darwin arm64-apple-darwin` instead of all the platforms.
2. The `guix-codesign` command ***requires*** a `DETACHED_SIGS_REPO` flag.
* _**DETACHED_SIGS_REPO**_
diff --git a/contrib/guix/guix-attest b/contrib/guix/guix-attest
index 6e12cbead7..b0ef28dc3f 100755
--- a/contrib/guix/guix-attest
+++ b/contrib/guix/guix-attest
@@ -19,8 +19,16 @@ source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
################
check_tools cat env basename mkdir diff sort
+
if [ -z "$NO_SIGN" ]; then
- check_tools gpg
+ # make it possible to override the gpg binary
+ GPG=${GPG:-gpg}
+
+ # $GPG can contain extra arguments passed to the binary
+ # so let's check only the existence of arg[0]
+ # shellcheck disable=SC2206
+ GPG_ARRAY=($GPG)
+ check_tools "${GPG_ARRAY[0]}"
fi
################
@@ -90,7 +98,7 @@ if [ -z "${signer_name}" ]; then
signer_name="$gpg_key_name"
fi
-if [ -z "$NO_SIGN" ] && ! gpg --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then
+if [ -z "$NO_SIGN" ] && ! ${GPG} --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then
echo "ERR: GPG can't seem to find any key named '${gpg_key_name}'"
exit 1
fi
@@ -239,11 +247,11 @@ mkdir -p "$outsigdir"
echo "Signing SHA256SUMS to produce SHA256SUMS.asc"
for i in *.SHA256SUMS; do
if [ ! -e "$i".asc ]; then
- gpg --detach-sign \
- --digest-algo sha256 \
- --local-user "$gpg_key_name" \
- --armor \
- --output "$i".asc "$i"
+ ${GPG} --detach-sign \
+ --digest-algo sha256 \
+ --local-user "$gpg_key_name" \
+ --armor \
+ --output "$i".asc "$i"
else
echo "Signature already there"
fi
diff --git a/contrib/guix/guix-codesign b/contrib/guix/guix-codesign
index 94895d3fee..3279d431aa 100755
--- a/contrib/guix/guix-codesign
+++ b/contrib/guix/guix-codesign
@@ -152,10 +152,10 @@ outdir_for_host() {
unsigned_tarball_for_host() {
case "$1" in
*mingw*)
- echo "$(outdir_for_host "$1")/${DISTNAME}-win-unsigned.tar.gz"
+ echo "$(outdir_for_host "$1")/${DISTNAME}-win64-unsigned.tar.gz"
;;
*darwin*)
- echo "$(outdir_for_host "$1")/${DISTNAME}-osx-unsigned.tar.gz"
+ echo "$(outdir_for_host "$1")/${DISTNAME}-${1}-unsigned.tar.gz"
;;
*)
exit 1
diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh
index e06a469338..4eeb360603 100755
--- a/contrib/guix/libexec/build.sh
+++ b/contrib/guix/libexec/build.sh
@@ -167,7 +167,6 @@ case "$HOST" in
*linux*)
glibc_dynamic_linker=$(
case "$HOST" in
- i686-linux-gnu) echo /lib/ld-linux.so.2 ;;
x86_64-linux-gnu) echo /lib64/ld-linux-x86-64.so.2 ;;
arm-linux-gnueabihf) echo /lib/ld-linux-armhf.so.3 ;;
aarch64-linux-gnu) echo /lib/ld-linux-aarch64.so.1 ;;
@@ -204,19 +203,12 @@ make -C depends --jobs="$JOBS" HOST="$HOST" \
${SOURCES_PATH+SOURCES_PATH="$SOURCES_PATH"} \
${BASE_CACHE+BASE_CACHE="$BASE_CACHE"} \
${SDK_PATH+SDK_PATH="$SDK_PATH"} \
- i686_linux_CC=i686-linux-gnu-gcc \
- i686_linux_CXX=i686-linux-gnu-g++ \
- i686_linux_AR=i686-linux-gnu-ar \
- i686_linux_RANLIB=i686-linux-gnu-ranlib \
- i686_linux_NM=i686-linux-gnu-nm \
- i686_linux_STRIP=i686-linux-gnu-strip \
x86_64_linux_CC=x86_64-linux-gnu-gcc \
x86_64_linux_CXX=x86_64-linux-gnu-g++ \
x86_64_linux_AR=x86_64-linux-gnu-ar \
x86_64_linux_RANLIB=x86_64-linux-gnu-ranlib \
x86_64_linux_NM=x86_64-linux-gnu-nm \
x86_64_linux_STRIP=x86_64-linux-gnu-strip \
- qt_config_opts_i686_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \
qt_config_opts_x86_64_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \
FORCE_USE_SYSTEM_CLANG=1
@@ -340,7 +332,7 @@ mkdir -p "$DISTSRC"
mkdir -p "unsigned-app-${HOST}"
cp --target-directory="unsigned-app-${HOST}" \
osx_volname \
- contrib/macdeploy/detached-sig-{apply,create}.sh \
+ contrib/macdeploy/detached-sig-create.sh \
"${BASEPREFIX}/${HOST}"/native/bin/dmg
mv --target-directory="unsigned-app-${HOST}" dist
(
@@ -348,10 +340,10 @@ mkdir -p "$DISTSRC"
find . -print0 \
| sort --zero-terminated \
| tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
- | gzip -9n > "${OUTDIR}/${DISTNAME}-osx-unsigned.tar.gz" \
- || ( rm -f "${OUTDIR}/${DISTNAME}-osx-unsigned.tar.gz" && exit 1 )
+ | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" && exit 1 )
)
- make deploy ${V:+V=1} OSX_DMG="${OUTDIR}/${DISTNAME}-osx-unsigned.dmg"
+ make deploy ${V:+V=1} OSX_DMG="${OUTDIR}/${DISTNAME}-${HOST}-unsigned.dmg"
;;
esac
(
@@ -423,8 +415,8 @@ mkdir -p "$DISTSRC"
find "${DISTNAME}" -print0 \
| sort --zero-terminated \
| tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
- | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST//x86_64-apple-darwin/osx64}.tar.gz" \
- || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-apple-darwin/osx64}.tar.gz" && exit 1 )
+ | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 )
;;
esac
) # $DISTSRC/installed
@@ -439,8 +431,8 @@ mkdir -p "$DISTSRC"
find . -print0 \
| sort --zero-terminated \
| tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
- | gzip -9n > "${OUTDIR}/${DISTNAME}-win-unsigned.tar.gz" \
- || ( rm -f "${OUTDIR}/${DISTNAME}-win-unsigned.tar.gz" && exit 1 )
+ | gzip -9n > "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" \
+ || ( rm -f "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" && exit 1 )
)
;;
esac
diff --git a/contrib/guix/libexec/codesign.sh b/contrib/guix/libexec/codesign.sh
index a8867ca351..6ede95f42b 100755
--- a/contrib/guix/libexec/codesign.sh
+++ b/contrib/guix/libexec/codesign.sh
@@ -91,7 +91,7 @@ mkdir -p "$DISTSRC"
-- -volume_date all_file_dates ="$SOURCE_DATE_EPOCH"
# Compress uncompressed.dmg and output to OUTDIR
- ./dmg dmg uncompressed.dmg "${OUTDIR}/${DISTNAME}-osx-signed.dmg"
+ ./dmg dmg uncompressed.dmg "${OUTDIR}/${DISTNAME}-${HOST}.dmg"
;;
*)
exit 1
diff --git a/contrib/guix/libexec/prelude.bash b/contrib/guix/libexec/prelude.bash
index 086df48fbe..f24c120863 100644
--- a/contrib/guix/libexec/prelude.bash
+++ b/contrib/guix/libexec/prelude.bash
@@ -51,7 +51,7 @@ fi
time-machine() {
# shellcheck disable=SC2086
guix time-machine --url=https://git.savannah.gnu.org/git/guix.git \
- --commit=ae03f401381e956c4c41b4cf495cbde964fa43d0 \
+ --commit=34e9eae68c9583acce5abc4100add3d88932a5ae \
--cores="$JOBS" \
--keep-failed \
--fallback \
diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm
index a0afb7b4f6..fcec592c2c 100644
--- a/contrib/guix/manifest.scm
+++ b/contrib/guix/manifest.scm
@@ -162,9 +162,9 @@ desirable for building Bitcoin Core release binaries."
(define (make-gcc-with-pthreads gcc)
(package-with-extra-configure-variable gcc "--enable-threads" "posix"))
-;; Required to support std::filesystem for mingw-w64 target.
-(define (make-gcc-without-newlib gcc)
- (package-with-extra-configure-variable gcc "--with-newlib" "no"))
+(define (make-mingw-w64-cross-gcc-vmov-alignment cross-gcc)
+ (package-with-extra-patches cross-gcc
+ (search-our-patches "vmov-alignment.patch")))
(define (make-mingw-pthreads-cross-toolchain target)
"Create a cross-compilation toolchain package for TARGET"
@@ -172,7 +172,7 @@ desirable for building Bitcoin Core release binaries."
(pthreads-xlibc mingw-w64-x86_64-winpthreads)
(pthreads-xgcc (make-gcc-with-pthreads
(cross-gcc target
- #:xgcc (make-gcc-without-newlib (make-ssp-fixed-gcc base-gcc))
+ #:xgcc (make-ssp-fixed-gcc (make-mingw-w64-cross-gcc-vmov-alignment base-gcc))
#:xbinutils xbinutils
#:libc pthreads-xlibc))))
;; Define a meta-package that propagates the resulting XBINUTILS, XLIBC, and
@@ -201,7 +201,7 @@ chain for " target " development."))
(define-public lief
(package
(name "python-lief")
- (version "0.11.5")
+ (version "0.12.0")
(source
(origin
(method git-fetch)
@@ -211,7 +211,7 @@ chain for " target " development."))
(file-name (git-file-name name version))
(sha256
(base32
- "0qahjfg1n0x76ps2mbyljvws1l3qhkqvmxqbahps4qgywl2hbdkj"))))
+ "026jchj56q25v6gc0754dj9cj5hz5zaza8ij93y5ga94w20kzm9q"))))
(build-system python-build-system)
(native-inputs
`(("cmake" ,cmake)))
@@ -485,7 +485,7 @@ and endian independent.")
(license license:expat)))
(define-public python-signapple
- (let ((commit "0777ce58e61b0e6be753a5f524149d6d47905186"))
+ (let ((commit "8a945a2e7583be2665cf3a6a89d665b70ecd1ab6"))
(package
(name "python-signapple")
(version (git-version "0.1" "1" commit))
@@ -498,7 +498,7 @@ and endian independent.")
(file-name (git-file-name name commit))
(sha256
(base32
- "19axspyyfqbrfw2r53c17mi9bvm8zsb39mz8v9h7c173qkm3x5ym"))))
+ "0fr1hangvfyiwflca6jg5g8zvg3jc9qr7vd2c12ff89pznf38dlg"))))
(build-system python-build-system)
(propagated-inputs
`(("python-asn1crypto" ,python-asn1crypto)
@@ -506,8 +506,7 @@ and endian independent.")
("python-certvalidator" ,python-certvalidator)
("python-elfesteem" ,python-elfesteem)
("python-requests" ,python-requests)
- ("python-macholib" ,python-macholib)
- ("libcrypto" ,openssl)))
+ ("python-macholib" ,python-macholib)))
;; There are no tests, but attempting to run python setup.py test leads to
;; problems, just disable the test
(arguments '(#:tests? #f))
diff --git a/contrib/guix/patches/vmov-alignment.patch b/contrib/guix/patches/vmov-alignment.patch
new file mode 100644
index 0000000000..072f76eafd
--- /dev/null
+++ b/contrib/guix/patches/vmov-alignment.patch
@@ -0,0 +1,267 @@
+Description: Use unaligned VMOV instructions
+Author: Stephen Kitt <skitt@debian.org>
+Bug-Debian: https://bugs.debian.org/939559
+
+Based on a patch originally by Claude Heiland-Allen <claude@mathr.co.uk>
+
+--- a/gcc/config/i386/sse.md
++++ b/gcc/config/i386/sse.md
+@@ -1058,17 +1058,11 @@
+ {
+ if (FLOAT_MODE_P (GET_MODE_INNER (<MODE>mode)))
+ {
+- if (misaligned_operand (operands[1], <MODE>mode))
+- return "vmovu<ssemodesuffix>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}";
+- else
+- return "vmova<ssemodesuffix>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}";
++ return "vmovu<ssemodesuffix>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}";
+ }
+ else
+ {
+- if (misaligned_operand (operands[1], <MODE>mode))
+- return "vmovdqu<ssescalarsize>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}";
+- else
+- return "vmovdqa<ssescalarsize>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}";
++ return "vmovdqu<ssescalarsize>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}";
+ }
+ }
+ [(set_attr "type" "ssemov")
+@@ -1184,17 +1178,11 @@
+ {
+ if (FLOAT_MODE_P (GET_MODE_INNER (<MODE>mode)))
+ {
+- if (misaligned_operand (operands[0], <MODE>mode))
+- return "vmovu<ssemodesuffix>\t{%1, %0%{%2%}|%0%{%2%}, %1}";
+- else
+- return "vmova<ssemodesuffix>\t{%1, %0%{%2%}|%0%{%2%}, %1}";
++ return "vmovu<ssemodesuffix>\t{%1, %0%{%2%}|%0%{%2%}, %1}";
+ }
+ else
+ {
+- if (misaligned_operand (operands[0], <MODE>mode))
+- return "vmovdqu<ssescalarsize>\t{%1, %0%{%2%}|%0%{%2%}, %1}";
+- else
+- return "vmovdqa<ssescalarsize>\t{%1, %0%{%2%}|%0%{%2%}, %1}";
++ return "vmovdqu<ssescalarsize>\t{%1, %0%{%2%}|%0%{%2%}, %1}";
+ }
+ }
+ [(set_attr "type" "ssemov")
+@@ -7806,7 +7794,7 @@
+ "TARGET_SSE && !(MEM_P (operands[0]) && MEM_P (operands[1]))"
+ "@
+ %vmovlps\t{%1, %0|%q0, %1}
+- %vmovaps\t{%1, %0|%0, %1}
++ %vmovups\t{%1, %0|%0, %1}
+ %vmovlps\t{%1, %d0|%d0, %q1}"
+ [(set_attr "type" "ssemov")
+ (set_attr "prefix" "maybe_vex")
+@@ -13997,29 +13985,15 @@
+ switch (<MODE>mode)
+ {
+ case E_V8DFmode:
+- if (misaligned_operand (operands[2], <ssequartermode>mode))
+- return "vmovupd\t{%2, %x0|%x0, %2}";
+- else
+- return "vmovapd\t{%2, %x0|%x0, %2}";
++ return "vmovupd\t{%2, %x0|%x0, %2}";
+ case E_V16SFmode:
+- if (misaligned_operand (operands[2], <ssequartermode>mode))
+- return "vmovups\t{%2, %x0|%x0, %2}";
+- else
+- return "vmovaps\t{%2, %x0|%x0, %2}";
++ return "vmovups\t{%2, %x0|%x0, %2}";
+ case E_V8DImode:
+- if (misaligned_operand (operands[2], <ssequartermode>mode))
+- return which_alternative == 2 ? "vmovdqu64\t{%2, %x0|%x0, %2}"
++ return which_alternative == 2 ? "vmovdqu64\t{%2, %x0|%x0, %2}"
+ : "vmovdqu\t{%2, %x0|%x0, %2}";
+- else
+- return which_alternative == 2 ? "vmovdqa64\t{%2, %x0|%x0, %2}"
+- : "vmovdqa\t{%2, %x0|%x0, %2}";
+ case E_V16SImode:
+- if (misaligned_operand (operands[2], <ssequartermode>mode))
+- return which_alternative == 2 ? "vmovdqu32\t{%2, %x0|%x0, %2}"
++ return which_alternative == 2 ? "vmovdqu32\t{%2, %x0|%x0, %2}"
+ : "vmovdqu\t{%2, %x0|%x0, %2}";
+- else
+- return which_alternative == 2 ? "vmovdqa32\t{%2, %x0|%x0, %2}"
+- : "vmovdqa\t{%2, %x0|%x0, %2}";
+ default:
+ gcc_unreachable ();
+ }
+@@ -21225,63 +21199,27 @@
+ switch (get_attr_mode (insn))
+ {
+ case MODE_V16SF:
+- if (misaligned_operand (operands[1], <ssehalfvecmode>mode))
+- return "vmovups\t{%1, %t0|%t0, %1}";
+- else
+- return "vmovaps\t{%1, %t0|%t0, %1}";
++ return "vmovups\t{%1, %t0|%t0, %1}";
+ case MODE_V8DF:
+- if (misaligned_operand (operands[1], <ssehalfvecmode>mode))
+- return "vmovupd\t{%1, %t0|%t0, %1}";
+- else
+- return "vmovapd\t{%1, %t0|%t0, %1}";
++ return "vmovupd\t{%1, %t0|%t0, %1}";
+ case MODE_V8SF:
+- if (misaligned_operand (operands[1], <ssehalfvecmode>mode))
+- return "vmovups\t{%1, %x0|%x0, %1}";
+- else
+- return "vmovaps\t{%1, %x0|%x0, %1}";
++ return "vmovups\t{%1, %x0|%x0, %1}";
+ case MODE_V4DF:
+- if (misaligned_operand (operands[1], <ssehalfvecmode>mode))
+- return "vmovupd\t{%1, %x0|%x0, %1}";
+- else
+- return "vmovapd\t{%1, %x0|%x0, %1}";
++ return "vmovupd\t{%1, %x0|%x0, %1}";
+ case MODE_XI:
+- if (misaligned_operand (operands[1], <ssehalfvecmode>mode))
+- {
+- if (which_alternative == 2)
+- return "vmovdqu\t{%1, %t0|%t0, %1}";
+- else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8)
+- return "vmovdqu64\t{%1, %t0|%t0, %1}";
+- else
+- return "vmovdqu32\t{%1, %t0|%t0, %1}";
+- }
++ if (which_alternative == 2)
++ return "vmovdqu\t{%1, %t0|%t0, %1}";
++ else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8)
++ return "vmovdqu64\t{%1, %t0|%t0, %1}";
+ else
+- {
+- if (which_alternative == 2)
+- return "vmovdqa\t{%1, %t0|%t0, %1}";
+- else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8)
+- return "vmovdqa64\t{%1, %t0|%t0, %1}";
+- else
+- return "vmovdqa32\t{%1, %t0|%t0, %1}";
+- }
++ return "vmovdqu32\t{%1, %t0|%t0, %1}";
+ case MODE_OI:
+- if (misaligned_operand (operands[1], <ssehalfvecmode>mode))
+- {
+- if (which_alternative == 2)
+- return "vmovdqu\t{%1, %x0|%x0, %1}";
+- else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8)
+- return "vmovdqu64\t{%1, %x0|%x0, %1}";
+- else
+- return "vmovdqu32\t{%1, %x0|%x0, %1}";
+- }
++ if (which_alternative == 2)
++ return "vmovdqu\t{%1, %x0|%x0, %1}";
++ else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8)
++ return "vmovdqu64\t{%1, %x0|%x0, %1}";
+ else
+- {
+- if (which_alternative == 2)
+- return "vmovdqa\t{%1, %x0|%x0, %1}";
+- else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8)
+- return "vmovdqa64\t{%1, %x0|%x0, %1}";
+- else
+- return "vmovdqa32\t{%1, %x0|%x0, %1}";
+- }
++ return "vmovdqu32\t{%1, %x0|%x0, %1}";
+ default:
+ gcc_unreachable ();
+ }
+--- a/gcc/config/i386/i386.c
++++ b/gcc/config/i386/i386.c
+@@ -4981,13 +4981,13 @@
+ switch (type)
+ {
+ case opcode_int:
+- opcode = misaligned_p ? "vmovdqu32" : "vmovdqa32";
++ opcode = "vmovdqu32";
+ break;
+ case opcode_float:
+- opcode = misaligned_p ? "vmovups" : "vmovaps";
++ opcode = "vmovups";
+ break;
+ case opcode_double:
+- opcode = misaligned_p ? "vmovupd" : "vmovapd";
++ opcode = "vmovupd";
+ break;
+ }
+ }
+@@ -4996,16 +4996,16 @@
+ switch (scalar_mode)
+ {
+ case E_SFmode:
+- opcode = misaligned_p ? "%vmovups" : "%vmovaps";
++ opcode = "%vmovups";
+ break;
+ case E_DFmode:
+- opcode = misaligned_p ? "%vmovupd" : "%vmovapd";
++ opcode = "%vmovupd";
+ break;
+ case E_TFmode:
+ if (evex_reg_p)
+- opcode = misaligned_p ? "vmovdqu64" : "vmovdqa64";
++ opcode = "vmovdqu64";
+ else
+- opcode = misaligned_p ? "%vmovdqu" : "%vmovdqa";
++ opcode = "%vmovdqu";
+ break;
+ default:
+ gcc_unreachable ();
+@@ -5017,48 +5017,32 @@
+ {
+ case E_QImode:
+ if (evex_reg_p)
+- opcode = (misaligned_p
+- ? (TARGET_AVX512BW
+- ? "vmovdqu8"
+- : "vmovdqu64")
+- : "vmovdqa64");
++ opcode = TARGET_AVX512BW ? "vmovdqu8" : "vmovdqu64";
+ else
+- opcode = (misaligned_p
+- ? (TARGET_AVX512BW
+- ? "vmovdqu8"
+- : "%vmovdqu")
+- : "%vmovdqa");
++ opcode = TARGET_AVX512BW ? "vmovdqu8" : "%vmovdqu";
+ break;
+ case E_HImode:
+ if (evex_reg_p)
+- opcode = (misaligned_p
+- ? (TARGET_AVX512BW
+- ? "vmovdqu16"
+- : "vmovdqu64")
+- : "vmovdqa64");
++ opcode = TARGET_AVX512BW ? "vmovdqu16" : "vmovdqu64";
+ else
+- opcode = (misaligned_p
+- ? (TARGET_AVX512BW
+- ? "vmovdqu16"
+- : "%vmovdqu")
+- : "%vmovdqa");
++ opcode = TARGET_AVX512BW ? "vmovdqu16" : "%vmovdqu";
+ break;
+ case E_SImode:
+ if (evex_reg_p)
+- opcode = misaligned_p ? "vmovdqu32" : "vmovdqa32";
++ opcode = "vmovdqu32";
+ else
+- opcode = misaligned_p ? "%vmovdqu" : "%vmovdqa";
++ opcode = "%vmovdqu";
+ break;
+ case E_DImode:
+ case E_TImode:
+ case E_OImode:
+ if (evex_reg_p)
+- opcode = misaligned_p ? "vmovdqu64" : "vmovdqa64";
++ opcode = "vmovdqu64";
+ else
+- opcode = misaligned_p ? "%vmovdqu" : "%vmovdqa";
++ opcode = "%vmovdqu";
+ break;
+ case E_XImode:
+- opcode = misaligned_p ? "vmovdqu64" : "vmovdqa64";
++ opcode = "vmovdqu64";
+ break;
+ default:
+ gcc_unreachable ();
diff --git a/contrib/linearize/linearize-data.py b/contrib/linearize/linearize-data.py
index 441b5da764..7510204bb1 100755
--- a/contrib/linearize/linearize-data.py
+++ b/contrib/linearize/linearize-data.py
@@ -20,49 +20,9 @@ from collections import namedtuple
settings = {}
-def hex_switchEndian(s):
- """ Switches the endianness of a hex string (in pairs of hex chars) """
- pairList = [s[i:i+2].encode() for i in range(0, len(s), 2)]
- return b''.join(pairList[::-1]).decode()
-
-def uint32(x):
- return x & 0xffffffff
-
-def bytereverse(x):
- return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) |
- (((x) >> 8) & 0x0000ff00) | ((x) >> 24) ))
-
-def bufreverse(in_buf):
- out_words = []
- for i in range(0, len(in_buf), 4):
- word = struct.unpack('@I', in_buf[i:i+4])[0]
- out_words.append(struct.pack('@I', bytereverse(word)))
- return b''.join(out_words)
-
-def wordreverse(in_buf):
- out_words = []
- for i in range(0, len(in_buf), 4):
- out_words.append(in_buf[i:i+4])
- out_words.reverse()
- return b''.join(out_words)
-
-def calc_hdr_hash(blk_hdr):
- hash1 = hashlib.sha256()
- hash1.update(blk_hdr)
- hash1_o = hash1.digest()
-
- hash2 = hashlib.sha256()
- hash2.update(hash1_o)
- hash2_o = hash2.digest()
-
- return hash2_o
-
def calc_hash_str(blk_hdr):
- hash = calc_hdr_hash(blk_hdr)
- hash = bufreverse(hash)
- hash = wordreverse(hash)
- hash_str = hash.hex()
- return hash_str
+ blk_hdr_hash = hashlib.sha256(hashlib.sha256(blk_hdr).digest()).digest()
+ return blk_hdr_hash[::-1].hex()
def get_blk_dt(blk_hdr):
members = struct.unpack("<I", blk_hdr[68:68+4])
@@ -78,7 +38,7 @@ def get_block_hashes(settings):
for line in f:
line = line.rstrip()
if settings['rev_hash_bytes'] == 'true':
- line = hex_switchEndian(line)
+ line = bytes.fromhex(line)[::-1].hex()
blkindex.append(line)
print("Read " + str(len(blkindex)) + " hashes")
diff --git a/contrib/linearize/linearize-hashes.py b/contrib/linearize/linearize-hashes.py
index fed6e665b8..0a316eb818 100755
--- a/contrib/linearize/linearize-hashes.py
+++ b/contrib/linearize/linearize-hashes.py
@@ -17,11 +17,6 @@ import os.path
settings = {}
-def hex_switchEndian(s):
- """ Switches the endianness of a hex string (in pairs of hex chars) """
- pairList = [s[i:i+2].encode() for i in range(0, len(s), 2)]
- return b''.join(pairList[::-1]).decode()
-
class BitcoinRPC:
def __init__(self, host, port, username, password):
authpair = "%s:%s" % (username, password)
@@ -85,7 +80,7 @@ def get_block_hashes(settings, max_blocks_per_call=10000):
sys.exit(1)
assert(resp_obj['id'] == x) # assume replies are in-sequence
if settings['rev_hash_bytes'] == 'true':
- resp_obj['result'] = hex_switchEndian(resp_obj['result'])
+ resp_obj['result'] = bytes.fromhex(resp_obj['result'])[::-1].hex()
print(resp_obj['result'])
height += num_blocks
diff --git a/contrib/macdeploy/detached-sig-apply.sh b/contrib/macdeploy/detached-sig-apply.sh
deleted file mode 100755
index c7296387eb..0000000000
--- a/contrib/macdeploy/detached-sig-apply.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2014-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
-set -e
-
-UNSIGNED="$1"
-SIGNATURE="$2"
-ROOTDIR=dist
-OUTDIR=signed-app
-SIGNAPPLE=signapple
-
-if [ -z "$UNSIGNED" ]; then
- echo "usage: $0 <unsigned app> <signature>"
- exit 1
-fi
-
-if [ -z "$SIGNATURE" ]; then
- echo "usage: $0 <unsigned app> <signature>"
- exit 1
-fi
-
-${SIGNAPPLE} apply "${UNSIGNED}" "${SIGNATURE}"
-mv ${ROOTDIR} ${OUTDIR}
-echo "Signed: ${OUTDIR}"
diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus
index 0455a137f1..cc24e0317b 100755
--- a/contrib/macdeploy/macdeployqtplus
+++ b/contrib/macdeploy/macdeployqtplus
@@ -544,6 +544,22 @@ ds.close()
if platform.system() == "Darwin":
subprocess.check_call(f"codesign --deep --force --sign - {target}", shell=True)
+print("+ Installing background.tiff +")
+
+bg_path = os.path.join('dist', '.background', 'background.tiff')
+os.mkdir(os.path.dirname(bg_path))
+
+tiff_path = os.path.join('contrib', 'macdeploy', 'background.tiff')
+shutil.copy2(tiff_path, bg_path)
+
+# ------------------------------------------------
+
+print("+ Generating symlink for /Applications +")
+
+os.symlink("/Applications", os.path.join('dist', "Applications"))
+
+# ------------------------------------------------
+
if config.dmg is not None:
print("+ Preparing .dmg disk image +")
@@ -567,19 +583,6 @@ if config.dmg is not None:
print("Attaching temp image...")
output = run(["hdiutil", "attach", tempname, "-readwrite"], check=True, universal_newlines=True, stdout=PIPE).stdout
- m = re.search(r"/Volumes/(.+$)", output)
- disk_root = m.group(0)
-
- print("+ Applying fancy settings +")
-
- bg_path = os.path.join(disk_root, ".background", os.path.basename('background.tiff'))
- os.mkdir(os.path.dirname(bg_path))
- if verbose:
- print('background.tiff', "->", bg_path)
- shutil.copy2('contrib/macdeploy/background.tiff', bg_path)
-
- os.symlink("/Applications", os.path.join(disk_root, "Applications"))
-
print("+ Finalizing .dmg disk image +")
run(["hdiutil", "detach", f"/Volumes/{appname}"], universal_newlines=True)
diff --git a/contrib/signet/miner b/contrib/signet/miner
index 012bd6cc31..b366b98e2d 100755
--- a/contrib/signet/miner
+++ b/contrib/signet/miner
@@ -8,7 +8,7 @@ import base64
import json
import logging
import math
-import os.path
+import os
import re
import struct
import sys
@@ -493,10 +493,11 @@ def do_generate(args):
logging.debug("Mining block delta=%s start=%s mine=%s", seconds_to_hms(mine_time-bestheader["time"]), mine_time, is_mine)
mined_blocks += 1
psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time)
- psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=psbt.encode('utf8')))
+ input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
+ psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream))
if not psbt_signed.get("complete",False):
logging.debug("Generated PSBT: %s" % (psbt,))
- sys.stderr.write("PSBT signing failed")
+ sys.stderr.write("PSBT signing failed\n")
return 1
block, signet_solution = do_decode_psbt(psbt_signed["psbt"])
block = finish_block(block, signet_solution, args.grind_cmd)
diff --git a/contrib/testgen/README.md b/contrib/testgen/README.md
index fcc5a378e2..66276ec9dd 100644
--- a/contrib/testgen/README.md
+++ b/contrib/testgen/README.md
@@ -4,5 +4,5 @@ Utilities to generate test vectors for the data-driven Bitcoin tests.
Usage:
- PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json
- PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json
+ ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json
+ ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json
diff --git a/contrib/testgen/base58.py b/contrib/testgen/base58.py
deleted file mode 100644
index 87341ccf96..0000000000
--- a/contrib/testgen/base58.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright (c) 2012-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.
-'''
-Bitcoin base58 encoding and decoding.
-
-Based on https://bitcointalk.org/index.php?topic=1026.0 (public domain)
-'''
-import hashlib
-
-# for compatibility with following code...
-class SHA256:
- new = hashlib.sha256
-
-if str != bytes:
- # Python 3.x
- def ord(c):
- return c
- def chr(n):
- return bytes( (n,) )
-
-__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
-__b58base = len(__b58chars)
-b58chars = __b58chars
-
-def b58encode(v):
- """ encode v, which is a string of bytes, to base58.
- """
- long_value = 0
- for (i, c) in enumerate(v[::-1]):
- if isinstance(c, str):
- c = ord(c)
- long_value += (256**i) * c
-
- result = ''
- while long_value >= __b58base:
- div, mod = divmod(long_value, __b58base)
- result = __b58chars[mod] + result
- long_value = div
- result = __b58chars[long_value] + result
-
- # Bitcoin does a little leading-zero-compression:
- # leading 0-bytes in the input become leading-1s
- nPad = 0
- for c in v:
- if c == 0:
- nPad += 1
- else:
- break
-
- return (__b58chars[0]*nPad) + result
-
-def b58decode(v, length = None):
- """ decode v into a string of len bytes
- """
- long_value = 0
- for i, c in enumerate(v[::-1]):
- pos = __b58chars.find(c)
- assert pos != -1
- long_value += pos * (__b58base**i)
-
- result = bytes()
- while long_value >= 256:
- div, mod = divmod(long_value, 256)
- result = chr(mod) + result
- long_value = div
- result = chr(long_value) + result
-
- nPad = 0
- for c in v:
- if c == __b58chars[0]:
- nPad += 1
- continue
- break
-
- result = bytes(nPad) + result
- if length is not None and len(result) != length:
- return None
-
- return result
-
-def checksum(v):
- """Return 32-bit checksum based on SHA256"""
- return SHA256.new(SHA256.new(v).digest()).digest()[0:4]
-
-def b58encode_chk(v):
- """b58encode a string, with 32-bit checksum"""
- return b58encode(v + checksum(v))
-
-def b58decode_chk(v):
- """decode a base58 string, check and remove checksum"""
- result = b58decode(v)
- if result is None:
- return None
- if result[-4:] == checksum(result[:-4]):
- return result[:-4]
- else:
- return None
-
-def get_bcaddress_version(strAddress):
- """ Returns None if strAddress is invalid. Otherwise returns integer version of address. """
- addr = b58decode_chk(strAddress)
- if addr is None or len(addr)!=21:
- return None
- version = addr[0]
- return ord(version)
-
-if __name__ == '__main__':
- # Test case (from http://gitorious.org/bitcoin/python-base58.git)
- assert get_bcaddress_version('15VjRaDX9zpbA8LVnbrCAFzrVzN7ixHNsC') == 0
- _ohai = 'o hai'.encode('ascii')
- _tmp = b58encode(_ohai)
- assert _tmp == 'DYB3oMS'
- assert b58decode(_tmp, 5) == _ohai
- print("Tests passed")
diff --git a/contrib/testgen/gen_key_io_test_vectors.py b/contrib/testgen/gen_key_io_test_vectors.py
index eafaaaeceb..4aa7dc200b 100755
--- a/contrib/testgen/gen_key_io_test_vectors.py
+++ b/contrib/testgen/gen_key_io_test_vectors.py
@@ -1,21 +1,25 @@
#!/usr/bin/env python3
-# Copyright (c) 2012-2021 The Bitcoin Core developers
+# Copyright (c) 2012-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.
'''
Generate valid and invalid base58/bech32(m) address and private key test vectors.
Usage:
- PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json
- PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json
+ ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json
+ ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json
'''
-# 2012 Wladimir J. van der Laan
-# Released under MIT License
-import os
+
from itertools import islice
-from base58 import b58encode_chk, b58decode_chk, b58chars
+import os
import random
-from segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET, Encoding
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../../test/functional'))
+
+from test_framework.address import base58_to_byte, byte_to_base58, b58chars # noqa: E402
+from test_framework.script import OP_0, OP_1, OP_2, OP_3, OP_16, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_CHECKSIG # noqa: E402
+from test_framework.segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET, Encoding # noqa: E402
# key types
PUBKEY_ADDRESS = 0
@@ -29,16 +33,6 @@ PRIVKEY_TEST = 239
PRIVKEY_REGTEST = 239
# script
-OP_0 = 0x00
-OP_1 = 0x51
-OP_2 = 0x52
-OP_3 = 0x53
-OP_16 = 0x60
-OP_DUP = 0x76
-OP_EQUAL = 0x87
-OP_EQUALVERIFY = 0x88
-OP_HASH160 = 0xa9
-OP_CHECKSIG = 0xac
pubkey_prefix = (OP_DUP, OP_HASH160, 20)
pubkey_suffix = (OP_EQUALVERIFY, OP_CHECKSIG)
script_prefix = (OP_HASH160, 20)
@@ -114,8 +108,10 @@ def is_valid(v):
'''Check vector v for validity'''
if len(set(v) - set(b58chars)) > 0:
return is_valid_bech32(v)
- result = b58decode_chk(v)
- if result is None:
+ try:
+ payload, version = base58_to_byte(v)
+ result = bytes([version]) + payload
+ except ValueError: # thrown if checksum doesn't match
return is_valid_bech32(v)
for template in templates:
prefix = bytearray(template[0])
@@ -139,7 +135,8 @@ def gen_valid_base58_vector(template):
suffix = bytearray(template[2])
dst_prefix = bytearray(template[4])
dst_suffix = bytearray(template[5])
- rv = b58encode_chk(prefix + payload + suffix)
+ assert len(prefix) == 1
+ rv = byte_to_base58(payload + suffix, prefix[0])
return rv, dst_prefix + payload + dst_suffix
def gen_valid_bech32_vector(template):
@@ -190,7 +187,8 @@ def gen_invalid_base58_vector(template):
else:
suffix = bytearray(template[2])
- val = b58encode_chk(prefix + payload + suffix)
+ assert len(prefix) == 1
+ val = byte_to_base58(payload + suffix, prefix[0])
if random.randint(0,10)<1: # line corruption
if randbool(): # add random character to end
val += random.choice(b58chars)
@@ -250,7 +248,6 @@ def gen_invalid_vectors():
yield val,
if __name__ == '__main__':
- import sys
import json
iters = {'valid':gen_valid_vectors, 'invalid':gen_invalid_vectors}
try:
diff --git a/contrib/valgrind.supp b/contrib/valgrind.supp
index 99ca305fe7..6efe49254b 100644
--- a/contrib/valgrind.supp
+++ b/contrib/valgrind.supp
@@ -113,16 +113,6 @@
fun:GetCoin
}
{
- Suppress boost warning
- Memcheck:Leak
- fun:_Znwm
- ...
- fun:_ZN5boost9unit_test9framework5state17execute_test_treeEmjPKNS2_23random_generator_helperE
- fun:_ZN5boost9unit_test9framework3runEmb
- fun:_ZN5boost9unit_test14unit_test_mainEPFbvEiPPc
- fun:main
-}
-{
Suppress LogInstance still reachable memory warning
Memcheck:Leak
match-leak-kinds: reachable
diff --git a/depends/hosts/openbsd.mk b/depends/hosts/openbsd.mk
index dc8393e04c..5988f24bff 100644
--- a/depends/hosts/openbsd.mk
+++ b/depends/hosts/openbsd.mk
@@ -1,11 +1,11 @@
openbsd_CFLAGS=-pipe
-openbsd_CFLAGS_CXXFLAGS=$(openbsd_CFLAGS)
+openbsd_CXXFLAGS=$(openbsd_CFLAGS)
-openbsd_CFLAGS_release_CFLAGS=-O2
-openbsd_CFLAGS_release_CXXFLAGS=$(openbsd_release_CFLAGS)
+openbsd_release_CFLAGS=-O2
+openbsd_release_CXXFLAGS=$(openbsd_release_CFLAGS)
-openbsd_CFLAGS_debug_CFLAGS=-O1
-openbsd_CFLAGS_debug_CXXFLAGS=$(openbsd_debug_CFLAGS)
+openbsd_debug_CFLAGS=-O1
+openbsd_debug_CXXFLAGS=$(openbsd_debug_CFLAGS)
ifeq (86,$(findstring 86,$(build_arch)))
i686_openbsd_CC=clang -m32
diff --git a/depends/packages/libmultiprocess.mk b/depends/packages/libmultiprocess.mk
index 40ab3c68ea..864e33bc9a 100644
--- a/depends/packages/libmultiprocess.mk
+++ b/depends/packages/libmultiprocess.mk
@@ -6,7 +6,7 @@ $(package)_sha256_hash=$(native_$(package)_sha256_hash)
$(package)_dependencies=native_$(package) capnp
define $(package)_config_cmds
- $($(package)_cmake)
+ $($(package)_cmake) .
endef
define $(package)_build_cmds
diff --git a/depends/packages/native_libmultiprocess.mk b/depends/packages/native_libmultiprocess.mk
index 14653ce9fb..6e600c5720 100644
--- a/depends/packages/native_libmultiprocess.mk
+++ b/depends/packages/native_libmultiprocess.mk
@@ -6,7 +6,7 @@ $(package)_sha256_hash=9f8b055c8bba755dc32fe799b67c20b91e7b13e67cadafbc54c0f1def
$(package)_dependencies=native_capnp
define $(package)_config_cmds
- $($(package)_cmake)
+ $($(package)_cmake) .
endef
define $(package)_build_cmds
diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk
index 9cdfd21d2c..2bc3a81430 100644
--- a/depends/packages/qt.mk
+++ b/depends/packages/qt.mk
@@ -1,25 +1,32 @@
package=qt
-$(package)_version=5.15.2
+$(package)_version=5.15.3
$(package)_download_path=https://download.qt.io/official_releases/qt/5.15/$($(package)_version)/submodules
-$(package)_suffix=everywhere-src-$($(package)_version).tar.xz
+$(package)_suffix=everywhere-opensource-src-$($(package)_version).tar.xz
$(package)_file_name=qtbase-$($(package)_suffix)
-$(package)_sha256_hash=909fad2591ee367993a75d7e2ea50ad4db332f05e1c38dd7a5a274e156a4e0f8
+$(package)_sha256_hash=26394ec9375d52c1592bd7b689b1619c6b8dbe9b6f91fdd5c355589787f3a0b6
$(package)_linux_dependencies=freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm
$(package)_qt_libs=corelib network widgets gui plugins testlib
$(package)_linguist_tools = lrelease lupdate lconvert
-$(package)_patches = qt.pro qttools_src.pro
-$(package)_patches += fix_qt_pkgconfig.patch mac-qmake.conf fix_no_printer.patch no-xlib.patch
-$(package)_patches += dont_use_avx_android_x86_64.patch dont_hardcode_x86_64.patch fix_montery_include.patch
-$(package)_patches += fix_android_jni_static.patch dont_hardcode_pwd.patch
-$(package)_patches += qtbase-moc-ignore-gcc-macro.patch fix_limits_header.patch
-$(package)_patches += fix_bigsur_style.patch use_android_ndk23.patch
+$(package)_patches = qt.pro
+$(package)_patches += qttools_src.pro
+$(package)_patches += mac-qmake.conf
+$(package)_patches += fix_qt_pkgconfig.patch
+$(package)_patches += no-xlib.patch
+$(package)_patches += dont_hardcode_x86_64.patch
+$(package)_patches += fix_montery_include.patch
+$(package)_patches += fix_android_jni_static.patch
+$(package)_patches += dont_hardcode_pwd.patch
+$(package)_patches += qtbase-moc-ignore-gcc-macro.patch
+$(package)_patches += fix_limits_header.patch
+$(package)_patches += use_android_ndk23.patch
$(package)_patches += rcc_hardcode_timestamp.patch
+$(package)_patches += duplicate_lcqpafonts.patch
$(package)_qttranslations_file_name=qttranslations-$($(package)_suffix)
-$(package)_qttranslations_sha256_hash=d5788e86257b21d5323f1efd94376a213e091d1e5e03b45a95dd052b5f570db8
+$(package)_qttranslations_sha256_hash=5d7869f670a135ad0986e266813b9dd5bbae2b09577338f9cdf8904d4af52db0
$(package)_qttools_file_name=qttools-$($(package)_suffix)
-$(package)_qttools_sha256_hash=c189d0ce1ff7c739db9a3ace52ac3e24cb8fd6dbf234e49f075249b38f43c1cc
+$(package)_qttools_sha256_hash=463b2fe71a085e7ab4e39333ae360ab0ec857b966d7a08f752c427e5df55f90d
$(package)_extra_sources = $($(package)_qttranslations_file_name)
$(package)_extra_sources += $($(package)_qttools_file_name)
@@ -232,17 +239,15 @@ define $(package)_preprocess_cmds
cp $($(package)_patch_dir)/qttools_src.pro qttools/src/src.pro && \
patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \
patch -p1 -i $($(package)_patch_dir)/fix_qt_pkgconfig.patch && \
- patch -p1 -i $($(package)_patch_dir)/fix_no_printer.patch && \
patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \
patch -p1 -i $($(package)_patch_dir)/no-xlib.patch && \
- patch -p1 -i $($(package)_patch_dir)/dont_use_avx_android_x86_64.patch && \
patch -p1 -i $($(package)_patch_dir)/dont_hardcode_x86_64.patch && \
patch -p1 -i $($(package)_patch_dir)/qtbase-moc-ignore-gcc-macro.patch && \
patch -p1 -i $($(package)_patch_dir)/fix_limits_header.patch && \
patch -p1 -i $($(package)_patch_dir)/fix_montery_include.patch && \
- patch -p1 -i $($(package)_patch_dir)/fix_bigsur_style.patch && \
patch -p1 -i $($(package)_patch_dir)/use_android_ndk23.patch && \
patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch && \
+ patch -p1 -i $($(package)_patch_dir)/duplicate_lcqpafonts.patch && \
mkdir -p qtbase/mkspecs/macx-clang-linux &&\
cp -f qtbase/mkspecs/macx-clang/qplatformdefs.h qtbase/mkspecs/macx-clang-linux/ &&\
cp -f $($(package)_patch_dir)/mac-qmake.conf qtbase/mkspecs/macx-clang-linux/qmake.conf && \
diff --git a/depends/patches/qt/dont_use_avx_android_x86_64.patch b/depends/patches/qt/dont_use_avx_android_x86_64.patch
deleted file mode 100644
index b12ac8f4d0..0000000000
--- a/depends/patches/qt/dont_use_avx_android_x86_64.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-Android: don't use avx and avx2 when building for Android x86_64
-
-AVX and AVX2 are not supported on x86_64 ABI.
-See:
- - https://developer.android.com/ndk/guides/abis#86-64
- - https://bugreports.qt.io/browse/QTBUG-86785
-
-Upstream commits:
- - Qt 6.0: ff1a44be33f4bc05d502a2ca49171e0408992f61
- - Qt 5.15: 8b2cc0f6deb038a4c9d4f0d9b690c7726bd809a9
-
-
---- old/qtbase/configure.json
-+++ new/qtbase/configure.json
-@@ -1098,7 +1098,7 @@
- },
- "avx": {
- "label": "AVX",
-- "condition": "features.sse4_2 && tests.avx",
-+ "condition": "features.sse4_2 && tests.avx && (!config.android || !arch.x86_64)",
- "output": [
- "privateConfig",
- { "type": "define", "name": "QT_COMPILER_SUPPORTS_AVX", "value": 1 }
-@@ -1114,7 +1114,7 @@
- },
- "avx2": {
- "label": "AVX2",
-- "condition": "features.avx && tests.avx2",
-+ "condition": "features.avx && tests.avx2 && (!config.android || !arch.x86_64)",
- "output": [
- "privateConfig",
- "privateFeature",
diff --git a/depends/patches/qt/duplicate_lcqpafonts.patch b/depends/patches/qt/duplicate_lcqpafonts.patch
new file mode 100644
index 0000000000..c460b51dcf
--- /dev/null
+++ b/depends/patches/qt/duplicate_lcqpafonts.patch
@@ -0,0 +1,104 @@
+QtGui: Fix duplication of logging category lcQpaFonts
+
+Move it to qplatformfontdatabase.h.
+
+Upstream commit:
+ - Qt 6.0: ab01885e48873fb2ad71841a3f1627fe4d9cd835
+
+--- a/qtbase/src/gui/text/qplatformfontdatabase.cpp
++++ b/qtbase/src/gui/text/qplatformfontdatabase.cpp
+@@ -52,6 +52,8 @@
+
+ QT_BEGIN_NAMESPACE
+
++Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
++
+ void qt_registerFont(const QString &familyname, const QString &stylename,
+ const QString &foundryname, int weight,
+ QFont::Style style, int stretch, bool antialiased,
+
+--- a/qtbase/src/gui/text/qplatformfontdatabase.h
++++ b/qtbase/src/gui/text/qplatformfontdatabase.h
+@@ -50,6 +50,7 @@
+ //
+
+ #include <QtGui/qtguiglobal.h>
++#include <QtCore/qloggingcategory.h>
+ #include <QtCore/QString>
+ #include <QtCore/QStringList>
+ #include <QtCore/QList>
+@@ -62,6 +63,7 @@
+
+ QT_BEGIN_NAMESPACE
+
++Q_DECLARE_LOGGING_CATEGORY(lcQpaFonts)
+
+ class QWritingSystemsPrivate;
+
+
+--- a/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
++++ b/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm
+@@ -86,8 +86,6 @@
+
+ QT_BEGIN_NAMESPACE
+
+-Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
+-
+ static float SYNTHETIC_ITALIC_SKEW = std::tan(14.f * std::acos(0.f) / 90.f);
+
+ bool QCoreTextFontEngine::ct_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length)
+
+--- a/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext_p.h
++++ b/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext_p.h
+@@ -64,8 +64,6 @@
+
+ QT_BEGIN_NAMESPACE
+
+-Q_DECLARE_LOGGING_CATEGORY(lcQpaFonts)
+-
+ class QCoreTextFontEngine : public QFontEngine
+ {
+ Q_GADGET
+
+--- a/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp
++++ b/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp
+@@ -68,8 +68,6 @@
+
+ QT_BEGIN_NAMESPACE
+
+-Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
+-
+ #ifndef QT_NO_DIRECTWRITE
+ // ### fixme: Consider direct linking of dwrite.dll once Windows Vista pre SP2 is dropped (QTBUG-49711)
+
+
+--- a/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase_p.h
++++ b/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase_p.h
+@@ -63,8 +63,6 @@
+
+ QT_BEGIN_NAMESPACE
+
+-Q_DECLARE_LOGGING_CATEGORY(lcQpaFonts)
+-
+ class QWindowsFontEngineData
+ {
+ Q_DISABLE_COPY_MOVE(QWindowsFontEngineData)
+
+--- a/qtbase/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp
++++ b/qtbase/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp
+@@ -40,6 +40,7 @@
+ #include "qgenericunixthemes_p.h"
+
+ #include "qpa/qplatformtheme_p.h"
++#include "qpa/qplatformfontdatabase.h"
+
+ #include <QtGui/QPalette>
+ #include <QtGui/QFont>
+@@ -76,7 +77,6 @@
+ QT_BEGIN_NAMESPACE
+
+ Q_DECLARE_LOGGING_CATEGORY(qLcTray)
+-Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
+
+ ResourceHelper::ResourceHelper()
+ {
diff --git a/depends/patches/qt/fix_android_jni_static.patch b/depends/patches/qt/fix_android_jni_static.patch
index bb64661761..22a4d5ab0e 100644
--- a/depends/patches/qt/fix_android_jni_static.patch
+++ b/depends/patches/qt/fix_android_jni_static.patch
@@ -1,6 +1,6 @@
--- old/qtbase/src/plugins/platforms/android/androidjnimain.cpp
+++ new/qtbase/src/plugins/platforms/android/androidjnimain.cpp
-@@ -914,6 +914,14 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
+@@ -934,6 +934,14 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
return -1;
}
diff --git a/depends/patches/qt/fix_bigsur_style.patch b/depends/patches/qt/fix_bigsur_style.patch
deleted file mode 100644
index 0f6c848bb0..0000000000
--- a/depends/patches/qt/fix_bigsur_style.patch
+++ /dev/null
@@ -1,90 +0,0 @@
-Fix rendering in macOS BigSur
-
-See: https://bugreports.qt.io/browse/QTBUG-86513.
-
-Upstream commits (combined in this patch):
- - Qt 6.0: 40fb97e97f550b8afd13ecc3a038d9d0c2d82bbb
- - Qt 6.0: 3857f104cac127f62e64e55a20613f0ac2e6b843
- - Qt 6.1: abee4cdd5925a8513f51784754fca8fa35031732
-
---- old/qtbase/src/plugins/styles/mac/qmacstyle_mac.mm
-+++ new/qtbase/src/plugins/styles/mac/qmacstyle_mac.mm
-@@ -3870,6 +3870,7 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
- const auto cs = d->effectiveAquaSizeConstrain(opt, w);
- // Extra hacks to get the proper pressed appreance when not selected or selected and inactive
- const bool needsInactiveHack = (!isActive && isSelected);
-+ const bool isBigSurOrAbove = QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSBigSur;
- const auto ct = !needsInactiveHack && (isSelected || tp == QStyleOptionTab::OnlyOneTab) ?
- QMacStylePrivate::Button_PushButton :
- QMacStylePrivate::Button_PopupButton;
-@@ -3878,6 +3879,12 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
- auto *pb = static_cast<NSButton *>(d->cocoaControl(cw));
-
- auto vOffset = isPopupButton ? 1 : 2;
-+ if (isBigSurOrAbove) {
-+ // Make it 1, otherwise, offset is very visible compared
-+ // to selected tab (which is not a popup button).
-+ vOffset = 1;
-+ }
-+
- if (tabDirection == QMacStylePrivate::East)
- vOffset -= 1;
- const auto outerAdjust = isPopupButton ? 1 : 4;
-@@ -3894,9 +3901,22 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
- frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0);
- else
- frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0);
-+
-+ if (isSelected && isBigSurOrAbove) {
-+ // 1 pixed of 'roundness' is still visible on the right
-+ // (the left is OK, it's rounded).
-+ frameRect = frameRect.adjusted(0, 0, 1, 0);
-+ }
-+
- break;
- case QStyleOptionTab::Middle:
- frameRect = frameRect.adjusted(-innerAdjust, 0, innerAdjust, 0);
-+
-+ if (isSelected && isBigSurOrAbove) {
-+ // 1 pixel of 'roundness' is still visible on both
-+ // sides - left and right.
-+ frameRect = frameRect.adjusted(-1, 0, 1, 0);
-+ }
- break;
- case QStyleOptionTab::End:
- // Pressed state hack: tweak adjustments in preparation for flip below
-@@ -3904,6 +3924,11 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
- frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0);
- else
- frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0);
-+
-+ if (isSelected && isBigSurOrAbove) {
-+ // 1 pixel of 'roundness' is still visible on the left.
-+ frameRect = frameRect.adjusted(-1, 0, 0, 0);
-+ }
- break;
- case QStyleOptionTab::OnlyOneTab:
- frameRect = frameRect.adjusted(-outerAdjust, 0, outerAdjust, 0);
-@@ -3951,7 +3976,10 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
- NSPopUpArrowPosition oldPosition = NSPopUpArrowAtCenter;
- NSPopUpButtonCell *pbCell = nil;
- auto rAdjusted = r;
-- if (isPopupButton && tp == QStyleOptionTab::OnlyOneTab) {
-+ if (isPopupButton && (tp == QStyleOptionTab::OnlyOneTab || isBigSurOrAbove)) {
-+ // Note: starting from macOS BigSur NSPopupButton has this
-+ // arrow 'button' in a different place and it became
-+ // quite visible 'in between' inactive tabs.
- pbCell = static_cast<NSPopUpButtonCell *>(pb.cell);
- oldPosition = pbCell.arrowPosition;
- pbCell.arrowPosition = NSPopUpNoArrow;
-@@ -3959,6 +3987,10 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
- // NSPopUpButton in this state is smaller.
- rAdjusted.origin.x -= 3;
- rAdjusted.size.width += 6;
-+ if (isBigSurOrAbove) {
-+ if (tp == QStyleOptionTab::End)
-+ rAdjusted.origin.x -= 2;
-+ }
- }
- }
-
diff --git a/depends/patches/qt/fix_limits_header.patch b/depends/patches/qt/fix_limits_header.patch
index cb5a8cd1b5..258128c0ca 100644
--- a/depends/patches/qt/fix_limits_header.patch
+++ b/depends/patches/qt/fix_limits_header.patch
@@ -1,46 +1,9 @@
Fix compiling with GCC 11
-See: https://bugreports.qt.io/browse/QTBUG-90395.
+Upstream:
+ - bug report: https://bugreports.qt.io/browse/QTBUG-89977
+ - fix in Qt 6.1: 813a928c7c3cf98670b6043149880ed5c955efb9
-Upstream commits:
- - Qt 5.15 -- unavailable as open source
- - Qt 6.0: b2af6332ea37e45ab230a7a5d2d278f86d961b83
- - Qt 6.1: 9c56d4da2ff631a8c1c30475bd792f6c86bda53c
-
---- old/qtbase/src/corelib/global/qendian.h
-+++ new/qtbase/src/corelib/global/qendian.h
-@@ -44,6 +44,8 @@
- #include <QtCore/qfloat16.h>
- #include <QtCore/qglobal.h>
-
-+#include <limits>
-+
- // include stdlib.h and hope that it defines __GLIBC__ for glibc-based systems
- #include <stdlib.h>
- #include <string.h>
-
---- old/qtbase/src/corelib/global/qfloat16.h
-+++ new/qtbase/src/corelib/global/qfloat16.h
-@@ -43,6 +43,7 @@
-
- #include <QtCore/qglobal.h>
- #include <QtCore/qmetatype.h>
-+#include <limits>
- #include <string.h>
-
- #if defined(QT_COMPILER_SUPPORTS_F16C) && defined(__AVX2__) && !defined(__F16C__)
-
---- old/qtbase/src/tools/moc/generator.cpp
-+++ new/qtbase/src/tools/moc/generator.cpp
-@@ -40,6 +40,8 @@
- #include <QtCore/qplugin.h>
- #include <QtCore/qstringview.h>
-
-+#include <limits>
-+
- #include <math.h>
- #include <stdio.h>
-
--- old/qtbase/src/corelib/text/qbytearraymatcher.h
+++ new/qtbase/src/corelib/text/qbytearraymatcher.h
@@ -42,6 +42,8 @@
@@ -52,6 +15,12 @@ Upstream commits:
QT_BEGIN_NAMESPACE
+
+Upstream fix and backports:
+ - Qt 6.1: 3eab20ad382569cb2c9e6ccec2322c3d08c0f716
+ - Qt 6.2: 380294a5971da85010a708dc23b0edec192cbf27
+ - Qt 6.3: 2b2b3155d9f6ba1e4f859741468fbc47db09292b
+
--- old/qtbase/src/corelib/tools/qoffsetstringarray_p.h
+++ new/qtbase/src/corelib/tools/qoffsetstringarray_p.h
@@ -55,6 +55,7 @@
diff --git a/depends/patches/qt/fix_no_printer.patch b/depends/patches/qt/fix_no_printer.patch
deleted file mode 100644
index 1372356138..0000000000
--- a/depends/patches/qt/fix_no_printer.patch
+++ /dev/null
@@ -1,19 +0,0 @@
---- x/qtbase/src/plugins/platforms/cocoa/qprintengine_mac_p.h
-+++ y/qtbase/src/plugins/platforms/cocoa/qprintengine_mac_p.h
-@@ -52,6 +52,7 @@
- //
-
- #include <QtCore/qglobal.h>
-+#include <qpa/qplatformprintdevice.h>
-
- #ifndef QT_NO_PRINTER
-
---- x/qtbase/src/plugins/plugins.pro
-+++ y/qtbase/src/plugins/plugins.pro
-@@ -9,6 +9,3 @@ qtHaveModule(gui) {
- !android:qtConfig(library): SUBDIRS *= generic
- }
- qtHaveModule(widgets): SUBDIRS += styles
--
--!winrt:qtHaveModule(printsupport): \
-- SUBDIRS += printsupport
diff --git a/depends/patches/qt/mac-qmake.conf b/depends/patches/qt/mac-qmake.conf
index e4bfaa1463..543407f853 100644
--- a/depends/patches/qt/mac-qmake.conf
+++ b/depends/patches/qt/mac-qmake.conf
@@ -19,6 +19,4 @@ QMAKE_MAC_SDK.macosx.PlatformPath = /phony
!host_build: QMAKE_LFLAGS += -target $${MAC_TARGET}
QMAKE_AR = $${CROSS_COMPILE}ar cq
QMAKE_RANLIB=$${CROSS_COMPILE}ranlib
-QMAKE_LIBTOOL=$${CROSS_COMPILE}libtool
-QMAKE_INSTALL_NAME_TOOL=$${CROSS_COMPILE}install_name_tool
load(qt_config)
diff --git a/depends/patches/qt/use_android_ndk23.patch b/depends/patches/qt/use_android_ndk23.patch
index 85b8690218..f22367d527 100644
--- a/depends/patches/qt/use_android_ndk23.patch
+++ b/depends/patches/qt/use_android_ndk23.patch
@@ -2,7 +2,7 @@ Use Android NDK r23 LTS
--- old/qtbase/mkspecs/features/android/default_pre.prf
+++ new/qtbase/mkspecs/features/android/default_pre.prf
-@@ -73,7 +73,7 @@ else: equals(QT_ARCH, x86_64): CROSS_COMPILE = $$NDK_LLVM_PATH/bin/x86_64-linux-
+@@ -76,7 +76,7 @@ else: equals(QT_ARCH, x86_64): CROSS_COMPILE = $$NDK_LLVM_PATH/bin/x86_64-linux-
else: equals(QT_ARCH, arm64-v8a): CROSS_COMPILE = $$NDK_LLVM_PATH/bin/aarch64-linux-android-
else: CROSS_COMPILE = $$NDK_LLVM_PATH/bin/arm-linux-androideabi-
diff --git a/doc/README.md b/doc/README.md
index c200ac3753..33f71f4807 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -73,6 +73,7 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th
- [Assets Attribution](assets-attribution.md)
- [Assumeutxo design](assumeutxo.md)
- [bitcoin.conf Configuration File](bitcoin-conf.md)
+- [CJDNS Support](cjdns.md)
- [Files](files.md)
- [Fuzz-testing](fuzzing.md)
- [I2P Support](i2p.md)
diff --git a/doc/REST-interface.md b/doc/REST-interface.md
index 1f0a07a284..c359725faf 100644
--- a/doc/REST-interface.md
+++ b/doc/REST-interface.md
@@ -47,18 +47,24 @@ The HTTP request and response are both handled entirely in-memory.
With the /notxdetails/ option JSON response will only contain the transaction hash instead of the complete transaction details. The option only affects the JSON response.
#### Blockheaders
-`GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>`
+`GET /rest/headers/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>`
Given a block hash: returns <COUNT> amount of blockheaders in upward direction.
Returns empty if the block doesn't exist or it isn't in the active chain.
+*Deprecated (but not removed) since 24.0:*
+`GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>`
+
#### Blockfilter Headers
-`GET /rest/blockfilterheaders/<FILTERTYPE>/<COUNT>/<BLOCK-HASH>.<bin|hex|json>`
+`GET /rest/blockfilterheaders/<FILTERTYPE>/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>`
Given a block hash: returns <COUNT> amount of blockfilter headers in upward
direction for the filter type <FILTERTYPE>.
Returns empty if the block doesn't exist or it isn't in the active chain.
+*Deprecated (but not removed) since 24.0:*
+`GET /rest/blockfilterheaders/<FILTERTYPE>/<COUNT>/<BLOCK-HASH>.<bin|hex|json>`
+
#### Blockfilters
`GET /rest/blockfilter/<FILTERTYPE>/<BLOCK-HASH>.<bin|hex|json>`
diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md
index da2ab61c2a..a8e643a2ab 100644
--- a/doc/build-freebsd.md
+++ b/doc/build-freebsd.md
@@ -1,48 +1,21 @@
# FreeBSD Build Guide
-**Updated for FreeBSD [12.2](https://www.freebsd.org/releases/12.2R/announce.html)**
+**Updated for FreeBSD [12.3](https://www.freebsd.org/releases/12.3R/announce/)**
This guide describes how to build bitcoind, command-line utilities, and GUI on FreeBSD.
-## Dependencies
-
-The following dependencies are **required**:
-
- Library | Purpose | Description
- ----------------------------------------------------------------------|------------|----------------------
- [autoconf](https://svnweb.freebsd.org/ports/head/devel/autoconf/) | Build | Automatically configure software source code
- [automake](https://svnweb.freebsd.org/ports/head/devel/automake/) | Build | Generate makefile (requires autoconf)
- [libtool](https://svnweb.freebsd.org/ports/head/devel/libtool/) | Build | Shared library support
- [pkgconf](https://svnweb.freebsd.org/ports/head/devel/pkgconf/) | Build | Configure compiler and linker flags
- [git](https://svnweb.freebsd.org/ports/head/devel/git/) | Clone | Version control system
- [gmake](https://svnweb.freebsd.org/ports/head/devel/gmake/) | Compile | Generate executables
- [boost-libs](https://svnweb.freebsd.org/ports/head/devel/boost-libs/) | Utility | Library for threading, data structures, etc
- [libevent](https://svnweb.freebsd.org/ports/head/devel/libevent/) | Networking | OS independent asynchronous networking
-
-
-The following dependencies are **optional**:
-
- Library | Purpose | Description
- ---------------------------------------------------------------------------|------------------|----------------------
- [db5](https://svnweb.freebsd.org/ports/head/databases/db5/) | Berkeley DB | Wallet storage (only needed when wallet enabled)
- [qt5](https://svnweb.freebsd.org/ports/head/devel/qt5/) | GUI | GUI toolkit (only needed when GUI enabled)
- [libqrencode](https://svnweb.freebsd.org/ports/head/graphics/libqrencode/) | QR codes in GUI | Generating QR codes (only needed when GUI enabled)
- [libzmq4](https://svnweb.freebsd.org/ports/head/net/libzmq4/) | ZMQ notification | Allows generating ZMQ notifications (requires ZMQ version >= 4.0.0)
- [sqlite3](https://svnweb.freebsd.org/ports/head/databases/sqlite3/) | SQLite DB | Wallet storage (only needed when wallet enabled)
- [python3](https://svnweb.freebsd.org/ports/head/lang/python3/) | Testing | Python Interpreter (only needed when running the test suite)
-
- See [dependencies.md](dependencies.md) for a complete overview.
-
## Preparation
### 1. Install Required Dependencies
-Install the required dependencies the usual way you [install software on FreeBSD](https://www.freebsd.org/doc/en/books/handbook/ports.html) - either with `pkg` or via the Ports collection. The example commands below use `pkg` which is usually run as `root` or via `sudo`. If you want to use `sudo`, and you haven't set it up: [use this guide](http://www.freebsdwiki.net/index.php/Sudo%2C_configuring) to setup `sudo` access on FreeBSD.
+Run the following as root to install the base dependencies for building.
```bash
pkg install autoconf automake boost-libs git gmake libevent libtool pkgconf
```
+See [dependencies.md](dependencies.md) for a complete overview.
+
### 2. Clone Bitcoin Repo
Now that `git` and all the required dependencies are installed, let's clone the Bitcoin Core repository to a directory. All build scripts and commands will run from this directory.
``` bash
@@ -52,21 +25,23 @@ git clone https://github.com/bitcoin/bitcoin.git
### 3. Install Optional Dependencies
#### Wallet Dependencies
-It is not necessary to build wallet functionality to run bitcoind or the GUI. To enable legacy wallets, you must install `db5`. To enable [descriptor wallets](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md), `sqlite3` is required. Skip `db5` if you intend to *exclusively* use descriptor wallets
-
-###### Legacy Wallet Support
-`db5` is required to enable support for legacy wallets. Skip if you don't intend to use legacy wallets
-
-```bash
-pkg install db5
-```
+It is not necessary to build wallet functionality to run either `bitcoind` or `bitcoin-qt`.
###### Descriptor Wallet Support
-`sqlite3` is required to enable support for descriptor wallets. Skip if you don't intend to use descriptor wallets.
+`sqlite3` is required to support [descriptor wallets](descriptors.md).
+Skip if you don't intend to use descriptor wallets.
``` bash
pkg install sqlite3
```
+
+###### Legacy Wallet Support
+`db5` is only required to support legacy wallets.
+Skip if you don't intend to use legacy wallets.
+
+```bash
+pkg install db5
+```
---
#### GUI Dependencies
@@ -84,6 +59,14 @@ pkg install libqrencode
```
---
+#### Notifications
+###### ZeroMQ
+
+Bitcoin Core can provide notifications via ZeroMQ. If the package is installed, support will be compiled in.
+```bash
+pkg install libzmq4
+```
+
#### Test Suite Dependencies
There is an included test suite that is useful for testing code changes when developing.
To run the test suite (recommended), you will need to have Python 3 installed:
@@ -98,8 +81,17 @@ pkg install python3
### 1. Configuration
There are many ways to configure Bitcoin Core, here are a few common examples:
-##### Wallet (BDB + SQlite) Support, No GUI:
-This explicitly enables legacy wallet support and disables the GUI. If `sqlite3` is installed, then descriptor wallet support will be built.
+
+##### Descriptor Wallet and GUI:
+This explicitly enables the GUI and disables legacy wallet support, assuming `sqlite` and `qt` are installed.
+```bash
+./autogen.sh
+./configure --without-bdb --with-gui=yes MAKE=gmake
+```
+
+##### Descriptor & Legacy Wallet. No GUI:
+This enables support for both wallet types and disables the GUI, assuming
+`sqlite3` and `db5` are both installed.
```bash
./autogen.sh
./configure --with-gui=no --with-incompatible-bdb \
@@ -108,12 +100,6 @@ This explicitly enables legacy wallet support and disables the GUI. If `sqlite3`
MAKE=gmake
```
-##### Wallet (only SQlite) and GUI Support:
-This explicitly enables the GUI and disables legacy wallet support. If `qt5` is not installed, this will throw an error. If `sqlite3` is installed then descriptor wallet functionality will be built. If `sqlite3` is not installed, then wallet functionality will be disabled.
-```bash
-./autogen.sh
-./configure --without-bdb --with-gui=yes MAKE=gmake
-```
##### No Wallet or GUI
``` bash
./autogen.sh
diff --git a/doc/build-netbsd.md b/doc/build-netbsd.md
index 762406bf6b..ba9a80f83b 100644
--- a/doc/build-netbsd.md
+++ b/doc/build-netbsd.md
@@ -27,15 +27,33 @@ git clone https://github.com/bitcoin/bitcoin.git
See [dependencies.md](dependencies.md) for a complete overview.
-### Building BerkeleyDB
+### Building Bitcoin Core
+
+**Important**: Use `gmake` (the non-GNU `make` will exit with an error).
+
+#### With descriptor wallet:
+
+The descriptor wallet uses `sqlite3`. You can install it using:
+```bash
+pkgin install sqlite3
+```
+
+```bash
+./autogen.sh
+./configure --with-gui=no --without-bdb \
+ CPPFLAGS="-I/usr/pkg/include" \
+ LDFLAGS="-L/usr/pkg/lib" \
+ BOOST_CPPFLAGS="-I/usr/pkg/include" \
+ MAKE=gmake
+```
-BerkeleyDB is only necessary for the wallet functionality. To skip this, pass
-`--disable-wallet` to `./configure` and skip to the next section.
+#### With legacy wallet:
+
+BerkeleyDB is use for legacy wallet functionality.
It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library
-from ports, for the same reason as boost above (g++/libstd++ incompatibility).
-If you have to build it yourself, you can use [the installation script included
-in contrib/](/contrib/install_db4.sh) like so:
+from ports.
+You can use [the installation script included in contrib/](/contrib/install_db4.sh) like so:
```bash
./contrib/install_db4.sh `pwd`
@@ -47,11 +65,6 @@ from the root of the repository. Then set `BDB_PREFIX` for the next section:
export BDB_PREFIX="$PWD/db4"
```
-### Building Bitcoin Core
-
-**Important**: Use `gmake` (the non-GNU `make` will exit with an error).
-
-With wallet:
```bash
./autogen.sh
./configure --with-gui=no CPPFLAGS="-I/usr/pkg/include" \
@@ -62,7 +75,7 @@ With wallet:
MAKE=gmake
```
-Without wallet:
+#### Without wallet:
```bash
./autogen.sh
./configure --with-gui=no --disable-wallet \
diff --git a/doc/build-openbsd.md b/doc/build-openbsd.md
index 275b7ce124..99daf73c86 100644
--- a/doc/build-openbsd.md
+++ b/doc/build-openbsd.md
@@ -1,123 +1,133 @@
-OpenBSD build guide
-======================
-(updated for OpenBSD 6.9)
+# OpenBSD Build Guide
-This guide describes how to build bitcoind, bitcoin-qt, and command-line utilities on OpenBSD.
+**Updated for OpenBSD [7.0](https://www.openbsd.org/70.html)**
-Preparation
--------------
+This guide describes how to build bitcoind, command-line utilities, and GUI on OpenBSD.
-Run the following as root to install the base dependencies for building:
+## Preparation
+
+### 1. Install Required Dependencies
+Run the following as root to install the base dependencies for building.
```bash
-pkg_add git gmake libevent libtool boost
-pkg_add qt5 # (optional for enabling the GUI)
-pkg_add autoconf # (select highest version, e.g. 2.69)
-pkg_add automake # (select highest version, e.g. 1.16)
-pkg_add python # (select highest version, e.g. 3.8)
-pkg_add bash
+pkg_add bash git gmake libevent libtool boost
+# Select the newest version of the following packages:
+pkg_add autoconf automake python
+```
+See [dependencies.md](dependencies.md) for a complete overview.
+
+### 2. Clone Bitcoin Repo
+Clone the Bitcoin Core repository to a directory. All build scripts and commands will run from this directory.
+``` bash
git clone https://github.com/bitcoin/bitcoin.git
```
-See [dependencies.md](dependencies.md) for a complete overview.
+### 3. Install Optional Dependencies
-**Important**: From OpenBSD 6.2 onwards a C++11-supporting clang compiler is
-part of the base image, and while building it is necessary to make sure that
-this compiler is used and not ancient g++ 4.2.1. This is done by appending
-`CC=cc CXX=c++` to configuration commands. Mixing different compilers within
-the same executable will result in errors.
+#### Wallet Dependencies
-### Building BerkeleyDB
+It is not necessary to build wallet functionality to run either `bitcoind` or `bitcoin-qt`.
+
+###### Descriptor Wallet Support
+
+`sqlite3` is required to support [descriptor wallets](descriptors.md).
+
+``` bash
+pkg_add install sqlite3
+```
-BerkeleyDB is only necessary for the wallet functionality. To skip this, pass
-`--disable-wallet` to `./configure` and skip to the next section.
+###### Legacy Wallet Support
+BerkeleyDB is only required to support legacy wallets.
It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library
-from ports, for the same reason as boost above (g++/libstd++ incompatibility).
-If you have to build it yourself, you can use [the installation script included
-in contrib/](/contrib/install_db4.sh) like so:
+from ports. However you can build it yourself, [using the installation script included in contrib/](/contrib/install_db4.sh), like so, from the root of the repository.
```bash
-./contrib/install_db4.sh `pwd` CC=cc CXX=c++
+./contrib/install_db4.sh `pwd`
```
-from the root of the repository. Then set `BDB_PREFIX` for the next section:
+Then set `BDB_PREFIX`:
```bash
export BDB_PREFIX="$PWD/db4"
```
-### Building Bitcoin Core
+#### GUI Dependencies
+###### Qt5
+
+Bitcoin Core includes a GUI built with the cross-platform Qt Framework. To compile the GUI, Qt 5 is required.
+
+```bash
+pkg_add qt5
+```
+
+## Building Bitcoin Core
**Important**: Use `gmake` (the non-GNU `make` will exit with an error).
Preparation:
```bash
-# Replace this with the autoconf version that you installed. Include only
-# the major and minor parts of the version: use "2.69" for "autoconf-2.69p2".
-export AUTOCONF_VERSION=2.69
-
-# Replace this with the automake version that you installed. Include only
-# the major and minor parts of the version: use "1.16" for "automake-1.16.1".
+# Adapt the following for the version you installed (major.minor only):
+export AUTOCONF_VERSION=2.71
export AUTOMAKE_VERSION=1.16
./autogen.sh
```
-Make sure `BDB_PREFIX` is set to the appropriate path from the above steps.
+
+### 1. Configuration
Note that building with external signer support currently fails on OpenBSD,
hence you have to explicitly disable it by passing the parameter
-`--disable-external-signer` to the configure script.
-(Background: the feature requires the header-only library boost::process, which
-is available on OpenBSD 6.9 via Boost 1.72.0, but contains certain system calls
-and preprocessor defines like `waitid()` and `WEXITED` that are not available.)
+`--disable-external-signer` to the configure script. The feature requires the
+header-only library boost::process, which is available on OpenBSD, but contains
+certain system calls and preprocessor defines like `waitid()` and `WEXITED` that
+are not available.
-To configure with wallet:
-```bash
-./configure --with-gui=no --disable-external-signer CC=cc CXX=c++ \
- BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \
- BDB_CFLAGS="-I${BDB_PREFIX}/include" \
- MAKE=gmake
-```
+There are many ways to configure Bitcoin Core, here are a few common examples:
+
+##### Descriptor Wallet and GUI:
+This enables the GUI and descriptor wallet support, assuming `sqlite` and `qt5` are installed.
-To configure without wallet:
```bash
-./configure --disable-wallet --with-gui=no --disable-external-signer CC=cc CXX=c++ MAKE=gmake
+./configure --disable-external-signer MAKE=gmake
```
-To configure with GUI:
+##### Descriptor & Legacy Wallet. No GUI:
+This enables support for both wallet types and disables the GUI:
+
```bash
-./configure --with-gui=yes --disable-external-signer CC=cc CXX=c++ \
+./configure --disable-external-signer --with-gui=no \
BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \
BDB_CFLAGS="-I${BDB_PREFIX}/include" \
MAKE=gmake
```
-Build and run the tests:
+### 2. Compile
+**Important**: Use `gmake` (the non-GNU `make` will exit with an error).
+
```bash
-gmake # use "-j N" here for N parallel jobs
-gmake check
+gmake # use "-j N" for N parallel jobs
+gmake check # Run tests if Python 3 is available
```
-Resource limits
--------------------
+## Resource limits
If the build runs into out-of-memory errors, the instructions in this section
might help.
The standard ulimit restrictions in OpenBSD are very strict:
-
- data(kbytes) 1572864
+```bash
+data(kbytes) 1572864
+```
This is, unfortunately, in some cases not enough to compile some `.cpp` files in the project,
(see issue [#6658](https://github.com/bitcoin/bitcoin/issues/6658)).
If your user is in the `staff` group the limit can be raised with:
-
- ulimit -d 3000000
-
+```bash
+ulimit -d 3000000
+```
The change will only affect the current shell and processes spawned by it. To
make the change system-wide, change `datasize-cur` and `datasize-max` in
`/etc/login.conf`, and reboot.
-
diff --git a/doc/build-osx.md b/doc/build-osx.md
index 16dc224aed..fdf0a9d414 100644
--- a/doc/build-osx.md
+++ b/doc/build-osx.md
@@ -4,42 +4,6 @@
This guide describes how to build bitcoind, command-line utilities, and GUI on macOS
-**Note:** The following is for Intel Macs only!
-
-## Dependencies
-
-The following dependencies are **required**:
-
-Library | Purpose | Description
------------------------------------------------------------|------------|----------------------
-[automake](https://formulae.brew.sh/formula/automake) | Build | Generate makefile
-[libtool](https://formulae.brew.sh/formula/libtool) | Build | Shared library support
-[pkg-config](https://formulae.brew.sh/formula/pkg-config) | Build | Configure compiler and linker flags
-[boost](https://formulae.brew.sh/formula/boost) | Utility | Library for threading, data structures, etc
-[libevent](https://formulae.brew.sh/formula/libevent) | Networking | OS independent asynchronous networking
-
-The following dependencies are **optional**:
-
-Library | Purpose | Description
---------------------------------------------------------------- |------------------|----------------------
-[berkeley-db@4](https://formulae.brew.sh/formula/berkeley-db@4) | Berkeley DB | Wallet storage (only needed when wallet enabled)
-[qt@5](https://formulae.brew.sh/formula/qt@5) | GUI | GUI toolkit (only needed when GUI enabled)
-[qrencode](https://formulae.brew.sh/formula/qrencode) | QR codes in GUI | Generating QR codes (only needed when GUI enabled)
-[zeromq](https://formulae.brew.sh/formula/zeromq) | ZMQ notification | Allows generating ZMQ notifications (requires ZMQ version >= 4.0.0)
-[sqlite](https://formulae.brew.sh/formula/sqlite) | SQLite DB | Wallet storage (only needed when wallet enabled)
-[miniupnpc](https://formulae.brew.sh/formula/miniupnpc) | UPnP Support | Firewall-jumping support (needed for port mapping support)
-[libnatpmp](https://formulae.brew.sh/formula/libnatpmp) | NAT-PMP Support | Firewall-jumping support (needed for port mapping support)
-[python3](https://formulae.brew.sh/formula/python@3.9) | Testing | Python Interpreter (only needed when running the test suite)
-
-The following dependencies are **optional** packages required for deploying:
-
-Library | Purpose | Description
-----------------------------------------------------|------------------|----------------------
-[ds_store](https://pypi.org/project/ds-store/) | Deploy Dependency| Examine and modify .DS_Store files
-[mac_alias](https://pypi.org/project/mac-alias/) | Deploy Dependency| Generate/Read binary alias and bookmark records
-
-See [dependencies.md](dependencies.md) for a complete overview.
-
## Preparation
The commands in this guide should be executed in a Terminal application.
@@ -78,6 +42,9 @@ Note: If you run into issues while installing Homebrew or pulling packages, refe
The first step is to download the required dependencies.
These dependencies represent the packages required to get a barebones installation up and running.
+
+See [dependencies.md](dependencies.md) for a complete overview.
+
To install, run the following from your terminal:
``` bash
@@ -99,29 +66,21 @@ git clone https://github.com/bitcoin/bitcoin.git
#### Wallet Dependencies
It is not necessary to build wallet functionality to run `bitcoind` or `bitcoin-qt`.
-To enable legacy wallets, you must install `berkeley-db@4`.
-To enable [descriptor wallets](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md), `sqlite` is required.
-Skip `berkeley-db@4` if you intend to *exclusively* use descriptor wallets.
-
-###### Legacy Wallet Support
-`berkeley-db@4` is required to enable support for legacy wallets.
-Skip if you don't intend to use legacy wallets.
+###### Descriptor Wallet Support
-``` bash
-brew install berkeley-db@4
-```
+`sqlite` is required to support for descriptor wallets.
-###### Descriptor Wallet Support
+macOS ships with a useable `sqlite` package, meaning you don't need to
+install anything.
-Note: Apple has included a useable `sqlite` package since macOS 10.14.
-You may not need to install this package.
+###### Legacy Wallet Support
-`sqlite` is required to enable support for descriptor wallets.
-Skip if you don't intend to use descriptor wallets.
+`berkeley-db@4` is only required to support for legacy wallets.
+Skip if you don't intend to use legacy wallets.
``` bash
-brew install sqlite
+brew install berkeley-db@4
```
---
diff --git a/doc/build-unix.md b/doc/build-unix.md
index 15fe63d047..f02729ee32 100644
--- a/doc/build-unix.md
+++ b/doc/build-unix.md
@@ -26,30 +26,7 @@ make install # optional
This will build bitcoin-qt as well, if the dependencies are met.
-Dependencies
----------------------
-
-These dependencies are required:
-
- Library | Purpose | Description
- ------------|------------------|----------------------
- libboost | Utility | Library for threading, data structures, etc
- libevent | Networking | OS independent asynchronous networking
-
-Optional dependencies:
-
- Library | Purpose | Description
- ------------|------------------|----------------------
- miniupnpc | UPnP Support | Firewall-jumping support
- libnatpmp | NAT-PMP Support | Firewall-jumping support
- libdb4.8 | Berkeley DB | Wallet storage (only needed when legacy wallet enabled)
- qt | GUI | GUI toolkit (only needed when GUI enabled)
- libqrencode | QR codes in GUI | QR code generation (only needed when GUI enabled)
- libzmq3 | ZMQ notification | ZMQ notifications (requires ZMQ version >= 4.0.0)
- sqlite3 | SQLite DB | Wallet storage (only needed when descriptor wallet enabled)
- systemtap | Tracing (USDT) | Statically defined tracepoints
-
-For the versions used, see [dependencies.md](dependencies.md)
+See [dependencies.md](dependencies.md) for a complete overview.
Memory Requirements
--------------------
@@ -232,7 +209,7 @@ from the root of the repository.
Otherwise, you can build Bitcoin Core from self-compiled [depends](/depends/README.md).
-**Note**: You only need Berkeley DB if the wallet is enabled (see [*Disable-wallet mode*](#disable-wallet-mode)).
+**Note**: You only need Berkeley DB if the legacy wallet is enabled (see [*Disable-wallet mode*](#disable-wallet-mode)).
Security
--------
@@ -282,12 +259,12 @@ Hardening enables the following features:
Disable-wallet mode
--------------------
-When the intention is to run only a P2P node without a wallet, Bitcoin Core may be compiled in
-disable-wallet mode with:
+When the intention is to only run a P2P node, without a wallet, Bitcoin Core can
+be compiled in disable-wallet mode with:
./configure --disable-wallet
-In this case there is no dependency on Berkeley DB 4.8 and SQLite.
+In this case there is no dependency on SQLite or Berkeley DB.
Mining is also possible in disable-wallet mode using the `getblocktemplate` RPC call.
diff --git a/doc/cjdns.md b/doc/cjdns.md
new file mode 100644
index 0000000000..b69564729f
--- /dev/null
+++ b/doc/cjdns.md
@@ -0,0 +1,116 @@
+# CJDNS support in Bitcoin Core
+
+It is possible to run Bitcoin Core over CJDNS, an encrypted IPv6 network that
+uses public-key cryptography for address allocation and a distributed hash table
+for routing.
+
+## What is CJDNS?
+
+CJDNS is like a distributed, shared VPN with multiple entry points where every
+participant can reach any other participant. All participants use addresses from
+the `fc00::/8` network (reserved IPv6 range). Installation and configuration is
+done outside of Bitcoin Core, similarly to a VPN (either in the host/OS or on
+the network router). See https://github.com/cjdelisle/cjdns#readme and
+https://github.com/hyperboria/docs#hyperboriadocs for more information.
+
+Compared to IPv4/IPv6, CJDNS provides end-to-end encryption and protects nodes
+from traffic analysis and filtering.
+
+Used with Tor and I2P, CJDNS is a complementary option that can enhance network
+redundancy and robustness for both the Bitcoin network and individual nodes.
+
+Each network has different characteristics. For instance, Tor is widely used but
+somewhat centralized. I2P connections have a source address and I2P is slow.
+CJDNS is fast but does not hide the sender and the recipient from intermediate
+routers.
+
+## Installing CJDNS and finding a peer to connect to the network
+
+To install and set up CJDNS, follow the instructions at
+https://github.com/cjdelisle/cjdns#how-to-install-cjdns.
+
+You need to initiate an outbound connection to a peer on the CJDNS network
+before it will work with your Bitcoin Core node. This is described in steps
+["2. Find a friend"](https://github.com/cjdelisle/cjdns#2-find-a-friend) and
+["3. Connect your node to your friend's
+node"](https://github.com/cjdelisle/cjdns#3-connect-your-node-to-your-friends-node)
+in the CJDNS documentation.
+
+One quick way to accomplish these two steps is to query for available public
+peers on [Hyperboria](https://github.com/hyperboria) by running the following:
+
+```
+git clone https://github.com/hyperboria/peers hyperboria-peers
+cd hyperboria-peers
+./testAvailable.py
+```
+
+For each peer, the `./testAvailable.py` script prints the filename of the peer's
+credentials followed by the ping result.
+
+Choose one or several peers, copy their credentials from their respective files,
+paste them into the relevant IPv4 or IPv6 "connectTo" JSON object in the
+`cjdroute.conf` file you created in step ["1. Generate a new configuration
+file"](https://github.com/cjdelisle/cjdns#1-generate-a-new-configuration-file),
+and save the file.
+
+## Launching CJDNS
+
+Typically, CJDNS might be launched from its directory with
+`sudo ./cjdroute < cjdroute.conf` and it sheds permissions after setting up the
+[TUN](https://en.wikipedia.org/wiki/TUN/TAP) interface. You may also [launch it as an
+unprivileged user](https://github.com/cjdelisle/cjdns/blob/master/doc/non-root-user.md)
+with some additional setup.
+
+The network connection can be checked by running `./tools/peerStats` from the
+CJDNS directory.
+
+## Run Bitcoin Core with CJDNS
+
+Once you are connected to the CJDNS network, the following Bitcoin Core
+configuration option makes CJDNS peers automatically reachable:
+
+```
+-cjdnsreachable
+```
+
+When enabled, this option tells Bitcoin Core that it is running in an
+environment where a connection to an `fc00::/8` address will be to the CJDNS
+network instead of to an [RFC4193](https://datatracker.ietf.org/doc/html/rfc4193)
+IPv6 local network. This helps Bitcoin Core perform better address management:
+ - Your node can consider incoming `fc00::/8` connections to be from the CJDNS
+ network rather than from an IPv6 private one.
+ - If one of your node's local addresses is `fc00::/8`, then it can choose to
+ gossip that address to peers.
+
+## Additional configuration options related to CJDNS
+
+```
+-onlynet=cjdns
+```
+
+Make automatic outbound connections only to CJDNS addresses. Inbound and manual
+connections are not affected by this option. It can be specified multiple times
+to allow multiple networks, e.g. onlynet=cjdns, onlynet=i2p, onlynet=onion.
+
+CJDNS support was added to Bitcoin Core in version 23.0 and there may be fewer
+CJDNS peers than Tor or IP ones. You can use `bitcoin-cli -addrinfo` to see the
+number of CJDNS addresses known to your node.
+
+In general, a node can be run with both an onion service and CJDNS (or any/all
+of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if one of
+the networks has issues. There are a number of ways to configure this; see
+[doc/tor.md](https://github.com/bitcoin/bitcoin/blob/master/doc/tor.md) for
+details.
+
+## CJDNS-related information in Bitcoin Core
+
+There are several ways to see your CJDNS address in Bitcoin Core:
+- in the "Local addresses" output of CLI `-netinfo`
+- in the "localaddresses" output of RPC `getnetworkinfo`
+
+To see which CJDNS peers your node is connected to, use `bitcoin-cli -netinfo 4`
+or the `getpeerinfo` RPC (i.e. `bitcoin-cli getpeerinfo`).
+
+To see which CJDNS addresses your node knows, use the `getnodeaddresses 0 cjdns`
+RPC.
diff --git a/doc/dependencies.md b/doc/dependencies.md
index 4f1fd73c8a..d5d0c46679 100644
--- a/doc/dependencies.md
+++ b/doc/dependencies.md
@@ -1,46 +1,49 @@
-Dependencies
-============
-
-These are the dependencies currently used by Bitcoin Core. You can find instructions for installing them in the `build-*.md` file for your platform.
-
-| Dependency | Version used | Minimum required | CVEs | Shared | [Bundled Qt library](https://doc.qt.io/qt-5/configure-options.html#third-party-libraries) |
-| --- | --- | --- | --- | --- | --- |
-| Berkeley DB | [4.8.30](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | |
-| Boost | [1.77.0](https://www.boost.org/users/download/) | [1.64.0](https://github.com/bitcoin/bitcoin/pull/22320) | No | | |
-| Clang | | [8.0](https://releases.llvm.org/download.html) (C++17 & std::filesystem support) | | | |
-| Fontconfig | [2.12.6](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | |
-| FreeType | [2.11.0](https://download.savannah.gnu.org/releases/freetype) | | No | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Android only) |
-| GCC | | [8.1](https://gcc.gnu.org/) (C++17 & std::filesystem support) | | | |
-| glibc | | [2.18](https://www.gnu.org/software/libc/) | | | | |
-| HarfBuzz-NG | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) |
-| libevent | [2.1.12-stable](https://github.com/libevent/libevent/releases) | [2.0.21](https://github.com/bitcoin/bitcoin/pull/18676) | No | | |
-| libnatpmp | git commit [4536032...](https://github.com/miniupnp/libnatpmp/tree/4536032ae32268a45c073a4d5e91bbab4534773a) | | No | | |
-| libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) |
-| MiniUPnPc | [2.2.2](https://miniupnp.tuxfamily.org/files) | | No | | |
-| PCRE | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) |
-| Python (tests) | | [3.6](https://www.python.org/downloads) | | | |
-| qrencode | [3.4.4](https://fukuchi.org/works/qrencode) | | No | | |
-| Qt | [5.15.2](https://download.qt.io/official_releases/qt/) | [5.11.3](https://github.com/bitcoin/bitcoin/pull/24132) | No | | |
-| SQLite | [3.32.1](https://sqlite.org/download.html) | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | | | |
-| XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) |
-| systemtap ([tracing](tracing.md))| [4.5](https://sourceware.org/systemtap/ftp/releases/) | | | | |
-| xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) |
-| ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | |
-| zlib | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) |
-
-Controlling dependencies
-------------------------
-Some dependencies are not needed in all configurations. The following are some factors that affect the dependency list.
-
-#### Options passed to `./configure`
-* MiniUPnPc is not needed with `--without-miniupnpc`.
-* libnatpmp is not needed with `--without-natpmp`.
-* Berkeley DB is not needed with `--disable-wallet` or `--without-bdb`.
-* SQLite is not needed with `--disable-wallet` or `--without-sqlite`.
-* Qt is not needed with `--without-gui`.
-* If the qrencode dependency is absent, QR support won't be added. To force an error when that happens, pass `--with-qrencode`.
-* If the systemtap dependency is absent, USDT support won't compiled in.
-* ZeroMQ is needed only with the `--with-zmq` option.
-
-#### Other
-* Not-Qt-bundled zlib is required to build the [DMG tool](../contrib/macdeploy/README.md#deterministic-macos-dmg-notes) from the libdmg-hfsplus project.
+# Dependencies
+
+These are the dependencies used by Bitcoin Core.
+You can find installation instructions in the `build-*.md` file for your platform.
+"Runtime" and "Version Used" are both in reference to the release binaries.
+
+| Dependency | Minimum required |
+| --- | --- |
+| [Autoconf](https://www.gnu.org/software/autoconf/) | [2.69](https://github.com/bitcoin/bitcoin/pull/17769) |
+| [Automake](https://www.gnu.org/software/automake/) | [1.13](https://github.com/bitcoin/bitcoin/pull/18290) |
+| [Clang](https://clang.llvm.org) | [8.0](https://github.com/bitcoin/bitcoin/pull/24164) |
+| [GCC](https://gcc.gnu.org) | [8.1](https://github.com/bitcoin/bitcoin/pull/23060) |
+| [Python](https://www.python.org) (tests) | [3.6](https://github.com/bitcoin/bitcoin/pull/19504) |
+| [systemtap](https://sourceware.org/systemtap/) ([tracing](tracing.md))| N/A |
+
+## Required
+
+| Dependency | Version used | Minimum required | Runtime |
+| --- | --- | --- | --- |
+| [Boost](https://www.boost.org/users/download/) | 1.77.0 | [1.64.0](https://github.com/bitcoin/bitcoin/pull/22320) | No |
+| [libevent](https://github.com/libevent/libevent/releases) | 2.1.12-stable | [2.1.8](https://github.com/bitcoin/bitcoin/pull/24681) | No |
+| [glibc](https://www.gnu.org/software/libc/) | N/A | [2.18](https://github.com/bitcoin/bitcoin/pull/23511) | Yes |
+
+## Optional
+
+### GUI
+| Dependency | Version used | Minimum required | Runtime |
+| --- | --- | --- | --- |
+| [Fontconfig](https://www.freedesktop.org/wiki/Software/fontconfig/) | 2.12.6 | 2.6 | Yes |
+| [FreeType](https://freetype.org) | 2.11.0 | 2.3.0 | Yes |
+| [qrencode](https://fukuchi.org/works/qrencode/) | [3.4.4](https://fukuchi.org/works/qrencode) | | No |
+| [Qt](https://www.qt.io) | [5.15.3](https://download.qt.io/official_releases/qt/) | [5.11.3](https://github.com/bitcoin/bitcoin/pull/24132) | No |
+
+### Networking
+| Dependency | Version used | Minimum required | Runtime |
+| --- | --- | --- | --- |
+| [libnatpmp](https://github.com/miniupnp/libnatpmp/) | commit [4536032...](https://github.com/miniupnp/libnatpmp/tree/4536032ae32268a45c073a4d5e91bbab4534773a) | | No |
+| [MiniUPnPc](https://miniupnp.tuxfamily.org/) | 2.2.2 | 1.9 | No |
+
+### Notifications
+| Dependency | Version used | Minimum required | Runtime |
+| --- | --- | --- | --- |
+| [ZeroMQ](https://zeromq.org) | 4.3.4 | 4.0.0 | No |
+
+### Wallet
+| Dependency | Version used | Minimum required | Runtime |
+| --- | --- | --- | --- |
+| [Berkeley DB](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) (legacy wallet) | 4.8.30 | 4.8.x | No |
+| [SQLite](https://sqlite.org) | 3.32.1 | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | No |
diff --git a/doc/developer-notes.md b/doc/developer-notes.md
index bfb64093e1..9b1026a375 100644
--- a/doc/developer-notes.md
+++ b/doc/developer-notes.md
@@ -17,6 +17,7 @@ Developer Notes
- [`debug.log`](#debuglog)
- [Signet, testnet, and regtest modes](#signet-testnet-and-regtest-modes)
- [DEBUG_LOCKORDER](#debug_lockorder)
+ - [DEBUG_LOCKCONTENTION](#debug_lockcontention)
- [Valgrind suppressions file](#valgrind-suppressions-file)
- [Compiling for test coverage](#compiling-for-test-coverage)
- [Performance profiling with perf](#performance-profiling-with-perf)
@@ -137,11 +138,57 @@ public:
} // namespace foo
```
+Coding Style (C++ named arguments)
+------------------------------
+
+When passing named arguments, use a format that clang-tidy understands. The
+argument names can otherwise not be verified by clang-tidy.
+
+For example:
+
+```c++
+void function(Addrman& addrman, bool clear);
+
+int main()
+{
+ function(g_addrman, /*clear=*/false);
+}
+```
+
+### Running clang-tidy
+
+To run clang-tidy on Ubuntu/Debian, install the dependencies:
+
+```sh
+apt install clang-tidy bear clang
+```
+
+Then, pass clang as compiler to configure, and use bear to produce the `compile_commands.json`:
+
+```sh
+./autogen.sh && ./configure CC=clang CXX=clang++
+make clean && bear make -j $(nproc) # For bear 2.x
+make clean && bear -- make -j $(nproc) # For bear 3.x
+```
+
+To run clang-tidy on all source files:
+
+```sh
+( cd ./src/ && run-clang-tidy -j $(nproc) )
+```
+
+To run clang-tidy on the changed source lines:
+
+```sh
+git diff | ( cd ./src/ && clang-tidy-diff -p2 -j $(nproc) )
+```
+
Coding Style (Python)
---------------------
Refer to [/test/functional/README.md#style-guidelines](/test/functional/README.md#style-guidelines).
+
Coding Style (Doxygen-compatible comments)
------------------------------------------
@@ -316,6 +363,19 @@ configure option adds `-DDEBUG_LOCKORDER` to the compiler flags. This inserts
run-time checks to keep track of which locks are held and adds warnings to the
`debug.log` file if inconsistencies are detected.
+### DEBUG_LOCKCONTENTION
+
+Defining `DEBUG_LOCKCONTENTION` adds a "lock" logging category to the logging
+RPC that, when enabled, logs the location and duration of each lock contention
+to the `debug.log` file.
+
+To enable it, run configure with `-DDEBUG_LOCKCONTENTION` added to your
+CPPFLAGS, e.g. `CPPFLAGS="-DDEBUG_LOCKCONTENTION"`, then build and run bitcoind.
+
+You can then use the `-debug=lock` configuration option at bitcoind startup or
+`bitcoin-cli logging '["lock"]'` at runtime to turn on lock contention logging.
+It can be toggled off again with `bitcoin-cli logging [] '["lock"]'`.
+
### Assertions and Checks
The util file `src/util/check.h` offers helpers to protect against coding and
@@ -1160,7 +1220,10 @@ A few guidelines for introducing and reviewing new RPC interfaces:
- *Rationale*: Consistency with the existing interface.
-- Argument naming: use snake case `fee_delta` (and not, e.g. camel case `feeDelta`)
+- Argument and field naming: please consider whether there is already a naming
+ style or spelling convention in the API for the type of object in question
+ (`blockhash`, for example), and if so, try to use that. If not, use snake case
+ `fee_delta` (and not, e.g. `feedelta` or camel case `feeDelta`).
- *Rationale*: Consistency with the existing interface.
diff --git a/doc/i2p.md b/doc/i2p.md
index e45b5efb9b..39f65c4e5f 100644
--- a/doc/i2p.md
+++ b/doc/i2p.md
@@ -80,15 +80,15 @@ phase when syncing up a new node can be very slow. This phase can be sped up by
using other networks, for instance `onlynet=onion`, at the same time.
In general, a node can be run with both onion and I2P hidden services (or
-any/all of IPv4/IPv6/onion/I2P), which can provide a potential fallback if one
-of the networks has issues.
+any/all of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if
+one of the networks has issues.
## I2P-related information in Bitcoin Core
There are several ways to see your I2P address in Bitcoin Core:
-- in the debug log (grep for `AddLocal`, the I2P address ends in `.b32.i2p`)
-- in the output of the `getnetworkinfo` RPC in the "localaddresses" section
-- in the output of `bitcoin-cli -netinfo` peer connections dashboard
+- in the "Local addresses" output of CLI `-netinfo`
+- in the "localaddresses" output of RPC `getnetworkinfo`
+- in the debug log (grep for `AddLocal`; the I2P address ends in `.b32.i2p`)
To see which I2P peers your node is connected to, use `bitcoin-cli -netinfo 4`
or the `getpeerinfo` RPC (e.g. `bitcoin-cli getpeerinfo`).
diff --git a/doc/multisig-tutorial.md b/doc/multisig-tutorial.md
index 0793040418..1d2b3244bc 100644
--- a/doc/multisig-tutorial.md
+++ b/doc/multisig-tutorial.md
@@ -29,7 +29,7 @@ These three wallets should not be used directly for privacy reasons (public key
```bash
for ((n=1;n<=3;n++))
do
- ./src/bitcoin-cli -signet -named createwallet wallet_name="participant_${n}" descriptors=true
+ ./src/bitcoin-cli -signet createwallet "participant_${n}"
done
```
@@ -109,7 +109,7 @@ Then import the descriptors created in the previous step using the `importdescri
After that, `getwalletinfo` can be used to check if the wallet was created successfully.
```bash
-./src/bitcoin-cli -signet -named createwallet wallet_name="multisig_wallet_01" disable_private_keys=true blank=true descriptors=true
+./src/bitcoin-cli -signet -named createwallet wallet_name="multisig_wallet_01" disable_private_keys=true blank=true
./src/bitcoin-cli -signet -rpcwallet="multisig_wallet_01" importdescriptors "$multisig_desc"
@@ -238,4 +238,4 @@ psbt_2=$(./src/bitcoin-cli -signet -rpcwallet="participant_2" walletprocesspsbt
finalized_psbt_hex=$(./src/bitcoin-cli -signet finalizepsbt $psbt_2 | jq -r '.hex')
./src/bitcoin-cli -signet sendrawtransaction $finalized_psbt_hex
-``` \ No newline at end of file
+```
diff --git a/doc/policy/packages.md b/doc/policy/packages.md
index 7f7fbe18cd..f2a3d6517e 100644
--- a/doc/policy/packages.md
+++ b/doc/policy/packages.md
@@ -72,3 +72,48 @@ test accepts):
a competing package or transaction with a mutated witness, even though the two
same-txid-different-witness transactions are conflicting and cannot replace each other, the
honest package should still be considered for acceptance.
+
+### Package Fees and Feerate
+
+*Package Feerate* is the total modified fees (base fees + any fee delta from
+`prioritisetransaction`) divided by the total virtual size of all transactions in the package.
+If any transactions in the package are already in the mempool, they are not submitted again
+("deduplicated") and are thus excluded from this calculation.
+
+To meet the two feerate requirements of a mempool, i.e., the pre-configured minimum relay feerate
+(`minRelayTxFee`) and the dynamic mempool minimum feerate, the total package feerate is used instead
+of the individual feerate. The individual transactions are allowed to be below the feerate
+requirements if the package meets the feerate requirements. For example, the parent(s) in the
+package can pay no fees but be paid for by the child.
+
+*Rationale*: This can be thought of as "CPFP within a package," solving the issue of a parent not
+meeting minimum fees on its own. This would allow contracting applications to adjust their fees at
+broadcast time instead of overshooting or risking becoming stuck or pinned.
+
+*Rationale*: It would be incorrect to use the fees of transactions that are already in the mempool, as
+we do not want a transaction's fees to be double-counted.
+
+Implementation Note: Transactions within a package are always validated individually first, and
+package validation is used for the transactions that failed. Since package feerate is only
+calculated using transactions that are not in the mempool, this implementation detail affects the
+outcome of package validation.
+
+*Rationale*: Packages are intended for incentive-compatible fee-bumping: transaction B is a
+"legitimate" fee-bump for transaction A only if B is a descendant of A and has a *higher* feerate
+than A. We want to prevent "parents pay for children" behavior; fees of parents should not help
+their children, since the parents can be mined without the child. More generally, if transaction A
+is not needed in order for transaction B to be mined, A's fees cannot help B. In a
+child-with-parents package, simply excluding any parent transactions that meet feerate requirements
+individually is sufficient to ensure this.
+
+*Rationale*: We must not allow a low-feerate child to prevent its parent from being accepted; fees
+of children should not negatively impact their parents, since they are not necessary for the parents
+to be mined. More generally, if transaction B is not needed in order for transaction A to be mined,
+B's fees cannot harm A. In a child-with-parents package, simply validating parents individually
+first is sufficient to ensure this.
+
+*Rationale*: As a principle, we want to avoid accidentally restricting policy in order to be
+backward-compatible for users and applications that rely on p2p transaction relay. Concretely,
+package validation should not prevent the acceptance of a transaction that would otherwise be
+policy-valid on its own. By always accepting a transaction that passes individual validation before
+trying package validation, we prevent any unintentional restriction of policy.
diff --git a/doc/release-notes-24098.md b/doc/release-notes-24098.md
new file mode 100644
index 0000000000..79e047e9a5
--- /dev/null
+++ b/doc/release-notes-24098.md
@@ -0,0 +1,22 @@
+Notable changes
+===============
+
+Updated REST APIs
+-----------------
+
+- The `/headers/` and `/blockfilterheaders/` endpoints have been updated to use
+ a query parameter instead of path parameter to specify the result count. The
+ count parameter is now optional, and defaults to 5 for both endpoints. The old
+ endpoints are still functional, and have no documented behaviour change.
+
+ For `/headers`, use
+ `GET /rest/headers/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>`
+ instead of
+ `GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` (deprecated)
+
+ For `/blockfilterheaders/`, use
+ `GET /rest/blockfilterheaders/<FILTERTYPE>/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>`
+ instead of
+ `GET /rest/blockfilterheaders/<FILTERTYPE>/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` (deprecated)
+
+ (#24098)
diff --git a/doc/release-notes-24118.md b/doc/release-notes-24118.md
new file mode 100644
index 0000000000..16f23c7d00
--- /dev/null
+++ b/doc/release-notes-24118.md
@@ -0,0 +1,10 @@
+New RPCs
+--------
+
+- The `sendall` RPC spends specific UTXOs to one or more recipients
+ without creating change. By default, the `sendall` RPC will spend
+ every UTXO in the wallet. `sendall` is useful to empty wallets or to
+ create a changeless payment from select UTXOs. When creating a payment
+ from a specific amount for which the recipient incurs the transaction
+ fee, continue to use the `subtractfeefromamount` option via the
+ `send`, `sendtoaddress`, or `sendmany` RPCs. (#24118)
diff --git a/doc/release-notes-24494.md b/doc/release-notes-24494.md
new file mode 100644
index 0000000000..afbb926433
--- /dev/null
+++ b/doc/release-notes-24494.md
@@ -0,0 +1,2 @@
+To help prevent fingerprinting transactions created by the Bitcoin Core wallet, change output
+amounts are now randomized. (#24494)
diff --git a/doc/release-notes-empty-template.md b/doc/release-notes-empty-template.md
new file mode 100644
index 0000000000..8462714898
--- /dev/null
+++ b/doc/release-notes-empty-template.md
@@ -0,0 +1,99 @@
+*The release notes draft is a temporary file that can be added to by anyone. See
+[/doc/developer-notes.md#release-notes](/doc/developer-notes.md#release-notes)
+for the process.*
+
+*version* Release Notes Draft
+===============================
+
+Bitcoin Core version *version* is now available from:
+
+ <https://bitcoincore.org/bin/bitcoin-core-*version*/>
+
+This release includes new features, various bug fixes and performance
+improvements, as well as updated translations.
+
+Please report bugs using the issue tracker at GitHub:
+
+ <https://github.com/bitcoin/bitcoin/issues>
+
+To receive security and update notifications, please subscribe to:
+
+ <https://bitcoincore.org/en/list/announcements/join/>
+
+How to Upgrade
+==============
+
+If you are running an older version, shut it down. Wait until it has completely
+shut down (which might take a few minutes in some cases), then run the
+installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac)
+or `bitcoind`/`bitcoin-qt` (on Linux).
+
+Upgrading directly from a version of Bitcoin Core that has reached its EOL is
+possible, but it might take some time if the data directory needs to be migrated. Old
+wallet versions of Bitcoin Core are generally supported.
+
+Compatibility
+==============
+
+Bitcoin Core is supported and extensively tested on operating systems
+using the Linux kernel, macOS 10.15+, and Windows 7 and newer. Bitcoin
+Core should also work on most other Unix-like systems but is not as
+frequently tested on them. It is not recommended to use Bitcoin Core on
+unsupported systems.
+
+Notable changes
+===============
+
+P2P and network changes
+-----------------------
+
+Updated RPCs
+------------
+
+
+Changes to wallet related RPCs can be found in the Wallet section below.
+
+New RPCs
+--------
+
+Build System
+------------
+
+Updated settings
+----------------
+
+
+Changes to GUI or wallet related settings can be found in the GUI or Wallet section below.
+
+New settings
+------------
+
+Tools and Utilities
+-------------------
+
+Wallet
+------
+
+GUI changes
+-----------
+
+Low-level changes
+=================
+
+RPC
+---
+
+Tests
+-----
+
+*version* change log
+====================
+
+Credits
+=======
+
+Thanks to everyone who directly contributed to this release:
+
+
+As well as to everyone that helped with translations on
+[Transifex](https://www.transifex.com/bitcoin/bitcoin/).
diff --git a/doc/release-notes.md b/doc/release-notes.md
index 2342342ae2..8462714898 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,17 +1,7 @@
-*After branching off for a major version release of Bitcoin Core, use this
-template to create the initial release notes draft.*
-
*The release notes draft is a temporary file that can be added to by anyone. See
[/doc/developer-notes.md#release-notes](/doc/developer-notes.md#release-notes)
for the process.*
-*Create the draft, named* "*version* Release Notes Draft"
-*(e.g. "23.0 Release Notes Draft"), as a collaborative wiki in:*
-
-https://github.com/bitcoin-core/bitcoin-devwiki/wiki/
-
-*Before the final release, move the notes back to this git repository.*
-
*version* Release Notes Draft
===============================
@@ -54,9 +44,51 @@ unsupported systems.
Notable changes
===============
-Example item
+P2P and network changes
+-----------------------
+
+Updated RPCs
+------------
+
+
+Changes to wallet related RPCs can be found in the Wallet section below.
+
+New RPCs
+--------
+
+Build System
+------------
+
+Updated settings
+----------------
+
+
+Changes to GUI or wallet related settings can be found in the GUI or Wallet section below.
+
+New settings
------------
+Tools and Utilities
+-------------------
+
+Wallet
+------
+
+GUI changes
+-----------
+
+Low-level changes
+=================
+
+RPC
+---
+
+Tests
+-----
+
+*version* change log
+====================
+
Credits
=======
diff --git a/doc/release-process.md b/doc/release-process.md
index 5a74f72b6e..bc80e3d072 100644
--- a/doc/release-process.md
+++ b/doc/release-process.md
@@ -47,13 +47,15 @@ Release Process
#### After branch-off (on the major release branch)
- Update the versions.
+- Create the draft, named "*version* Release Notes Draft", as a [collaborative wiki](https://github.com/bitcoin-core/bitcoin-devwiki/wiki/_new).
+- Clear the release notes: `cp doc/release-notes-empty-template.md doc/release-notes.md`
- Create a pinned meta-issue for testing the release candidate (see [this issue](https://github.com/bitcoin/bitcoin/issues/17079) for an example) and provide a link to it in the release announcements where useful.
- Translations on Transifex
- Change the auto-update URL for the new major version's resource away from `master` and to the branch, e.g. `https://raw.githubusercontent.com/bitcoin/bitcoin/<branch>/src/qt/locale/bitcoin_en.xlf`. Do not forget this or it will keep tracking the translations on master instead, drifting away from the specific major release.
#### Before final release
-- Merge the release notes from the wiki into the branch.
+- Merge the release notes from [the wiki](https://github.com/bitcoin-core/bitcoin-devwiki/wiki/) into the branch.
- Ensure the "Needs release note" label is removed from all relevant pull requests and issues.
#### Tagging a release (candidate)
@@ -110,28 +112,24 @@ against other `guix-attest` signatures.
git -C ./guix.sigs pull
```
-### Create the macOS SDK tarball: (first time, or when SDK version changes)
+### Create the macOS SDK tarball (first time, or when SDK version changes)
Create the macOS SDK tarball, see the [macdeploy
instructions](/contrib/macdeploy/README.md#deterministic-macos-dmg-notes) for
details.
-### Build and attest to build outputs:
+### Build and attest to build outputs
Follow the relevant Guix README.md sections:
- [Building](/contrib/guix/README.md#building)
- [Attesting to build outputs](/contrib/guix/README.md#attesting-to-build-outputs)
-### Verify other builders' signatures to your own. (Optional)
+### Verify other builders' signatures to your own (optional)
-Add other builders keys to your gpg keyring, and/or refresh keys: See `../bitcoin/contrib/builder-keys/README.md`.
-
-Follow the relevant Guix README.md sections:
+- [Add other builders keys to your gpg keyring, and/or refresh keys](/contrib/builder-keys/README.md)
- [Verifying build output attestations](/contrib/guix/README.md#verifying-build-output-attestations)
-### Next steps:
-
-Commit your signature to guix.sigs:
+### Commit your non codesigned signature to guix.sigs
```sh
pushd ./guix.sigs
@@ -141,29 +139,27 @@ git push # Assuming you can push to the guix.sigs tree
popd
```
-Codesigner only: Create Windows/macOS detached signatures:
-- Only one person handles codesigning. Everyone else should skip to the next step.
-- Only once the Windows/macOS builds each have 3 matching signatures may they be signed with their respective release keys.
+## Codesigning
-Codesigner only: Sign the macOS binary:
+### macOS codesigner only: Create detached macOS signatures (assuming [signapple](https://github.com/achow101/signapple/) is installed and up to date with master branch)
- transfer bitcoin-osx-unsigned.tar.gz to macOS for signing
tar xf bitcoin-osx-unsigned.tar.gz
- ./detached-sig-create.sh -s "Key ID"
+ ./detached-sig-create.sh /path/to/codesign.p12
Enter the keychain password and authorize the signature
- Move signature-osx.tar.gz back to the guix-build host
+ signature-osx.tar.gz will be created
-Codesigner only: Sign the windows binaries:
+### Windows codesigner only: Create detached Windows signatures
tar xf bitcoin-win-unsigned.tar.gz
./detached-sig-create.sh -key /path/to/codesign.key
Enter the passphrase for the key when prompted
signature-win.tar.gz will be created
-Code-signer only: It is advised to test that the code signature attaches properly prior to tagging by performing the `guix-codesign` step.
+### Windows and macOS codesigners only: test code signatures
+It is advised to test that the code signature attaches properly prior to tagging by performing the `guix-codesign` step.
However if this is done, once the release has been tagged in the bitcoin-detached-sigs repo, the `guix-codesign` step must be performed again in order for the guix attestation to be valid when compared against the attestations of non-codesigner builds.
-Codesigner only: Commit the detached codesign payloads:
+### Windows and macOS codesigners only: Commit the detached codesign payloads
```sh
pushd ./bitcoin-detached-sigs
@@ -178,16 +174,21 @@ git push the current branch and new tag
popd
```
-Non-codesigners: wait for Windows/macOS detached signatures:
+### Non-codesigners: wait for Windows and macOS detached signatures
-- Once the Windows/macOS builds each have 3 matching signatures, they will be signed with their respective release keys.
+- Once the Windows and macOS builds each have 3 matching signatures, they will be signed with their respective release keys.
- Detached signatures will then be committed to the [bitcoin-detached-sigs](https://github.com/bitcoin-core/bitcoin-detached-sigs) repository, which can be combined with the unsigned apps to create signed binaries.
-Create (and optionally verify) the codesigned outputs:
+### Create the codesigned build outputs
-- [Codesigning](/contrib/guix/README.md#codesigning)
+- [Codesigning build outputs](/contrib/guix/README.md#codesigning-build-outputs)
+
+### Verify other builders' signatures to your own (optional)
+
+- [Add other builders keys to your gpg keyring, and/or refresh keys](/contrib/builder-keys/README.md)
+- [Verifying build output attestations](/contrib/guix/README.md#verifying-build-output-attestations)
-Commit your signature for the signed macOS/Windows binaries:
+### Commit your codesigned signature to guix.sigs (for the signed macOS/Windows binaries)
```sh
pushd ./guix.sigs
@@ -197,7 +198,7 @@ git push # Assuming you can push to the guix.sigs tree
popd
```
-### After 3 or more people have guix-built and their results match:
+## After 3 or more people have guix-built and their results match
Combine the `all.SHA256SUMS.asc` file from all signers into `SHA256SUMS.asc`:
diff --git a/doc/tor.md b/doc/tor.md
index b7c4f7d425..08d031d084 100644
--- a/doc/tor.md
+++ b/doc/tor.md
@@ -16,9 +16,9 @@ configure Tor.
## How to see information about your Tor configuration via Bitcoin Core
There are several ways to see your local onion address in Bitcoin Core:
-- in the debug log (grep for "tor:" or "AddLocal")
-- in the output of RPC `getnetworkinfo` in the "localaddresses" section
-- in the output of the CLI `-netinfo` peer connections dashboard
+- in the "Local addresses" output of CLI `-netinfo`
+- in the "localaddresses" output of RPC `getnetworkinfo`
+- in the debug log (grep for "AddLocal"; the Tor address ends in `.onion`)
You may set the `-debug=tor` config logging option to have additional
information in the debug log about your Tor configuration.
@@ -27,6 +27,9 @@ CLI `-addrinfo` returns the number of addresses known to your node per
network. This can be useful to see how many onion peers your node knows,
e.g. for `-onlynet=onion`.
+To fetch a number of onion addresses that your node knows, for example seven
+addresses, use the `getnodeaddresses 7 onion` RPC.
+
## 1. Run Bitcoin Core behind a Tor proxy
The first step is running Bitcoin Core behind a Tor proxy. This will already anonymize all
@@ -58,7 +61,7 @@ outgoing connections, but more is possible.
-onlynet=onion Make automatic outbound connections only to .onion addresses.
Inbound and manual connections are not affected by this option.
It can be specified multiple times to allow multiple networks,
- e.g. onlynet=onion, onlynet=i2p.
+ e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns.
In a typical situation, this suffices to run behind a Tor proxy:
diff --git a/src/Makefile.am b/src/Makefile.am
index e940736b71..c089bed0c9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,9 +8,9 @@ print-%: FORCE
DIST_SUBDIRS = secp256k1
-AM_LDFLAGS = $(LIBTOOL_LDFLAGS) $(HARDENED_LDFLAGS) $(GPROF_LDFLAGS) $(SANITIZER_LDFLAGS) $(LTO_LDFLAGS)
-AM_CXXFLAGS = $(DEBUG_CXXFLAGS) $(HARDENED_CXXFLAGS) $(WARN_CXXFLAGS) $(NOWARN_CXXFLAGS) $(ERROR_CXXFLAGS) $(GPROF_CXXFLAGS) $(SANITIZER_CXXFLAGS) $(LTO_CXXFLAGS)
-AM_CPPFLAGS = $(DEBUG_CPPFLAGS) $(HARDENED_CPPFLAGS)
+AM_LDFLAGS = $(LIBTOOL_LDFLAGS) $(HARDENED_LDFLAGS) $(GPROF_LDFLAGS) $(SANITIZER_LDFLAGS) $(LTO_LDFLAGS) $(CORE_LDFLAGS)
+AM_CXXFLAGS = $(DEBUG_CXXFLAGS) $(HARDENED_CXXFLAGS) $(WARN_CXXFLAGS) $(NOWARN_CXXFLAGS) $(ERROR_CXXFLAGS) $(GPROF_CXXFLAGS) $(SANITIZER_CXXFLAGS) $(LTO_CXXFLAGS) $(CORE_CXXFLAGS)
+AM_CPPFLAGS = $(DEBUG_CPPFLAGS) $(HARDENED_CPPFLAGS) $(CORE_CPPFLAGS)
AM_LIBTOOLFLAGS = --preserve-dup-deps
PTHREAD_FLAGS = $(PTHREAD_CFLAGS) $(PTHREAD_LIBS)
EXTRA_LIBRARIES =
@@ -206,6 +206,7 @@ BITCOIN_CORE_H = \
psbt.h \
random.h \
randomenv.h \
+ rest.h \
reverse_iterator.h \
rpc/blockchain.h \
rpc/client.h \
@@ -221,6 +222,7 @@ BITCOIN_CORE_H = \
scheduler.h \
script/descriptor.h \
script/keyorigin.h \
+ script/miniscript.h \
script/sigcache.h \
script/sign.h \
script/signingprovider.h \
@@ -378,6 +380,7 @@ libbitcoin_node_a_SOURCES = \
rpc/rawtransaction.cpp \
rpc/server.cpp \
rpc/server_util.cpp \
+ rpc/txoutproof.cpp \
script/sigcache.cpp \
shutdown.cpp \
signet.cpp \
@@ -589,6 +592,7 @@ libbitcoin_common_a_SOURCES = \
rpc/util.cpp \
scheduler.cpp \
script/descriptor.cpp \
+ script/miniscript.cpp \
script/sign.cpp \
script/signingprovider.cpp \
script/standard.cpp \
@@ -605,7 +609,6 @@ libbitcoin_util_a_SOURCES = \
chainparamsbase.cpp \
clientversion.cpp \
compat/glibcxx_sanity.cpp \
- compat/strnlen.cpp \
fs.cpp \
interfaces/echo.cpp \
interfaces/handler.cpp \
@@ -620,6 +623,7 @@ libbitcoin_util_a_SOURCES = \
util/asmap.cpp \
util/bip32.cpp \
util/bytevectorhash.cpp \
+ util/check.cpp \
util/error.cpp \
util/fees.cpp \
util/getuniquepath.cpp \
@@ -838,6 +842,7 @@ bitcoin_chainstate_SOURCES = \
uint256.cpp \
util/asmap.cpp \
util/bytevectorhash.cpp \
+ util/check.cpp \
util/getuniquepath.cpp \
util/hasher.cpp \
util/moneystr.cpp \
diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include
index 0bcce6ebe1..5dae4374e3 100644
--- a/src/Makefile.bench.include
+++ b/src/Makefile.bench.include
@@ -13,38 +13,39 @@ GENERATED_BENCH_FILES = $(RAW_BENCH_FILES:.raw=.raw.h)
bench_bench_bitcoin_SOURCES = \
$(RAW_BENCH_FILES) \
bench/addrman.cpp \
- bench/bench_bitcoin.cpp \
+ bench/base58.cpp \
+ bench/bech32.cpp \
bench/bench.cpp \
bench/bench.h \
+ bench/bench_bitcoin.cpp \
bench/block_assemble.cpp \
+ bench/ccoins_caching.cpp \
+ bench/chacha20.cpp \
+ bench/chacha_poly_aead.cpp \
bench/checkblock.cpp \
bench/checkqueue.cpp \
- bench/data.h \
+ bench/crypto_hash.cpp \
bench/data.cpp \
+ bench/data.h \
bench/duplicate_inputs.cpp \
bench/examples.cpp \
- bench/rollingbloom.cpp \
- bench/chacha20.cpp \
- bench/chacha_poly_aead.cpp \
- bench/crypto_hash.cpp \
- bench/ccoins_caching.cpp \
bench/gcs_filter.cpp \
bench/hashpadding.cpp \
- bench/merkle_root.cpp \
+ bench/lockedpool.cpp \
+ bench/logging.cpp \
bench/mempool_eviction.cpp \
bench/mempool_stress.cpp \
- bench/nanobench.h \
+ bench/merkle_root.cpp \
bench/nanobench.cpp \
+ bench/nanobench.h \
bench/peer_eviction.cpp \
+ bench/poly1305.cpp \
+ bench/prevector.cpp \
+ bench/rollingbloom.cpp \
bench/rpc_blockchain.cpp \
bench/rpc_mempool.cpp \
bench/util_time.cpp \
- bench/verify_script.cpp \
- bench/base58.cpp \
- bench/bech32.cpp \
- bench/lockedpool.cpp \
- bench/poly1305.cpp \
- bench/prevector.cpp
+ bench/verify_script.cpp
nodist_bench_bench_bitcoin_SOURCES = $(GENERATED_BENCH_FILES)
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index e2e08468a7..96a9a74802 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -94,6 +94,7 @@ BITCOIN_TESTS =\
test/fs_tests.cpp \
test/getarg_tests.cpp \
test/hash_tests.cpp \
+ test/httpserver_tests.cpp \
test/i2p_tests.cpp \
test/interfaces_tests.cpp \
test/key_io_tests.cpp \
@@ -103,6 +104,7 @@ BITCOIN_TESTS =\
test/merkle_tests.cpp \
test/merkleblock_tests.cpp \
test/miner_tests.cpp \
+ test/miniscript_tests.cpp \
test/minisketch_tests.cpp \
test/multisig_tests.cpp \
test/net_peer_eviction_tests.cpp \
@@ -115,12 +117,14 @@ BITCOIN_TESTS =\
test/prevector_tests.cpp \
test/raii_event_tests.cpp \
test/random_tests.cpp \
+ test/rest_tests.cpp \
test/reverselock_tests.cpp \
test/rpc_tests.cpp \
test/sanity_tests.cpp \
test/scheduler_tests.cpp \
test/script_p2sh_tests.cpp \
test/script_parse_tests.cpp \
+ test/script_segwit_tests.cpp \
test/script_standard_tests.cpp \
test/script_tests.cpp \
test/scriptnum10.h \
@@ -175,8 +179,11 @@ if USE_BDB
BITCOIN_TESTS += wallet/test/db_tests.cpp
endif
-if USE_SQLITE
FUZZ_WALLET_SRC = \
+ wallet/test/fuzz/coinselection.cpp
+
+if USE_SQLITE
+FUZZ_WALLET_SRC += \
wallet/test/fuzz/notifications.cpp
endif # USE_SQLITE
@@ -262,6 +269,7 @@ test_fuzz_fuzz_SOURCES = \
test/fuzz/locale.cpp \
test/fuzz/merkleblock.cpp \
test/fuzz/message.cpp \
+ test/fuzz/miniscript_decode.cpp \
test/fuzz/minisketch.cpp \
test/fuzz/muhash.cpp \
test/fuzz/multiplication_overflow.cpp \
@@ -384,8 +392,19 @@ univalue_test_object_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS)
endif
%.cpp.test: %.cpp
- @echo Running tests: `cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1` from $<
- $(AM_V_at)$(TEST_BINARY) --catch_system_errors=no -l test_suite -t "`cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" -- DEBUG_LOG_OUT > $<.log 2>&1 || (cat $<.log && false)
+ @echo Running tests: $$(\
+ cat $< | \
+ grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | \
+ cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1\
+ ) from $<
+ $(AM_V_at)export TEST_LOGFILE=$(abs_builddir)/$$(\
+ echo $< | grep -E -o "(wallet/test/.*\.cpp|test/.*\.cpp)" | $(SED) -e s/\.cpp/.log/ \
+ ) && \
+ $(TEST_BINARY) --catch_system_errors=no -l test_suite -t "$$(\
+ cat $< | \
+ grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | \
+ cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1\
+ )" -- DEBUG_LOG_OUT > "$$TEST_LOGFILE" 2>&1 || (cat "$$TEST_LOGFILE" && false)
%.json.h: %.json
@$(MKDIR_P) $(@D)
diff --git a/src/addrdb.cpp b/src/addrdb.cpp
index 0fa8f3c3da..1fa2644647 100644
--- a/src/addrdb.cpp
+++ b/src/addrdb.cpp
@@ -185,7 +185,7 @@ void ReadFromStream(AddrMan& addr, CDataStream& ssPeers)
std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const ArgsManager& args, std::unique_ptr<AddrMan>& addrman)
{
auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
- addrman = std::make_unique<AddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
+ addrman = std::make_unique<AddrMan>(asmap, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
int64_t nStart = GetTimeMillis();
const auto path_addr{args.GetDataDirNet() / "peers.dat"};
@@ -194,7 +194,7 @@ std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const A
LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), GetTimeMillis() - nStart);
} catch (const DbNotFoundError&) {
// Addrman can be in an inconsistent state after failure, reset it
- addrman = std::make_unique<AddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
+ addrman = std::make_unique<AddrMan>(asmap, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
LogPrintf("Creating peers.dat because the file was not found (%s)\n", fs::quoted(fs::PathToString(path_addr)));
DumpPeerAddresses(args, *addrman);
} catch (const InvalidAddrManVersionError&) {
@@ -203,7 +203,7 @@ std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const A
return strprintf(_("Failed to rename invalid peers.dat file. Please move or delete it and try again."));
}
// Addrman can be in an inconsistent state after failure, reset it
- addrman = std::make_unique<AddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman);
+ addrman = std::make_unique<AddrMan>(asmap, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
LogPrintf("Creating new peers.dat because the file version was not compatible (%s). Original backed up to peers.dat.bak\n", fs::quoted(fs::PathToString(path_addr)));
DumpPeerAddresses(args, *addrman);
} catch (const std::exception& e) {
diff --git a/src/addrman.cpp b/src/addrman.cpp
index 2fd8143c1c..2a08d99eef 100644
--- a/src/addrman.cpp
+++ b/src/addrman.cpp
@@ -946,16 +946,16 @@ std::optional<AddressPosition> AddrManImpl::FindAddressEntry_(const CAddress& ad
if(addr_info->fInTried) {
int bucket{addr_info->GetTriedBucket(nKey, m_asmap)};
- return AddressPosition(/*tried=*/true,
- /*multiplicity=*/1,
- /*bucket=*/bucket,
- /*position=*/addr_info->GetBucketPosition(nKey, false, bucket));
+ return AddressPosition(/*tried_in=*/true,
+ /*multiplicity_in=*/1,
+ /*bucket_in=*/bucket,
+ /*position_in=*/addr_info->GetBucketPosition(nKey, false, bucket));
} else {
int bucket{addr_info->GetNewBucket(nKey, m_asmap)};
- return AddressPosition(/*tried=*/false,
- /*multiplicity=*/addr_info->nRefCount,
- /*bucket=*/bucket,
- /*position=*/addr_info->GetBucketPosition(nKey, true, bucket));
+ return AddressPosition(/*tried_in=*/false,
+ /*multiplicity_in=*/addr_info->nRefCount,
+ /*bucket_in=*/bucket,
+ /*position_in=*/addr_info->GetBucketPosition(nKey, true, bucket));
}
}
diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp
index 3ca58b923e..34bc4380dd 100644
--- a/src/bench/addrman.cpp
+++ b/src/bench/addrman.cpp
@@ -101,7 +101,7 @@ static void AddrManGetAddr(benchmark::Bench& bench)
FillAddrMan(addrman);
bench.run([&] {
- const auto& addresses = addrman.GetAddr(/* max_addresses */ 2500, /* max_pct */ 23, /* network */ std::nullopt);
+ const auto& addresses = addrman.GetAddr(/*max_addresses=*/2500, /*max_pct=*/23, /*network=*/std::nullopt);
assert(addresses.size() > 0);
});
}
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index 609c592d20..de8ab9807c 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -13,7 +13,7 @@
using node::NodeContext;
using wallet::AttemptSelection;
-using wallet::CInputCoin;
+using wallet::CHANGE_LOWER;
using wallet::COutput;
using wallet::CWallet;
using wallet::CWalletTx;
@@ -58,14 +58,22 @@ static void CoinSelection(benchmark::Bench& bench)
// Create coins
std::vector<COutput> coins;
for (const auto& wtx : wtxs) {
- coins.emplace_back(wallet, *wtx, 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
+ coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true);
}
const CoinEligibilityFilter filter_standard(1, 6, 0);
- const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34,
- /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
- /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
+ FastRandomContext rand{};
+ const CoinSelectionParams coin_selection_params{
+ rand,
+ /*change_output_size=*/ 34,
+ /*change_spend_size=*/ 148,
+ /*min_change_target=*/ CHANGE_LOWER,
+ /*effective_feerate=*/ CFeeRate(0),
+ /*long_term_feerate=*/ CFeeRate(0),
+ /*discard_feerate=*/ CFeeRate(0),
+ /*tx_noinputs_size=*/ 0,
+ /*avoid_partial=*/ false,
+ };
bench.run([&] {
auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params);
assert(result);
@@ -74,17 +82,15 @@ static void CoinSelection(benchmark::Bench& bench)
});
}
-typedef std::set<CInputCoin> CoinSet;
-
// Copied from src/wallet/test/coinselector_tests.cpp
static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>& set)
{
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
- CInputCoin coin(MakeTransactionRef(tx), nInput);
+ COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true);
set.emplace_back();
- set.back().Insert(coin, 0, true, 0, 0, false);
+ set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
// Copied from src/wallet/test/coinselector_tests.cpp
static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool)
diff --git a/src/bench/logging.cpp b/src/bench/logging.cpp
new file mode 100644
index 0000000000..d28777df9e
--- /dev/null
+++ b/src/bench/logging.cpp
@@ -0,0 +1,48 @@
+// 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.
+
+#include <bench/bench.h>
+#include <logging.h>
+#include <test/util/setup_common.h>
+
+
+static void Logging(benchmark::Bench& bench, const std::vector<const char*>& extra_args, const std::function<void()>& log)
+{
+ TestingSetup test_setup{
+ CBaseChainParams::REGTEST,
+ extra_args,
+ };
+
+ bench.run([&] { log(); });
+}
+
+static void LoggingYoThreadNames(benchmark::Bench& bench)
+{
+ Logging(bench, {"-logthreadnames=1"}, [] { LogPrintf("%s\n", "test"); });
+}
+static void LoggingNoThreadNames(benchmark::Bench& bench)
+{
+ Logging(bench, {"-logthreadnames=0"}, [] { LogPrintf("%s\n", "test"); });
+}
+static void LoggingYoCategory(benchmark::Bench& bench)
+{
+ Logging(bench, {"-logthreadnames=0", "-debug=net"}, [] { LogPrint(BCLog::NET, "%s\n", "test"); });
+}
+static void LoggingNoCategory(benchmark::Bench& bench)
+{
+ Logging(bench, {"-logthreadnames=0", "-debug=0"}, [] { LogPrint(BCLog::NET, "%s\n", "test"); });
+}
+static void LoggingNoFile(benchmark::Bench& bench)
+{
+ Logging(bench, {"-nodebuglogfile", "-debug=1"}, [] {
+ LogPrintf("%s\n", "test");
+ LogPrint(BCLog::NET, "%s\n", "test");
+ });
+}
+
+BENCHMARK(LoggingYoThreadNames);
+BENCHMARK(LoggingNoThreadNames);
+BENCHMARK(LoggingYoCategory);
+BENCHMARK(LoggingNoCategory);
+BENCHMARK(LoggingNoFile);
diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp
index afa4618e1b..a58658c4f1 100644
--- a/src/bench/mempool_stress.cpp
+++ b/src/bench/mempool_stress.cpp
@@ -86,7 +86,7 @@ static void ComplexMemPool(benchmark::Bench& bench)
if (bench.complexityN() > 1) {
childTxs = static_cast<int>(bench.complexityN());
}
- std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /* min_ancestors */ 1);
+ std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /*min_ancestors=*/1);
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN);
CTxMemPool pool;
LOCK2(cs_main, pool.cs);
@@ -103,7 +103,7 @@ static void MempoolCheck(benchmark::Bench& bench)
{
FastRandomContext det_rand{true};
const int childTxs = bench.complexityN() > 1 ? static_cast<int>(bench.complexityN()) : 2000;
- const std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /* min_ancestors */ 5);
+ const std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /*min_ancestors=*/5);
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN, {"-checkmempool=1"});
CTxMemPool pool;
LOCK2(cs_main, pool.cs);
@@ -111,7 +111,7 @@ static void MempoolCheck(benchmark::Bench& bench)
for (auto& tx : ordered_coins) AddTx(tx, pool);
bench.run([&]() NO_THREAD_SAFETY_ANALYSIS {
- pool.check(coins_tip, /* spendheight */ 2);
+ pool.check(coins_tip, /*spendheight=*/2);
});
}
diff --git a/src/bench/rollingbloom.cpp b/src/bench/rollingbloom.cpp
index 9f1679aa84..8f05e3bad0 100644
--- a/src/bench/rollingbloom.cpp
+++ b/src/bench/rollingbloom.cpp
@@ -5,6 +5,9 @@
#include <bench/bench.h>
#include <common/bloom.h>
+#include <crypto/common.h>
+
+#include <vector>
static void RollingBloom(benchmark::Bench& bench)
{
@@ -13,16 +16,10 @@ static void RollingBloom(benchmark::Bench& bench)
uint32_t count = 0;
bench.run([&] {
count++;
- data[0] = count & 0xFF;
- data[1] = (count >> 8) & 0xFF;
- data[2] = (count >> 16) & 0xFF;
- data[3] = (count >> 24) & 0xFF;
+ WriteLE32(data.data(), count);
filter.insert(data);
- data[0] = (count >> 24) & 0xFF;
- data[1] = (count >> 16) & 0xFF;
- data[2] = (count >> 8) & 0xFF;
- data[3] = count & 0xFF;
+ WriteBE32(data.data(), count);
filter.contains(data);
});
}
diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp
index 64e4c46899..6e322ba6aa 100644
--- a/src/bench/rpc_mempool.cpp
+++ b/src/bench/rpc_mempool.cpp
@@ -29,11 +29,11 @@ static void RpcMempool(benchmark::Bench& bench)
tx.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL;
tx.vout[0].nValue = i;
const CTransactionRef tx_r{MakeTransactionRef(tx)};
- AddTx(tx_r, /* fee */ i, pool);
+ AddTx(tx_r, /*fee=*/i, pool);
}
bench.run([&] {
- (void)MempoolToJSON(pool, /*verbose*/ true);
+ (void)MempoolToJSON(pool, /*verbose=*/true);
});
}
diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp
index d4b8794c6d..a5dd2f3bb1 100644
--- a/src/bench/wallet_balance.cpp
+++ b/src/bench/wallet_balance.cpp
@@ -52,10 +52,10 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
});
}
-static void WalletBalanceDirty(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ true, /* add_mine */ true); }
-static void WalletBalanceClean(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_mine */ true); }
-static void WalletBalanceMine(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_mine */ true); }
-static void WalletBalanceWatch(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_mine */ false); }
+static void WalletBalanceDirty(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/true, /*add_mine=*/true); }
+static void WalletBalanceClean(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/true); }
+static void WalletBalanceMine(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/true); }
+static void WalletBalanceWatch(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/false); }
BENCHMARK(WalletBalanceDirty);
BENCHMARK(WalletBalanceClean);
diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp
index 72b8fefcc7..fcbb6aacce 100644
--- a/src/bitcoin-chainstate.cpp
+++ b/src/bitcoin-chainstate.cpp
@@ -192,7 +192,7 @@ int main(int argc, char* argv[])
bool new_block;
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
RegisterSharedValidationInterface(sc);
- bool accepted = chainman.ProcessNewBlock(chainparams, blockptr, /* force_processing */ true, /* new_block */ &new_block);
+ bool accepted = chainman.ProcessNewBlock(chainparams, blockptr, /*force_processing=*/true, /*new_block=*/&new_block);
UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
std::cerr << "duplicate" << std::endl;
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 5523fff3b2..6925a4c8c6 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -184,7 +184,6 @@ struct HTTPReply
static std::string http_errorstring(int code)
{
switch(code) {
-#if LIBEVENT_VERSION_NUMBER >= 0x02010300
case EVREQ_HTTP_TIMEOUT:
return "timeout reached";
case EVREQ_HTTP_EOF:
@@ -197,7 +196,6 @@ static std::string http_errorstring(int code)
return "request was canceled";
case EVREQ_HTTP_DATA_TOO_LONG:
return "response body is larger than allowed";
-#endif
default:
return "unknown";
}
@@ -228,13 +226,11 @@ static void http_request_done(struct evhttp_request *req, void *ctx)
}
}
-#if LIBEVENT_VERSION_NUMBER >= 0x02010300
static void http_error_cb(enum evhttp_request_error err, void *ctx)
{
HTTPReply *reply = static_cast<HTTPReply*>(ctx);
reply->error = err;
}
-#endif
/** Class that handles the conversion from a command-line to a JSON-RPC request,
* as well as converting back to a JSON object that can be shown as result.
@@ -745,11 +741,11 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
HTTPReply response;
raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
- if (req == nullptr)
+ if (req == nullptr) {
throw std::runtime_error("create http request failed");
-#if LIBEVENT_VERSION_NUMBER >= 0x02010300
+ }
+
evhttp_request_set_error_cb(req.get(), http_error_cb);
-#endif
// Get credentials
std::string strRPCUserColonPass;
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index b297081cab..0b40626595 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -750,7 +750,7 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command,
static void OutputTxJSON(const CTransaction& tx)
{
UniValue entry(UniValue::VOBJ);
- TxToUniv(tx, uint256(), entry);
+ TxToUniv(tx, /*block_hash=*/uint256(), entry);
std::string jsonOutput = entry.write(4);
tfm::format(std::cout, "%s\n", jsonOutput);
diff --git a/src/bitcoin-util.cpp b/src/bitcoin-util.cpp
index b457e0b354..a2f3fca26c 100644
--- a/src/bitcoin-util.cpp
+++ b/src/bitcoin-util.cpp
@@ -22,8 +22,6 @@
#include <memory>
#include <thread>
-#include <boost/algorithm/string.hpp>
-
static const int CONTINUE_EXECUTION=-1;
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 93510e925f..c65a816a5f 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -9,6 +9,7 @@
#include <consensus/merkle.h>
#include <deploymentinfo.h>
#include <hash.h> // for signet block challenge hash
+#include <script/interpreter.h>
#include <util/system.h>
#include <assert.h>
@@ -65,7 +66,10 @@ public:
consensus.signet_blocks = false;
consensus.signet_challenge.clear();
consensus.nSubsidyHalvingInterval = 210000;
- consensus.BIP16Exception = uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22");
+ consensus.script_flag_exceptions.emplace( // BIP16 exception
+ uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22"), SCRIPT_VERIFY_NONE);
+ consensus.script_flag_exceptions.emplace( // Taproot exception
+ uint256S("0x0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad"), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS);
consensus.BIP34Height = 227931;
consensus.BIP34Hash = uint256S("0x000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8");
consensus.BIP65Height = 388381; // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0
@@ -184,7 +188,8 @@ public:
consensus.signet_blocks = false;
consensus.signet_challenge.clear();
consensus.nSubsidyHalvingInterval = 210000;
- consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105");
+ consensus.script_flag_exceptions.emplace( // BIP16 exception
+ uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"), SCRIPT_VERIFY_NONE);
consensus.BIP34Height = 21111;
consensus.BIP34Hash = uint256S("0x0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8");
consensus.BIP65Height = 581885; // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6
@@ -323,7 +328,6 @@ public:
consensus.signet_blocks = true;
consensus.signet_challenge.assign(bin.begin(), bin.end());
consensus.nSubsidyHalvingInterval = 210000;
- consensus.BIP16Exception = uint256{};
consensus.BIP34Height = 1;
consensus.BIP34Hash = uint256{};
consensus.BIP65Height = 1;
@@ -391,7 +395,6 @@ public:
consensus.signet_blocks = false;
consensus.signet_challenge.clear();
consensus.nSubsidyHalvingInterval = 150;
- consensus.BIP16Exception = uint256();
consensus.BIP34Height = 1; // Always active unless overridden
consensus.BIP34Hash = uint256();
consensus.BIP65Height = 1; // Always active unless overridden
diff --git a/src/compat.h b/src/compat.h
index 237b881b11..3ec4ab53fd 100644
--- a/src/compat.h
+++ b/src/compat.h
@@ -80,10 +80,6 @@ typedef int32_t ssize_t;
#endif
#endif
-#if HAVE_DECL_STRNLEN == 0
-size_t strnlen( const char *start, size_t max_len);
-#endif // HAVE_DECL_STRNLEN
-
#ifndef WIN32
typedef void* sockopt_arg_type;
#else
diff --git a/src/compat/strnlen.cpp b/src/compat/strnlen.cpp
deleted file mode 100644
index 93a034a664..0000000000
--- a/src/compat/strnlen.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2009-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.
-
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
-
-#include <cstring>
-
-#if HAVE_DECL_STRNLEN == 0
-size_t strnlen( const char *start, size_t max_len)
-{
- const char *end = (const char *)memchr(start, '\0', max_len);
-
- return end ? (size_t)(end - start) : max_len;
-}
-#endif // HAVE_DECL_STRNLEN
diff --git a/src/consensus/params.h b/src/consensus/params.h
index 1ed5ca469f..0881f2e388 100644
--- a/src/consensus/params.h
+++ b/src/consensus/params.h
@@ -7,7 +7,9 @@
#define BITCOIN_CONSENSUS_PARAMS_H
#include <uint256.h>
+
#include <limits>
+#include <map>
namespace Consensus {
@@ -70,8 +72,13 @@ struct BIP9Deployment {
struct Params {
uint256 hashGenesisBlock;
int nSubsidyHalvingInterval;
- /* Block hash that is excepted from BIP16 enforcement */
- uint256 BIP16Exception;
+ /**
+ * Hashes of blocks that
+ * - are known to be consensus valid, and
+ * - buried in the chain, and
+ * - fail if the default script verify flags are applied.
+ */
+ std::map<uint256, uint32_t> script_flag_exceptions;
/** Block height and hash at which BIP34 becomes active */
int BIP34Height;
uint256 BIP34Hash;
diff --git a/src/core_io.h b/src/core_io.h
index 69b9ac3ebd..aa1381c374 100644
--- a/src/core_io.h
+++ b/src/core_io.h
@@ -53,8 +53,7 @@ UniValue ValueFromAmount(const CAmount amount);
std::string FormatScript(const CScript& script);
std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0);
std::string SighashToStr(unsigned char sighash_type);
-void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool include_hex, bool include_address = true);
-void ScriptToUniv(const CScript& script, UniValue& out);
-void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS);
+void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex = true, bool include_address = false);
+void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS);
#endif // BITCOIN_CORE_IO_H
diff --git a/src/core_write.cpp b/src/core_write.cpp
index c4b6b8d27e..cfd4cb1b49 100644
--- a/src/core_write.cpp
+++ b/src/core_write.cpp
@@ -19,6 +19,10 @@
#include <util/strencodings.h>
#include <util/system.h>
+#include <map>
+#include <string>
+#include <vector>
+
UniValue ValueFromAmount(const CAmount amount)
{
static_assert(COIN > 1);
@@ -143,29 +147,28 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags)
return HexStr(ssTx);
}
-void ScriptToUniv(const CScript& script, UniValue& out)
-{
- ScriptPubKeyToUniv(script, out, /* include_hex */ true, /* include_address */ false);
-}
-
-void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool include_hex, bool include_address)
+void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex, bool include_address)
{
CTxDestination address;
- out.pushKV("asm", ScriptToAsmStr(scriptPubKey));
- out.pushKV("desc", InferDescriptor(scriptPubKey, DUMMY_SIGNING_PROVIDER)->ToString());
- if (include_hex) out.pushKV("hex", HexStr(scriptPubKey));
+ out.pushKV("asm", ScriptToAsmStr(script));
+ if (include_address) {
+ out.pushKV("desc", InferDescriptor(script, DUMMY_SIGNING_PROVIDER)->ToString());
+ }
+ if (include_hex) {
+ out.pushKV("hex", HexStr(script));
+ }
std::vector<std::vector<unsigned char>> solns;
- const TxoutType type{Solver(scriptPubKey, solns)};
+ const TxoutType type{Solver(script, solns)};
- if (include_address && ExtractDestination(scriptPubKey, address) && type != TxoutType::PUBKEY) {
+ if (include_address && ExtractDestination(script, address) && type != TxoutType::PUBKEY) {
out.pushKV("address", EncodeDestination(address));
}
out.pushKV("type", GetTxnOutputType(type));
}
-void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo, TxVerbosity verbosity)
+void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo, TxVerbosity verbosity)
{
entry.pushKV("txid", tx.GetHash().GetHex());
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
@@ -213,7 +216,7 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
if (verbosity == TxVerbosity::SHOW_DETAILS_AND_PREVOUT) {
UniValue o_script_pub_key(UniValue::VOBJ);
- ScriptPubKeyToUniv(prev_txout.scriptPubKey, o_script_pub_key, /*include_hex=*/ true);
+ ScriptToUniv(prev_txout.scriptPubKey, /*out=*/o_script_pub_key, /*include_hex=*/true, /*include_address=*/true);
UniValue p(UniValue::VOBJ);
p.pushKV("generated", bool(prev_coin.fCoinBase));
@@ -238,7 +241,7 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
out.pushKV("n", (int64_t)i);
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(txout.scriptPubKey, o, true);
+ ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
out.pushKV("scriptPubKey", o);
vout.push_back(out);
@@ -254,8 +257,9 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
entry.pushKV("fee", ValueFromAmount(fee));
}
- if (!hashBlock.IsNull())
- entry.pushKV("blockhash", hashBlock.GetHex());
+ if (!block_hash.IsNull()) {
+ entry.pushKV("blockhash", block_hash.GetHex());
+ }
if (include_hex) {
entry.pushKV("hex", EncodeHexTx(tx, serialize_flags)); // The hex-encoded transaction. Used the name "hex" to be consistent with the verbose output of "getrawtransaction".
diff --git a/src/fs.h b/src/fs.h
index 00b786453c..8e145cbb8e 100644
--- a/src/fs.h
+++ b/src/fs.h
@@ -51,12 +51,26 @@ public:
// Disallow std::string conversion method to avoid locale-dependent encoding on windows.
std::string string() const = delete;
+ std::string u8string() const
+ {
+ const auto& utf8_str{std::filesystem::path::u8string()};
+ // utf8_str might either be std::string (C++17) or std::u8string
+ // (C++20). Convert both to std::string. This method can be removed
+ // after switching to C++20.
+ return std::string{utf8_str.begin(), utf8_str.end()};
+ }
+
// Required for path overloads in <fstream>.
// See https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=96e0367ead5d8dcac3bec2865582e76e2fbab190
path& make_preferred() { std::filesystem::path::make_preferred(); return *this; }
path filename() const { return std::filesystem::path::filename(); }
};
+static inline path u8path(const std::string& utf8_str)
+{
+ return std::filesystem::u8path(utf8_str);
+}
+
// Disallow implicit std::string conversion for absolute to avoid
// locale-dependent encoding on windows.
static inline path absolute(const path& p)
@@ -116,8 +130,8 @@ static inline std::string PathToString(const path& path)
// use here, because these methods encode the path using C++'s narrow
// multibyte encoding, which on Windows corresponds to the current "code
// page", which is unpredictable and typically not able to represent all
- // valid paths. So std::filesystem::path::u8string() and
- // std::filesystem::u8path() functions are used instead on Windows. On
+ // valid paths. So fs::path::u8string() and
+ // fs::u8path() functions are used instead on Windows. On
// POSIX, u8string/u8path functions are not safe to use because paths are
// not always valid UTF-8, so plain string methods which do not transform
// the path there are used.
diff --git a/src/httpserver.cpp b/src/httpserver.cpp
index e00c68585e..96bee8640d 100644
--- a/src/httpserver.cpp
+++ b/src/httpserver.cpp
@@ -23,6 +23,7 @@
#include <deque>
#include <memory>
+#include <optional>
#include <stdio.h>
#include <stdlib.h>
#include <string>
@@ -30,11 +31,12 @@
#include <sys/types.h>
#include <sys/stat.h>
-#include <event2/thread.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
-#include <event2/util.h>
+#include <event2/http.h>
#include <event2/keyvalq_struct.h>
+#include <event2/thread.h>
+#include <event2/util.h>
#include <support/events.h>
@@ -358,12 +360,8 @@ bool InitHTTPServer()
// Redirect libevent's logging to our own log
event_set_log_callback(&libevent_log_cb);
- // Update libevent's log handling. Returns false if our version of
- // libevent doesn't support debug logging, in which case we should
- // clear the BCLog::LIBEVENT flag.
- if (!UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT))) {
- LogInstance().DisableCategory(BCLog::LIBEVENT);
- }
+ // Update libevent's log handling.
+ UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT));
#ifdef WIN32
evthread_use_windows_threads();
@@ -402,18 +400,12 @@ bool InitHTTPServer()
return true;
}
-bool UpdateHTTPServerLogging(bool enable) {
-#if LIBEVENT_VERSION_NUMBER >= 0x02010100
+void UpdateHTTPServerLogging(bool enable) {
if (enable) {
event_enable_debug_logging(EVENT_DBG_ALL);
} else {
event_enable_debug_logging(EVENT_DBG_NONE);
}
- return true;
-#else
- // Can't update libevent logging if version < 02010100
- return false;
-#endif
}
static std::thread g_thread_http;
@@ -639,6 +631,37 @@ HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod() const
}
}
+std::optional<std::string> HTTPRequest::GetQueryParameter(const std::string& key) const
+{
+ const char* uri{evhttp_request_get_uri(req)};
+
+ return GetQueryParameterFromUri(uri, key);
+}
+
+std::optional<std::string> GetQueryParameterFromUri(const char* uri, const std::string& key)
+{
+ evhttp_uri* uri_parsed{evhttp_uri_parse(uri)};
+ const char* query{evhttp_uri_get_query(uri_parsed)};
+ std::optional<std::string> result;
+
+ if (query) {
+ // Parse the query string into a key-value queue and iterate over it
+ struct evkeyvalq params_q;
+ evhttp_parse_query_str(query, &params_q);
+
+ for (struct evkeyval* param{params_q.tqh_first}; param != nullptr; param = param->next.tqe_next) {
+ if (param->key == key) {
+ result = param->value;
+ break;
+ }
+ }
+ evhttp_clear_headers(&params_q);
+ }
+ evhttp_uri_free(uri_parsed);
+
+ return result;
+}
+
void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler)
{
LogPrint(BCLog::HTTP, "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch);
diff --git a/src/httpserver.h b/src/httpserver.h
index 97cd63778a..5ab3f18927 100644
--- a/src/httpserver.h
+++ b/src/httpserver.h
@@ -5,8 +5,9 @@
#ifndef BITCOIN_HTTPSERVER_H
#define BITCOIN_HTTPSERVER_H
-#include <string>
#include <functional>
+#include <optional>
+#include <string>
static const int DEFAULT_HTTP_THREADS=4;
static const int DEFAULT_HTTP_WORKQUEUE=16;
@@ -31,9 +32,8 @@ void InterruptHTTPServer();
/** Stop HTTP server */
void StopHTTPServer();
-/** Change logging level for libevent. Removes BCLog::LIBEVENT from log categories if
- * libevent doesn't support debug logging.*/
-bool UpdateHTTPServerLogging(bool enable);
+/** Change logging level for libevent. */
+void UpdateHTTPServerLogging(bool enable);
/** Handler for requests to a certain HTTP path */
typedef std::function<bool(HTTPRequest* req, const std::string &)> HTTPRequestHandler;
@@ -83,6 +83,17 @@ public:
*/
RequestMethod GetRequestMethod() const;
+ /** Get the query parameter value from request uri for a specified key, or std::nullopt if the
+ * key is not found.
+ *
+ * If the query string contains duplicate keys, the first value is returned. Many web frameworks
+ * would instead parse this as an array of values, but this is not (yet) implemented as it is
+ * currently not needed in any of the endpoints.
+ *
+ * @param[in] key represents the query parameter of which the value is returned
+ */
+ std::optional<std::string> GetQueryParameter(const std::string& key) const;
+
/**
* Get the request header specified by hdr, or an empty string.
* Return a pair (isPresent,string).
@@ -115,6 +126,20 @@ public:
void WriteReply(int nStatus, const std::string& strReply = "");
};
+/** Get the query parameter value from request uri for a specified key, or std::nullopt if the key
+ * is not found.
+ *
+ * If the query string contains duplicate keys, the first value is returned. Many web frameworks
+ * would instead parse this as an array of values, but this is not (yet) implemented as it is
+ * currently not needed in any of the endpoints.
+ *
+ * Helper function for HTTPRequest::GetQueryParameter.
+ *
+ * @param[in] uri is the entire request uri
+ * @param[in] key represents the query parameter of which the value is returned
+ */
+std::optional<std::string> GetQueryParameterFromUri(const char* uri, const std::string& key);
+
/** Event handler closure.
*/
class HTTPClosure
diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp
index 386eb67ce9..69078708f9 100644
--- a/src/index/coinstatsindex.cpp
+++ b/src/index/coinstatsindex.cpp
@@ -277,7 +277,7 @@ bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* n
{
LOCK(cs_main);
- CBlockIndex* iter_tip{m_chainstate->m_blockman.LookupBlockIndex(current_tip->GetBlockHash())};
+ const CBlockIndex* iter_tip{m_chainstate->m_blockman.LookupBlockIndex(current_tip->GetBlockHash())};
const auto& consensus_params{Params().GetConsensus()};
do {
diff --git a/src/init.cpp b/src/init.cpp
index bad402e56e..86e6ec4451 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -444,7 +444,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), signetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
- argsman.AddArg("-cjdnsreachable", "If set then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -457,7 +457,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> connections to peers (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by outbound peers forward or backward by this amount (default: %u seconds).", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target per 24h. Limit does not apply to peers with 'download' permission or blocks created within past week. 0 = no limit (default: %s). Optional suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000 base while uppercase is 1024 base", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -792,7 +792,7 @@ bool AppInitBasicSetup(const ArgsManager& args)
return true;
}
-bool AppInitParameterInteraction(const ArgsManager& args)
+bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandbox)
{
const CChainParams& chainparams = Params();
// ********************************************************* Step 2: parameter interactions
@@ -853,12 +853,14 @@ bool AppInitParameterInteraction(const ArgsManager& args)
nLocalServices = ServiceFlags(nLocalServices | NODE_COMPACT_FILTERS);
}
- // if using block pruning, then disallow txindex and coinstatsindex
if (args.GetIntArg("-prune", 0)) {
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX))
return InitError(_("Prune mode is incompatible with -txindex."));
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX))
return InitError(_("Prune mode is incompatible with -coinstatsindex."));
+ if (args.GetBoolArg("-reindex-chainstate", false)) {
+ return InitError(_("Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead."));
+ }
}
// If -forcednsseed is set to true, ensure -dnsseed has not been set to false
@@ -1056,6 +1058,9 @@ bool AppInitParameterInteraction(const ArgsManager& args)
if (!SetupSyscallSandbox(log_syscall_violation_before_terminating)) {
return InitError(Untranslated("Installation of the syscall sandbox failed."));
}
+ if (use_syscall_sandbox) {
+ SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION);
+ }
LogPrintf("Experimental syscall sandbox enabled (-sandbox=%s): bitcoind will terminate if an unexpected (not allowlisted) syscall is invoked.\n", sandbox_arg);
}
#endif // USE_SYSCALL_SANDBOX
@@ -1300,6 +1305,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
for (int n = 0; n < NET_MAX; n++) {
enum Network net = (enum Network)n;
+ assert(IsReachable(net));
if (!nets.count(net))
SetReachable(net, false);
}
@@ -1450,8 +1456,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
strLoadError = _("Error initializing block database");
break;
case ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED:
- strLoadError = _("Error upgrading chainstate database");
- break;
+ return InitError(_("Unsupported chainstate database format found. "
+ "Please restart with -reindex-chainstate. This will "
+ "rebuild the chainstate database."));
case ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED:
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.");
break;
@@ -1538,7 +1545,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// ********************************************************* Step 8: start indexers
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
- if (const auto error{CheckLegacyTxindex(*Assert(chainman.m_blockman.m_block_tree_db))}) {
+ if (const auto error{WITH_LOCK(cs_main, return CheckLegacyTxindex(*Assert(chainman.m_blockman.m_block_tree_db)))}) {
return InitError(*error);
}
diff --git a/src/init.h b/src/init.h
index ddd439f619..2250ae20a0 100644
--- a/src/init.h
+++ b/src/init.h
@@ -41,7 +41,7 @@ bool AppInitBasicSetup(const ArgsManager& args);
* @note This can be done before daemonization. Do not call Shutdown() if this function fails.
* @pre Parameters should be parsed and config file should be read, AppInitBasicSetup should have been called.
*/
-bool AppInitParameterInteraction(const ArgsManager& args);
+bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandbox = true);
/**
* Initialization sanity checks: ecc init, sanity checks, dir lock.
* @note This can be done before daemonization. Do not call Shutdown() if this function fails.
diff --git a/src/key.cpp b/src/key.cpp
index 354bd097ce..a54569b39e 100644
--- a/src/key.cpp
+++ b/src/key.cpp
@@ -288,7 +288,7 @@ bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint2
uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root);
if (!secp256k1_keypair_xonly_tweak_add(GetVerifyContext(), &keypair, tweak.data())) return false;
}
- bool ret = secp256k1_schnorrsig_sign(secp256k1_context_sign, sig.data(), hash.data(), &keypair, aux.data());
+ bool ret = secp256k1_schnorrsig_sign32(secp256k1_context_sign, sig.data(), hash.data(), &keypair, aux.data());
if (ret) {
// Additional verification step to prevent using a potentially corrupted signature
secp256k1_xonly_pubkey pubkey_verify;
diff --git a/src/logging.cpp b/src/logging.cpp
index 764941c8ea..a5e5fdb4cd 100644
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -160,7 +160,9 @@ const CLogCategoryDesc LogCategories[] =
{BCLog::VALIDATION, "validation"},
{BCLog::I2P, "i2p"},
{BCLog::IPC, "ipc"},
+#ifdef DEBUG_LOCKCONTENTION
{BCLog::LOCK, "lock"},
+#endif
{BCLog::UTIL, "util"},
{BCLog::BLOCKSTORE, "blockstorage"},
{BCLog::ALL, "1"},
diff --git a/src/logging.h b/src/logging.h
index 710e6c4c32..ae8cad906c 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -60,7 +60,9 @@ namespace BCLog {
VALIDATION = (1 << 21),
I2P = (1 << 22),
IPC = (1 << 23),
+#ifdef DEBUG_LOCKCONTENTION
LOCK = (1 << 24),
+#endif
UTIL = (1 << 25),
BLOCKSTORE = (1 << 26),
ALL = ~(uint32_t)0,
diff --git a/src/net.cpp b/src/net.cpp
index 955eec46e3..602d56ab98 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -626,12 +626,6 @@ void CNode::CopyStats(CNodeStats& stats)
X(addr);
X(addrBind);
stats.m_network = ConnectedThroughNetwork();
- if (m_tx_relay != nullptr) {
- LOCK(m_tx_relay->cs_filter);
- stats.fRelayTxes = m_tx_relay->fRelayTxes;
- } else {
- stats.fRelayTxes = false;
- }
X(m_last_send);
X(m_last_recv);
X(m_last_tx_time);
@@ -658,11 +652,6 @@ void CNode::CopyStats(CNodeStats& stats)
X(nRecvBytes);
}
X(m_permissionFlags);
- if (m_tx_relay != nullptr) {
- stats.minFeeFilter = m_tx_relay->minFeeFilter;
- } else {
- stats.minFeeFilter = 0;
- }
X(m_last_ping_time);
X(m_min_ping_time);
@@ -913,7 +902,7 @@ static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEviction
{
// There is a fall-through here because it is common for a node to have more than a few peers that have not yet relayed txn.
if (a.m_last_tx_time != b.m_last_tx_time) return a.m_last_tx_time < b.m_last_tx_time;
- if (a.fRelayTxes != b.fRelayTxes) return b.fRelayTxes;
+ if (a.m_relay_txs != b.m_relay_txs) return b.m_relay_txs;
if (a.fBloomFilter != b.fBloomFilter) return a.fBloomFilter;
return a.m_connected > b.m_connected;
}
@@ -921,7 +910,7 @@ static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEviction
// Pick out the potential block-relay only peers, and sort them by last block time.
static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
{
- if (a.fRelayTxes != b.fRelayTxes) return a.fRelayTxes;
+ if (a.m_relay_txs != b.m_relay_txs) return a.m_relay_txs;
if (a.m_last_block_time != b.m_last_block_time) return a.m_last_block_time < b.m_last_block_time;
if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices;
return a.m_connected > b.m_connected;
@@ -1046,7 +1035,7 @@ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& evicti
EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
// Protect up to 8 non-tx-relay peers that have sent us novel blocks.
EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8,
- [](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; });
+ [](const NodeEvictionCandidate& n) { return !n.m_relay_txs && n.fRelevantServices; });
// Protect 4 nodes that most recently sent us novel blocks.
// An attacker cannot manipulate this metric without performing useful work.
@@ -1112,18 +1101,11 @@ bool CConnman::AttemptToEvictConnection()
continue;
if (node->fDisconnect)
continue;
- bool peer_relay_txes = false;
- bool peer_filter_not_null = false;
- if (node->m_tx_relay != nullptr) {
- LOCK(node->m_tx_relay->cs_filter);
- peer_relay_txes = node->m_tx_relay->fRelayTxes;
- peer_filter_not_null = node->m_tx_relay->pfilter != nullptr;
- }
NodeEvictionCandidate candidate = {node->GetId(), node->m_connected, node->m_min_ping_time,
node->m_last_block_time, node->m_last_tx_time,
HasAllDesirableServiceFlags(node->nServices),
- peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup,
- node->m_prefer_evict, node->addr.IsLocal(),
+ node->m_relays_txs.load(), node->m_bloom_filter_loaded.load(),
+ node->nKeyedNetGroup, node->m_prefer_evict, node->addr.IsLocal(),
node->ConnectedThroughNetwork()};
vEvictionCandidates.push_back(candidate);
}
@@ -2800,7 +2782,7 @@ std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addres
auto r = m_addr_response_caches.emplace(cache_id, CachedAddrResponse{});
CachedAddrResponse& cache_entry = r.first->second;
if (cache_entry.m_cache_entry_expiration < current_time) { // If emplace() added new one it has expiration 0.
- cache_entry.m_addrs_response_cache = GetAddresses(max_addresses, max_pct, /* network */ std::nullopt);
+ cache_entry.m_addrs_response_cache = GetAddresses(max_addresses, max_pct, /*network=*/std::nullopt);
// Choosing a proper cache lifetime is a trade-off between the privacy leak minimization
// and the usefulness of ADDR responses to honest users.
//
@@ -3031,9 +3013,6 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, std::shared_ptr<Sock> s
nLocalServices(nLocalServicesIn)
{
if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND);
- if (conn_type_in != ConnectionType::BLOCK_RELAY) {
- m_tx_relay = std::make_unique<TxRelay>();
- }
for (const std::string &msg : getAllNetMessageTypes())
mapRecvBytesPerMsgCmd[msg] = 0;
diff --git a/src/net.h b/src/net.h
index a38310938b..1bc011c748 100644
--- a/src/net.h
+++ b/src/net.h
@@ -13,6 +13,7 @@
#include <crypto/siphash.h>
#include <hash.h>
#include <i2p.h>
+#include <logging.h>
#include <net_permissions.h>
#include <netaddress.h>
#include <netbase.h>
@@ -32,6 +33,7 @@
#include <cstdint>
#include <deque>
#include <functional>
+#include <list>
#include <map>
#include <memory>
#include <optional>
@@ -99,15 +101,22 @@ struct AddedNodeInfo
class CNodeStats;
class CClientUIInterface;
-struct CSerializedNetMsg
-{
+struct CSerializedNetMsg {
CSerializedNetMsg() = default;
CSerializedNetMsg(CSerializedNetMsg&&) = default;
CSerializedNetMsg& operator=(CSerializedNetMsg&&) = default;
- // No copying, only moves.
+ // No implicit copying, only moves.
CSerializedNetMsg(const CSerializedNetMsg& msg) = delete;
CSerializedNetMsg& operator=(const CSerializedNetMsg&) = delete;
+ CSerializedNetMsg Copy() const
+ {
+ CSerializedNetMsg copy;
+ copy.data = data;
+ copy.m_type = m_type;
+ return copy;
+ }
+
std::vector<unsigned char> data;
std::string m_type;
};
@@ -250,7 +259,6 @@ class CNodeStats
public:
NodeId nodeid;
ServiceFlags nServices;
- bool fRelayTxes;
std::chrono::seconds m_last_send;
std::chrono::seconds m_last_recv;
std::chrono::seconds m_last_tx_time;
@@ -271,7 +279,6 @@ public:
NetPermissionFlags m_permissionFlags;
std::chrono::microseconds m_last_ping_time;
std::chrono::microseconds m_min_ping_time;
- CAmount minFeeFilter;
// Our address, as reported by the peer
std::string addrLocal;
// Address of this peer
@@ -548,34 +555,15 @@ public:
// Peer selected us as (compact blocks) high-bandwidth peer (BIP152)
std::atomic<bool> m_bip152_highbandwidth_from{false};
- struct TxRelay {
- mutable RecursiveMutex cs_filter;
- // We use fRelayTxes for two purposes -
- // a) it allows us to not relay tx invs before receiving the peer's version message
- // b) the peer may tell us in its version message that we should not relay tx invs
- // unless it loads a bloom filter.
- bool fRelayTxes GUARDED_BY(cs_filter){false};
- std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter) GUARDED_BY(cs_filter){nullptr};
-
- mutable RecursiveMutex cs_tx_inventory;
- CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_tx_inventory){50000, 0.000001};
- // Set of transaction ids we still have to announce.
- // They are sorted by the mempool before relay, so the order is not important.
- std::set<uint256> setInventoryTxToSend;
- // Used for BIP35 mempool sending
- bool fSendMempool GUARDED_BY(cs_tx_inventory){false};
- // Last time a "MEMPOOL" request was serviced.
- std::atomic<std::chrono::seconds> m_last_mempool_req{0s};
- std::chrono::microseconds nNextInvSend{0};
-
- /** Minimum fee rate with which to filter inv's to this node */
- std::atomic<CAmount> minFeeFilter{0};
- CAmount lastSentFeeFilter{0};
- std::chrono::microseconds m_next_send_feefilter{0};
- };
+ /** Whether we should relay transactions to this peer (their version
+ * message did not include fRelay=false and this is not a block-relay-only
+ * connection). This only changes from false to true. It will never change
+ * back to false. Used only in inbound eviction logic. */
+ std::atomic_bool m_relays_txs{false};
- // m_tx_relay == nullptr if we're not relaying transactions with this peer
- std::unique_ptr<TxRelay> m_tx_relay;
+ /** Whether this peer has loaded a bloom filter. Used only in inbound
+ * eviction logic. */
+ std::atomic_bool m_bloom_filter_loaded{false};
/** UNIX epoch time of the last block received from this peer that we had
* not yet seen (e.g. not already received from another peer), that passed
@@ -651,23 +639,6 @@ public:
nRefCount--;
}
- void AddKnownTx(const uint256& hash)
- {
- if (m_tx_relay != nullptr) {
- LOCK(m_tx_relay->cs_tx_inventory);
- m_tx_relay->filterInventoryKnown.insert(hash);
- }
- }
-
- void PushTxInventory(const uint256& hash)
- {
- if (m_tx_relay == nullptr) return;
- LOCK(m_tx_relay->cs_tx_inventory);
- if (!m_tx_relay->filterInventoryKnown.contains(hash)) {
- m_tx_relay->setInventoryTxToSend.insert(hash);
- }
- }
-
void CloseSocketDisconnect();
void CopyStats(CNodeStats& stats);
@@ -1301,7 +1272,7 @@ struct NodeEvictionCandidate
std::chrono::seconds m_last_block_time;
std::chrono::seconds m_last_tx_time;
bool fRelevantServices;
- bool fRelayTxes;
+ bool m_relay_txs;
bool fBloomFilter;
uint64_t nKeyedNetGroup;
bool prefer_evict;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 59cd83e493..ba72a11ec9 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -41,6 +41,7 @@
#include <algorithm>
#include <atomic>
#include <chrono>
+#include <future>
#include <memory>
#include <optional>
#include <typeinfo>
@@ -232,6 +233,39 @@ struct Peer {
/** Whether a ping has been requested by the user */
std::atomic<bool> m_ping_queued{false};
+ /** Whether this peer relays txs via wtxid */
+ std::atomic<bool> m_wtxid_relay{false};
+
+ struct TxRelay {
+ mutable RecursiveMutex m_bloom_filter_mutex;
+ // We use m_relay_txs for two purposes -
+ // a) it allows us to not relay tx invs before receiving the peer's version message
+ // b) the peer may tell us in its version message that we should not relay tx invs
+ // unless it loads a bloom filter.
+ bool m_relay_txs GUARDED_BY(m_bloom_filter_mutex){false};
+ std::unique_ptr<CBloomFilter> m_bloom_filter PT_GUARDED_BY(m_bloom_filter_mutex) GUARDED_BY(m_bloom_filter_mutex){nullptr};
+
+ mutable RecursiveMutex m_tx_inventory_mutex;
+ CRollingBloomFilter m_tx_inventory_known_filter GUARDED_BY(m_tx_inventory_mutex){50000, 0.000001};
+ // Set of transaction ids we still have to announce.
+ // They are sorted by the mempool before relay, so the order is not important.
+ std::set<uint256> m_tx_inventory_to_send;
+ // Used for BIP35 mempool sending
+ bool m_send_mempool GUARDED_BY(m_tx_inventory_mutex){false};
+ // Last time a "MEMPOOL" request was serviced.
+ std::atomic<std::chrono::seconds> m_last_mempool_req{0s};
+ std::chrono::microseconds m_next_inv_send_time{0};
+
+ /** Minimum fee rate with which to filter inv's to this node */
+ std::atomic<CAmount> m_fee_filter_received{0};
+ CAmount m_fee_filter_sent{0};
+ std::chrono::microseconds m_next_send_feefilter{0};
+ };
+
+ /** Transaction relay data. Will be a nullptr if we're not relaying
+ * transactions with this peer (e.g. if it's a block-relay-only peer) */
+ std::unique_ptr<TxRelay> m_tx_relay;
+
/** A vector of addresses to send to the peer, limited to MAX_ADDR_TO_SEND. */
std::vector<CAddress> m_addrs_to_send;
/** Probabilistic filter to track recent addr messages relayed with this
@@ -290,8 +324,9 @@ struct Peer {
/** Work queue of items requested by this peer **/
std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex);
- explicit Peer(NodeId id)
+ explicit Peer(NodeId id, bool tx_relay)
: m_id(id)
+ , m_tx_relay(tx_relay ? std::make_unique<TxRelay>() : nullptr)
{}
};
@@ -331,9 +366,6 @@ public:
const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override;
private:
- void _RelayTransaction(const uint256& txid, const uint256& wtxid)
- EXCLUSIVE_LOCKS_REQUIRED(cs_main);
-
/** Consider evicting an outbound peer based on the amount of time they've been behind our tip */
void ConsiderEviction(CNode& pto, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -394,7 +426,7 @@ private:
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Send a version message to a peer */
- void PushNodeVersion(CNode& pnode);
+ void PushNodeVersion(CNode& pnode, const Peer& peer);
/** Send a ping message every PING_INTERVAL or if requested via RPC. May
* mark the peer to be disconnected if a ping has timed out.
@@ -415,7 +447,7 @@ private:
void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable);
/** Send `feefilter` message. */
- void MaybeSendFeefilter(CNode& node, std::chrono::microseconds current_time);
+ void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time);
const CChainParams& m_chainparams;
CConnman& m_connman;
@@ -464,7 +496,7 @@ private:
std::map<uint256, std::pair<NodeId, bool>> mapBlockSource GUARDED_BY(cs_main);
/** Number of peers with wtxid relay. */
- int m_wtxid_relay_peers GUARDED_BY(cs_main) = 0;
+ std::atomic<int> m_wtxid_relay_peers{0};
/** Number of outbound peers with m_chain_sync.m_protect. */
int m_outbound_peers_with_protect_from_disconnect GUARDED_BY(cs_main) = 0;
@@ -779,9 +811,6 @@ struct CNodeState {
//! A rolling bloom filter of all announced tx CInvs to this peer.
CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001};
- //! Whether this peer relays txs via wtxid
- bool m_wtxid_relay{false};
-
CNodeState(bool is_inbound) : m_is_inbound(is_inbound) {}
};
@@ -826,6 +855,14 @@ static void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& ins
}
}
+static void AddKnownTx(Peer& peer, const uint256& hash)
+{
+ if (peer.m_tx_relay != nullptr) {
+ LOCK(peer.m_tx_relay->m_tx_inventory_mutex);
+ peer.m_tx_relay->m_tx_inventory_known_filter.insert(hash);
+ }
+}
+
static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
nPreferredDownload -= state->fPreferredDownload;
@@ -1121,7 +1158,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count
} // namespace
-void PeerManagerImpl::PushNodeVersion(CNode& pnode)
+void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer)
{
// Note that pnode->GetLocalServices() is a reflection of the local
// services we were offering when the CNode object was created for this
@@ -1136,7 +1173,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode)
CService addr_you = addr.IsRoutable() && !IsProxy(addr) && addr.IsAddrV1Compatible() ? addr : CService();
uint64_t your_services{addr.nServices};
- const bool tx_relay = !m_ignore_incoming_txs && pnode.m_tx_relay != nullptr && !pnode.IsFeelerConn();
+ const bool tx_relay = !m_ignore_incoming_txs && peer.m_tx_relay != nullptr && !pnode.IsFeelerConn();
m_connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, my_services, nTime,
your_services, addr_you, // Together the pre-version-31402 serialization of CAddress "addrYou" (without nTime)
my_services, CService(), // Together the pre-version-31402 serialization of CAddress "addrMe" (without nTime)
@@ -1193,13 +1230,13 @@ void PeerManagerImpl::InitializeNode(CNode *pnode)
mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(pnode->IsInboundConn()));
assert(m_txrequest.Count(nodeid) == 0);
}
+ PeerRef peer = std::make_shared<Peer>(nodeid, /*tx_relay=*/ !pnode->IsBlockOnlyConn());
{
- PeerRef peer = std::make_shared<Peer>(nodeid);
LOCK(m_peer_mutex);
- m_peer_map.emplace_hint(m_peer_map.end(), nodeid, std::move(peer));
+ m_peer_map.emplace_hint(m_peer_map.end(), nodeid, peer);
}
if (!pnode->IsInboundConn()) {
- PushNodeVersion(*pnode);
+ PushNodeVersion(*pnode, *peer);
}
}
@@ -1211,8 +1248,7 @@ void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler)
CTransactionRef tx = m_mempool.get(txid);
if (tx != nullptr) {
- LOCK(cs_main);
- _RelayTransaction(txid, tx->GetWitnessHash());
+ RelayTransaction(txid, tx->GetWitnessHash());
} else {
m_mempool.RemoveUnbroadcastTx(txid, true);
}
@@ -1239,6 +1275,8 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
PeerRef peer = RemovePeer(nodeid);
assert(peer != nullptr);
misbehavior = WITH_LOCK(peer->m_misbehavior_mutex, return peer->m_misbehavior_score);
+ m_wtxid_relay_peers -= peer->m_wtxid_relay;
+ assert(m_wtxid_relay_peers >= 0);
}
CNodeState *state = State(nodeid);
assert(state != nullptr);
@@ -1256,8 +1294,6 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
assert(m_peers_downloading_from >= 0);
m_outbound_peers_with_protect_from_disconnect -= state->m_chain_sync.m_protect;
assert(m_outbound_peers_with_protect_from_disconnect >= 0);
- m_wtxid_relay_peers -= state->m_wtxid_relay;
- assert(m_wtxid_relay_peers >= 0);
mapNodeState.erase(nodeid);
@@ -1330,6 +1366,14 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c
ping_wait = GetTime<std::chrono::microseconds>() - peer->m_ping_start.load();
}
+ if (peer->m_tx_relay != nullptr) {
+ stats.m_relay_txs = WITH_LOCK(peer->m_tx_relay->m_bloom_filter_mutex, return peer->m_tx_relay->m_relay_txs);
+ stats.m_fee_filter_received = peer->m_tx_relay->m_fee_filter_received.load();
+ } else {
+ stats.m_relay_txs = false;
+ stats.m_fee_filter_received = 0;
+ }
+
stats.m_ping_wait = ping_wait;
stats.m_addr_processed = peer->m_addr_processed.load();
stats.m_addr_rate_limited = peer->m_addr_rate_limited.load();
@@ -1596,6 +1640,8 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
bool fWitnessEnabled = DeploymentActiveAt(*pindex, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT);
uint256 hashBlock(pblock->GetHash());
+ const std::shared_future<CSerializedNetMsg> lazy_ser{
+ std::async(std::launch::deferred, [&] { return msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock); })};
{
LOCK(cs_most_recent_block);
@@ -1605,10 +1651,9 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
fWitnessesPresentInMostRecentCompactBlock = fWitnessEnabled;
}
- m_connman.ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
+ m_connman.ForEachNode([this, pindex, fWitnessEnabled, &lazy_ser, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
- // TODO: Avoid the repeated-serialization here
if (pnode->GetCommonVersion() < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect)
return;
ProcessBlockAvailability(pnode->GetId());
@@ -1620,7 +1665,9 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerManager::NewPoWValidBlock",
hashBlock.ToString(), pnode->GetId());
- m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock));
+
+ const CSerializedNetMsg& ser_cmpctblock{lazy_ser.get()};
+ m_connman.PushMessage(pnode, ser_cmpctblock.Copy());
state.pindexBestHeaderSent = pindex;
}
});
@@ -1742,22 +1789,17 @@ void PeerManagerImpl::SendPings()
void PeerManagerImpl::RelayTransaction(const uint256& txid, const uint256& wtxid)
{
- WITH_LOCK(cs_main, _RelayTransaction(txid, wtxid););
-}
-
-void PeerManagerImpl::_RelayTransaction(const uint256& txid, const uint256& wtxid)
-{
- m_connman.ForEachNode([&txid, &wtxid](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
- AssertLockHeld(::cs_main);
+ LOCK(m_peer_mutex);
+ for(auto& it : m_peer_map) {
+ Peer& peer = *it.second;
+ if (!peer.m_tx_relay) continue;
- CNodeState* state = State(pnode->GetId());
- if (state == nullptr) return;
- if (state->m_wtxid_relay) {
- pnode->PushTxInventory(wtxid);
- } else {
- pnode->PushTxInventory(txid);
+ const uint256& hash{peer.m_wtxid_relay ? wtxid : txid};
+ LOCK(peer.m_tx_relay->m_tx_inventory_mutex);
+ if (!peer.m_tx_relay->m_tx_inventory_known_filter.contains(hash)) {
+ peer.m_tx_relay->m_tx_inventory_to_send.insert(hash);
}
- });
+ };
}
void PeerManagerImpl::RelayAddress(NodeId originator,
@@ -1903,11 +1945,11 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv&
} else if (inv.IsMsgFilteredBlk()) {
bool sendMerkleBlock = false;
CMerkleBlock merkleBlock;
- if (pfrom.m_tx_relay != nullptr) {
- LOCK(pfrom.m_tx_relay->cs_filter);
- if (pfrom.m_tx_relay->pfilter) {
+ if (peer.m_tx_relay != nullptr) {
+ LOCK(peer.m_tx_relay->m_bloom_filter_mutex);
+ if (peer.m_tx_relay->m_bloom_filter) {
sendMerkleBlock = true;
- merkleBlock = CMerkleBlock(*pblock, *pfrom.m_tx_relay->pfilter);
+ merkleBlock = CMerkleBlock(*pblock, *peer.m_tx_relay->m_bloom_filter);
}
}
if (sendMerkleBlock) {
@@ -1996,7 +2038,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic
const auto now{GetTime<std::chrono::seconds>()};
// Get last mempool request time
- const auto mempool_req = pfrom.m_tx_relay != nullptr ? pfrom.m_tx_relay->m_last_mempool_req.load() : std::chrono::seconds::min();
+ const auto mempool_req = peer.m_tx_relay != nullptr ? peer.m_tx_relay->m_last_mempool_req.load() : std::chrono::seconds::min();
// Process as many TX items from the front of the getdata queue as
// possible, since they're common and it's efficient to batch process
@@ -2009,7 +2051,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic
const CInv &inv = *it++;
- if (pfrom.m_tx_relay == nullptr) {
+ if (peer.m_tx_relay == nullptr) {
// Ignore GETDATA requests for transactions from blocks-only peers.
continue;
}
@@ -2037,7 +2079,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic
}
for (const uint256& parent_txid : parent_ids_to_add) {
// Relaying a transaction with a recent but unconfirmed parent.
- if (WITH_LOCK(pfrom.m_tx_relay->cs_tx_inventory, return !pfrom.m_tx_relay->filterInventoryKnown.contains(parent_txid))) {
+ if (WITH_LOCK(peer.m_tx_relay->m_tx_inventory_mutex, return !peer.m_tx_relay->m_tx_inventory_known_filter.contains(parent_txid))) {
LOCK(cs_main);
State(pfrom.GetId())->m_recently_announced_invs.insert(parent_txid);
}
@@ -2317,7 +2359,7 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
- _RelayTransaction(orphanHash, porphanTx->GetWitnessHash());
+ RelayTransaction(orphanHash, porphanTx->GetWitnessHash());
m_orphanage.AddChildrenToWorkSet(*porphanTx, orphan_work_set);
m_orphanage.EraseTx(orphanHash);
for (const CTransactionRef& removedTx : result.m_replaced_transactions.value()) {
@@ -2633,7 +2675,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Inbound peers send us their version message when they connect.
// We send our version message in response.
if (pfrom.IsInboundConn()) {
- PushNodeVersion(pfrom);
+ PushNodeVersion(pfrom, *peer);
}
// Change version
@@ -2672,9 +2714,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// set nodes not capable of serving the complete blockchain history as "limited nodes"
pfrom.m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED));
- if (pfrom.m_tx_relay != nullptr) {
- LOCK(pfrom.m_tx_relay->cs_filter);
- pfrom.m_tx_relay->fRelayTxes = fRelay; // set to true after we get the first filter* message
+ if (peer->m_tx_relay != nullptr) {
+ {
+ LOCK(peer->m_tx_relay->m_bloom_filter_mutex);
+ peer->m_tx_relay->m_relay_txs = fRelay; // set to true after we get the first filter* message
+ }
+ if (fRelay) pfrom.m_relays_txs = true;
}
if((nServices & NODE_WITNESS))
@@ -2863,9 +2908,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
if (pfrom.GetCommonVersion() >= WTXID_RELAY_VERSION) {
- LOCK(cs_main);
- if (!State(pfrom.GetId())->m_wtxid_relay) {
- State(pfrom.GetId())->m_wtxid_relay = true;
+ if (!peer->m_wtxid_relay) {
+ peer->m_wtxid_relay = true;
m_wtxid_relay_peers++;
} else {
LogPrint(BCLog::NET, "ignoring duplicate wtxidrelay from peer=%d\n", pfrom.GetId());
@@ -3001,7 +3045,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Reject tx INVs when the -blocksonly setting is enabled, or this is a
// block-relay-only peer
- bool reject_tx_invs{m_ignore_incoming_txs || (pfrom.m_tx_relay == nullptr)};
+ bool reject_tx_invs{m_ignore_incoming_txs || (peer->m_tx_relay == nullptr)};
// Allow peers with relay permission to send data other than blocks in blocks only mode
if (pfrom.HasPermission(NetPermissionFlags::Relay)) {
@@ -3019,7 +3063,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Ignore INVs that don't match wtxidrelay setting.
// Note that orphan parent fetching always uses MSG_TX GETDATAs regardless of the wtxidrelay setting.
// This is fine as no INV messages are involved in that process.
- if (State(pfrom.GetId())->m_wtxid_relay) {
+ if (peer->m_wtxid_relay) {
if (inv.IsMsgTx()) continue;
} else {
if (inv.IsMsgWtx()) continue;
@@ -3048,7 +3092,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
const bool fAlreadyHave = AlreadyHaveTx(gtxid);
LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
- pfrom.AddKnownTx(inv.hash);
+ AddKnownTx(*peer, inv.hash);
if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
AddTxAnnouncement(pfrom, gtxid, current_time);
}
@@ -3278,8 +3322,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Stop processing the transaction early if
// 1) We are in blocks only mode and peer has no relay permission
// 2) This peer is a block-relay-only peer
- if ((m_ignore_incoming_txs && !pfrom.HasPermission(NetPermissionFlags::Relay)) || (pfrom.m_tx_relay == nullptr))
- {
+ if ((m_ignore_incoming_txs && !pfrom.HasPermission(NetPermissionFlags::Relay)) || (peer->m_tx_relay == nullptr)) {
LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
@@ -3297,21 +3340,19 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
const uint256& txid = ptx->GetHash();
const uint256& wtxid = ptx->GetWitnessHash();
- LOCK2(cs_main, g_cs_orphans);
-
- CNodeState* nodestate = State(pfrom.GetId());
-
- const uint256& hash = nodestate->m_wtxid_relay ? wtxid : txid;
- pfrom.AddKnownTx(hash);
- if (nodestate->m_wtxid_relay && txid != wtxid) {
- // Insert txid into filterInventoryKnown, even for
+ const uint256& hash = peer->m_wtxid_relay ? wtxid : txid;
+ AddKnownTx(*peer, hash);
+ if (peer->m_wtxid_relay && txid != wtxid) {
+ // Insert txid into m_tx_inventory_known_filter, even for
// wtxidrelay peers. This prevents re-adding of
// unconfirmed parents to the recently_announced
// filter, when a child tx is requested. See
// ProcessGetData().
- pfrom.AddKnownTx(txid);
+ AddKnownTx(*peer, txid);
}
+ LOCK2(cs_main, g_cs_orphans);
+
m_txrequest.ReceivedResponse(pfrom.GetId(), txid);
if (tx.HasWitness()) m_txrequest.ReceivedResponse(pfrom.GetId(), wtxid);
@@ -3336,7 +3377,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
LogPrintf("Not relaying non-mempool transaction %s from forcerelay peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
} else {
LogPrintf("Force relaying tx %s from peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
- _RelayTransaction(tx.GetHash(), tx.GetWitnessHash());
+ RelayTransaction(tx.GetHash(), tx.GetWitnessHash());
}
}
return;
@@ -3350,7 +3391,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// requests for it.
m_txrequest.ForgetTxHash(tx.GetHash());
m_txrequest.ForgetTxHash(tx.GetWitnessHash());
- _RelayTransaction(tx.GetHash(), tx.GetWitnessHash());
+ RelayTransaction(tx.GetHash(), tx.GetWitnessHash());
m_orphanage.AddChildrenToWorkSet(tx, peer->m_orphan_work_set);
pfrom.m_last_tx_time = GetTime<std::chrono::seconds>();
@@ -3397,7 +3438,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Eventually we should replace this with an improved
// protocol for getting all unconfirmed parents.
const auto gtxid{GenTxid::Txid(parent_txid)};
- pfrom.AddKnownTx(parent_txid);
+ AddKnownTx(*peer, parent_txid);
if (!AlreadyHaveTx(gtxid)) AddTxAnnouncement(pfrom, gtxid, current_time);
}
@@ -3521,7 +3562,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
BlockValidationState state;
if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, m_chainparams, &pindex)) {
if (state.IsInvalid()) {
- MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock");
+ MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block=*/true, "invalid header via cmpctblock");
return;
}
}
@@ -3861,7 +3902,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
peer->m_addrs_to_send.clear();
std::vector<CAddress> vAddr;
if (pfrom.HasPermission(NetPermissionFlags::Addr)) {
- vAddr = m_connman.GetAddresses(MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND, /* network */ std::nullopt);
+ vAddr = m_connman.GetAddresses(MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND, /*network=*/std::nullopt);
} else {
vAddr = m_connman.GetAddresses(pfrom, MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND);
}
@@ -3893,9 +3934,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
- if (pfrom.m_tx_relay != nullptr) {
- LOCK(pfrom.m_tx_relay->cs_tx_inventory);
- pfrom.m_tx_relay->fSendMempool = true;
+ if (peer->m_tx_relay != nullptr) {
+ LOCK(peer->m_tx_relay->m_tx_inventory_mutex);
+ peer->m_tx_relay->m_send_mempool = true;
}
return;
}
@@ -3989,11 +4030,15 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// There is no excuse for sending a too-large filter
Misbehaving(pfrom.GetId(), 100, "too-large bloom filter");
}
- else if (pfrom.m_tx_relay != nullptr)
+ else if (peer->m_tx_relay != nullptr)
{
- LOCK(pfrom.m_tx_relay->cs_filter);
- pfrom.m_tx_relay->pfilter.reset(new CBloomFilter(filter));
- pfrom.m_tx_relay->fRelayTxes = true;
+ {
+ LOCK(peer->m_tx_relay->m_bloom_filter_mutex);
+ peer->m_tx_relay->m_bloom_filter.reset(new CBloomFilter(filter));
+ peer->m_tx_relay->m_relay_txs = true;
+ }
+ pfrom.m_bloom_filter_loaded = true;
+ pfrom.m_relays_txs = true;
}
return;
}
@@ -4012,10 +4057,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
bool bad = false;
if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) {
bad = true;
- } else if (pfrom.m_tx_relay != nullptr) {
- LOCK(pfrom.m_tx_relay->cs_filter);
- if (pfrom.m_tx_relay->pfilter) {
- pfrom.m_tx_relay->pfilter->insert(vData);
+ } else if (peer->m_tx_relay != nullptr) {
+ LOCK(peer->m_tx_relay->m_bloom_filter_mutex);
+ if (peer->m_tx_relay->m_bloom_filter) {
+ peer->m_tx_relay->m_bloom_filter->insert(vData);
} else {
bad = true;
}
@@ -4032,12 +4077,17 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
pfrom.fDisconnect = true;
return;
}
- if (pfrom.m_tx_relay == nullptr) {
+ if (peer->m_tx_relay == nullptr) {
return;
}
- LOCK(pfrom.m_tx_relay->cs_filter);
- pfrom.m_tx_relay->pfilter = nullptr;
- pfrom.m_tx_relay->fRelayTxes = true;
+
+ {
+ LOCK(peer->m_tx_relay->m_bloom_filter_mutex);
+ peer->m_tx_relay->m_bloom_filter = nullptr;
+ peer->m_tx_relay->m_relay_txs = true;
+ }
+ pfrom.m_bloom_filter_loaded = false;
+ pfrom.m_relays_txs = true;
return;
}
@@ -4045,8 +4095,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
CAmount newFeeFilter = 0;
vRecv >> newFeeFilter;
if (MoneyRange(newFeeFilter)) {
- if (pfrom.m_tx_relay != nullptr) {
- pfrom.m_tx_relay->minFeeFilter = newFeeFilter;
+ if (peer->m_tx_relay != nullptr) {
+ peer->m_tx_relay->m_fee_filter_received = newFeeFilter;
}
LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom.GetId());
}
@@ -4504,10 +4554,10 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
}
}
-void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, std::chrono::microseconds current_time)
+void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::microseconds current_time)
{
if (m_ignore_incoming_txs) return;
- if (!pto.m_tx_relay) return;
+ if (!peer.m_tx_relay) return;
if (pto.GetCommonVersion() < FEEFILTER_VERSION) return;
// peers with the forcerelay permission should not filter txs to us
if (pto.HasPermission(NetPermissionFlags::ForceRelay)) return;
@@ -4521,27 +4571,27 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, std::chrono::microseconds c
currentFilter = MAX_MONEY;
} else {
static const CAmount MAX_FILTER{g_filter_rounder.round(MAX_MONEY)};
- if (pto.m_tx_relay->lastSentFeeFilter == MAX_FILTER) {
+ if (peer.m_tx_relay->m_fee_filter_sent == MAX_FILTER) {
// Send the current filter if we sent MAX_FILTER previously
// and made it out of IBD.
- pto.m_tx_relay->m_next_send_feefilter = 0us;
+ peer.m_tx_relay->m_next_send_feefilter = 0us;
}
}
- if (current_time > pto.m_tx_relay->m_next_send_feefilter) {
+ if (current_time > peer.m_tx_relay->m_next_send_feefilter) {
CAmount filterToSend = g_filter_rounder.round(currentFilter);
// We always have a fee filter of at least minRelayTxFee
filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK());
- if (filterToSend != pto.m_tx_relay->lastSentFeeFilter) {
+ if (filterToSend != peer.m_tx_relay->m_fee_filter_sent) {
m_connman.PushMessage(&pto, CNetMsgMaker(pto.GetCommonVersion()).Make(NetMsgType::FEEFILTER, filterToSend));
- pto.m_tx_relay->lastSentFeeFilter = filterToSend;
+ peer.m_tx_relay->m_fee_filter_sent = filterToSend;
}
- pto.m_tx_relay->m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL);
+ peer.m_tx_relay->m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL);
}
// If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY
// until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY.
- else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < pto.m_tx_relay->m_next_send_feefilter &&
- (currentFilter < 3 * pto.m_tx_relay->lastSentFeeFilter / 4 || currentFilter > 4 * pto.m_tx_relay->lastSentFeeFilter / 3)) {
- pto.m_tx_relay->m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY);
+ else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < peer.m_tx_relay->m_next_send_feefilter &&
+ (currentFilter < 3 * peer.m_tx_relay->m_fee_filter_sent / 4 || currentFilter > 4 * peer.m_tx_relay->m_fee_filter_sent / 3)) {
+ peer.m_tx_relay->m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY);
}
}
@@ -4808,45 +4858,45 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
peer->m_blocks_for_inv_relay.clear();
}
- if (pto->m_tx_relay != nullptr) {
- LOCK(pto->m_tx_relay->cs_tx_inventory);
+ if (peer->m_tx_relay != nullptr) {
+ LOCK(peer->m_tx_relay->m_tx_inventory_mutex);
// Check whether periodic sends should happen
bool fSendTrickle = pto->HasPermission(NetPermissionFlags::NoBan);
- if (pto->m_tx_relay->nNextInvSend < current_time) {
+ if (peer->m_tx_relay->m_next_inv_send_time < current_time) {
fSendTrickle = true;
if (pto->IsInboundConn()) {
- pto->m_tx_relay->nNextInvSend = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL);
+ peer->m_tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL);
} else {
- pto->m_tx_relay->nNextInvSend = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL);
+ peer->m_tx_relay->m_next_inv_send_time = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL);
}
}
// Time to send but the peer has requested we not relay transactions.
if (fSendTrickle) {
- LOCK(pto->m_tx_relay->cs_filter);
- if (!pto->m_tx_relay->fRelayTxes) pto->m_tx_relay->setInventoryTxToSend.clear();
+ LOCK(peer->m_tx_relay->m_bloom_filter_mutex);
+ if (!peer->m_tx_relay->m_relay_txs) peer->m_tx_relay->m_tx_inventory_to_send.clear();
}
// Respond to BIP35 mempool requests
- if (fSendTrickle && pto->m_tx_relay->fSendMempool) {
+ if (fSendTrickle && peer->m_tx_relay->m_send_mempool) {
auto vtxinfo = m_mempool.infoAll();
- pto->m_tx_relay->fSendMempool = false;
- const CFeeRate filterrate{pto->m_tx_relay->minFeeFilter.load()};
+ peer->m_tx_relay->m_send_mempool = false;
+ const CFeeRate filterrate{peer->m_tx_relay->m_fee_filter_received.load()};
- LOCK(pto->m_tx_relay->cs_filter);
+ LOCK(peer->m_tx_relay->m_bloom_filter_mutex);
for (const auto& txinfo : vtxinfo) {
- const uint256& hash = state.m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash();
- CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
- pto->m_tx_relay->setInventoryTxToSend.erase(hash);
+ const uint256& hash = peer->m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash();
+ CInv inv(peer->m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
+ peer->m_tx_relay->m_tx_inventory_to_send.erase(hash);
// Don't send transactions that peers will not put into their mempool
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
continue;
}
- if (pto->m_tx_relay->pfilter) {
- if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
+ if (peer->m_tx_relay->m_bloom_filter) {
+ if (!peer->m_tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue;
}
- pto->m_tx_relay->filterInventoryKnown.insert(hash);
+ peer->m_tx_relay->m_tx_inventory_known_filter.insert(hash);
// Responses to MEMPOOL requests bypass the m_recently_announced_invs filter.
vInv.push_back(inv);
if (vInv.size() == MAX_INV_SZ) {
@@ -4854,37 +4904,37 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
vInv.clear();
}
}
- pto->m_tx_relay->m_last_mempool_req = std::chrono::duration_cast<std::chrono::seconds>(current_time);
+ peer->m_tx_relay->m_last_mempool_req = std::chrono::duration_cast<std::chrono::seconds>(current_time);
}
// Determine transactions to relay
if (fSendTrickle) {
// Produce a vector with all candidates for sending
std::vector<std::set<uint256>::iterator> vInvTx;
- vInvTx.reserve(pto->m_tx_relay->setInventoryTxToSend.size());
- for (std::set<uint256>::iterator it = pto->m_tx_relay->setInventoryTxToSend.begin(); it != pto->m_tx_relay->setInventoryTxToSend.end(); it++) {
+ vInvTx.reserve(peer->m_tx_relay->m_tx_inventory_to_send.size());
+ for (std::set<uint256>::iterator it = peer->m_tx_relay->m_tx_inventory_to_send.begin(); it != peer->m_tx_relay->m_tx_inventory_to_send.end(); it++) {
vInvTx.push_back(it);
}
- const CFeeRate filterrate{pto->m_tx_relay->minFeeFilter.load()};
+ const CFeeRate filterrate{peer->m_tx_relay->m_fee_filter_received.load()};
// Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
// A heap is used so that not all items need sorting if only a few are being sent.
- CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, state.m_wtxid_relay);
+ CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, peer->m_wtxid_relay);
std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
// No reason to drain out at many times the network's capacity,
// especially since we have many peers and some will draw much shorter delays.
unsigned int nRelayedTransactions = 0;
- LOCK(pto->m_tx_relay->cs_filter);
+ LOCK(peer->m_tx_relay->m_bloom_filter_mutex);
while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) {
// Fetch the top element from the heap
std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
std::set<uint256>::iterator it = vInvTx.back();
vInvTx.pop_back();
uint256 hash = *it;
- CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
+ CInv inv(peer->m_wtxid_relay ? MSG_WTX : MSG_TX, hash);
// Remove it from the to-be-sent set
- pto->m_tx_relay->setInventoryTxToSend.erase(it);
+ peer->m_tx_relay->m_tx_inventory_to_send.erase(it);
// Check if not in the filter already
- if (pto->m_tx_relay->filterInventoryKnown.contains(hash)) {
+ if (peer->m_tx_relay->m_tx_inventory_known_filter.contains(hash)) {
continue;
}
// Not in the mempool anymore? don't bother sending it.
@@ -4898,7 +4948,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
continue;
}
- if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
+ if (peer->m_tx_relay->m_bloom_filter && !peer->m_tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue;
// Send
State(pto->GetId())->m_recently_announced_invs.insert(hash);
vInv.push_back(inv);
@@ -4925,14 +4975,14 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
- pto->m_tx_relay->filterInventoryKnown.insert(hash);
+ peer->m_tx_relay->m_tx_inventory_known_filter.insert(hash);
if (hash != txid) {
- // Insert txid into filterInventoryKnown, even for
+ // Insert txid into m_tx_inventory_known_filter, even for
// wtxidrelay peers. This prevents re-adding of
// unconfirmed parents to the recently_announced
// filter, when a child tx is requested. See
// ProcessGetData().
- pto->m_tx_relay->filterInventoryKnown.insert(txid);
+ peer->m_tx_relay->m_tx_inventory_known_filter.insert(txid);
}
}
}
@@ -5053,6 +5103,6 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
if (!vGetData.empty())
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
} // release cs_main
- MaybeSendFeefilter(*pto, current_time);
+ MaybeSendFeefilter(*pto, *peer, current_time);
return true;
}
diff --git a/src/net_processing.h b/src/net_processing.h
index e30f9f516c..7dacaee5c1 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -29,6 +29,8 @@ struct CNodeStateStats {
int m_starting_height = -1;
std::chrono::microseconds m_ping_wait;
std::vector<int> vHeightInFlight;
+ bool m_relay_txs;
+ CAmount m_fee_filter_received;
uint64_t m_addr_processed = 0;
uint64_t m_addr_rate_limited = 0;
bool m_addr_relay_enabled{false};
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index 7392830261..763fd29744 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -28,10 +28,45 @@ bool fHavePruned = false;
bool fPruneMode = false;
uint64_t nPruneTarget = 0;
+bool CBlockIndexWorkComparator::operator()(const CBlockIndex* pa, const CBlockIndex* pb) const
+{
+ // First sort by most total work, ...
+ if (pa->nChainWork > pb->nChainWork) return false;
+ if (pa->nChainWork < pb->nChainWork) return true;
+
+ // ... then by earliest time received, ...
+ if (pa->nSequenceId < pb->nSequenceId) return false;
+ if (pa->nSequenceId > pb->nSequenceId) return true;
+
+ // Use pointer address as tie breaker (should only happen with blocks
+ // loaded from disk, as those all have id 0).
+ if (pa < pb) return false;
+ if (pa > pb) return true;
+
+ // Identical blocks.
+ return false;
+}
+
+bool CBlockIndexHeightOnlyComparator::operator()(const CBlockIndex* pa, const CBlockIndex* pb) const
+{
+ return pa->nHeight < pb->nHeight;
+}
+
static FILE* OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false);
static FlatFileSeq BlockFileSeq();
static FlatFileSeq UndoFileSeq();
+std::vector<CBlockIndex*> BlockManager::GetAllBlockIndices()
+{
+ AssertLockHeld(cs_main);
+ std::vector<CBlockIndex*> rv;
+ rv.reserve(m_block_index.size());
+ for (auto& [_, block_index] : m_block_index) {
+ rv.push_back(&block_index);
+ }
+ return rv;
+}
+
CBlockIndex* BlockManager::LookupBlockIndex(const uint256& hash)
{
AssertLockHeld(cs_main);
@@ -203,8 +238,7 @@ CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
return nullptr;
}
- // Return existing or create new
- auto [mi, inserted] = m_block_index.try_emplace(hash);
+ const auto [mi, inserted]{m_block_index.try_emplace(hash)};
CBlockIndex* pindex = &(*mi).second;
if (inserted) {
pindex->phashBlock = &((*mi).first);
@@ -212,46 +246,19 @@ CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
return pindex;
}
-bool BlockManager::LoadBlockIndex(
- const Consensus::Params& consensus_params,
- ChainstateManager& chainman)
+bool BlockManager::LoadBlockIndex(const Consensus::Params& consensus_params)
{
if (!m_block_tree_db->LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) {
return false;
}
// Calculate nChainWork
- std::vector<std::pair<int, CBlockIndex*>> vSortedByHeight;
- vSortedByHeight.reserve(m_block_index.size());
- for (auto& [_, block_index] : m_block_index) {
- CBlockIndex* pindex = &block_index;
- vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex));
- }
- sort(vSortedByHeight.begin(), vSortedByHeight.end());
-
- // Find start of assumed-valid region.
- int first_assumed_valid_height = std::numeric_limits<int>::max();
-
- for (const auto& [height, block] : vSortedByHeight) {
- if (block->IsAssumedValid()) {
- auto chainstates = chainman.GetAll();
-
- // If we encounter an assumed-valid block index entry, ensure that we have
- // one chainstate that tolerates assumed-valid entries and another that does
- // not (i.e. the background validation chainstate), since assumed-valid
- // entries should always be pending validation by a fully-validated chainstate.
- auto any_chain = [&](auto fnc) { return std::any_of(chainstates.cbegin(), chainstates.cend(), fnc); };
- assert(any_chain([](auto chainstate) { return chainstate->reliesOnAssumedValid(); }));
- assert(any_chain([](auto chainstate) { return !chainstate->reliesOnAssumedValid(); }));
-
- first_assumed_valid_height = height;
- break;
- }
- }
+ std::vector<CBlockIndex*> vSortedByHeight{GetAllBlockIndices()};
+ std::sort(vSortedByHeight.begin(), vSortedByHeight.end(),
+ CBlockIndexHeightOnlyComparator());
- for (const std::pair<int, CBlockIndex*>& item : vSortedByHeight) {
+ for (CBlockIndex* pindex : vSortedByHeight) {
if (ShutdownRequested()) return false;
- CBlockIndex* pindex = item.second;
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime);
@@ -275,43 +282,6 @@ bool BlockManager::LoadBlockIndex(
pindex->nStatus |= BLOCK_FAILED_CHILD;
m_dirty_blockindex.insert(pindex);
}
- if (pindex->IsAssumedValid() ||
- (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) &&
- (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) {
-
- // Fill each chainstate's block candidate set. Only add assumed-valid
- // blocks to the tip candidate set if the chainstate is allowed to rely on
- // assumed-valid blocks.
- //
- // If all setBlockIndexCandidates contained the assumed-valid blocks, the
- // background chainstate's ActivateBestChain() call would add assumed-valid
- // blocks to the chain (based on how FindMostWorkChain() works). Obviously
- // we don't want this since the purpose of the background validation chain
- // is to validate assued-valid blocks.
- //
- // Note: This is considering all blocks whose height is greater or equal to
- // the first assumed-valid block to be assumed-valid blocks, and excluding
- // them from the background chainstate's setBlockIndexCandidates set. This
- // does mean that some blocks which are not technically assumed-valid
- // (later blocks on a fork beginning before the first assumed-valid block)
- // might not get added to the background chainstate, but this is ok,
- // because they will still be attached to the active chainstate if they
- // actually contain more work.
- //
- // Instead of this height-based approach, an earlier attempt was made at
- // detecting "holistically" whether the block index under consideration
- // relied on an assumed-valid ancestor, but this proved to be too slow to
- // be practical.
- for (CChainState* chainstate : chainman.GetAll()) {
- if (chainstate->reliesOnAssumedValid() ||
- pindex->nHeight < first_assumed_valid_height) {
- chainstate->setBlockIndexCandidates.insert(pindex);
- }
- }
- }
- if (pindex->nStatus & BLOCK_FAILED_MASK && (!chainman.m_best_invalid || pindex->nChainWork > chainman.m_best_invalid->nChainWork)) {
- chainman.m_best_invalid = pindex;
- }
if (pindex->pprev) {
pindex->BuildSkip();
}
@@ -355,9 +325,9 @@ bool BlockManager::WriteBlockIndexDB()
return true;
}
-bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman)
+bool BlockManager::LoadBlockIndexDB()
{
- if (!LoadBlockIndex(::Params().GetConsensus(), chainman)) {
+ if (!LoadBlockIndex(::Params().GetConsensus())) {
return false;
}
@@ -382,9 +352,8 @@ bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman)
LogPrintf("Checking all blk files are present...\n");
std::set<int> setBlkDataFiles;
for (const auto& [_, block_index] : m_block_index) {
- const CBlockIndex* pindex = &block_index;
- if (pindex->nStatus & BLOCK_HAVE_DATA) {
- setBlkDataFiles.insert(pindex->nFile);
+ if (block_index.nStatus & BLOCK_HAVE_DATA) {
+ setBlkDataFiles.insert(block_index.nFile);
}
}
for (std::set<int>::iterator it = setBlkDataFiles.begin(); it != setBlkDataFiles.end(); it++) {
@@ -408,13 +377,13 @@ bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman)
return true;
}
-CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data)
+const CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data)
{
const MapCheckpoints& checkpoints = data.mapCheckpoints;
for (const MapCheckpoints::value_type& i : reverse_iterate(checkpoints)) {
const uint256& hash = i.second;
- CBlockIndex* pindex = LookupBlockIndex(hash);
+ const CBlockIndex* pindex = LookupBlockIndex(hash);
if (pindex) {
return pindex;
}
diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
index 12224f7a5d..a051e90808 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -62,6 +62,11 @@ struct CBlockIndexWorkComparator {
bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const;
};
+struct CBlockIndexHeightOnlyComparator {
+ /* Only compares the height of two block indices, doesn't try to tie-break */
+ bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const;
+};
+
/**
* Maintains a tree of blocks (stored in `m_block_index`) which is consulted
* to determine where the most-work tip is.
@@ -118,6 +123,8 @@ private:
public:
BlockMap m_block_index GUARDED_BY(cs_main);
+ std::vector<CBlockIndex*> GetAllBlockIndices() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
/**
* All pairs A->B, where A (or one of its ancestors) misses transactions, but B has transactions.
* Pruned nodes may have entries where B is missing data.
@@ -127,16 +134,15 @@ public:
std::unique_ptr<CBlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main);
bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
- bool LoadBlockIndexDB(ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ bool LoadBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* Load the blocktree off disk and into memory. Populate certain metadata
* per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral
* collections like m_dirty_blockindex.
*/
- bool LoadBlockIndex(
- const Consensus::Params& consensus_params,
- ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool LoadBlockIndex(const Consensus::Params& consensus_params)
+ EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Clear all data members. */
void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -163,7 +169,7 @@ public:
uint64_t CalculateCurrentUsage();
//! Returns last CBlockIndex* that is a checkpoint
- CBlockIndex* GetLastCheckpoint(const CCheckpointData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ const CBlockIndex* GetLastCheckpoint(const CCheckpointData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
~BlockManager()
{
diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp
index d03b9dcac6..9fdeb036fd 100644
--- a/src/node/chainstate.cpp
+++ b/src/node/chainstate.cpp
@@ -82,17 +82,17 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
for (CChainState* chainstate : chainman.GetAll()) {
chainstate->InitCoinsDB(
- /* cache_size_bytes */ nCoinDBCache,
- /* in_memory */ coins_db_in_memory,
- /* should_wipe */ fReset || fReindexChainState);
+ /*cache_size_bytes=*/nCoinDBCache,
+ /*in_memory=*/coins_db_in_memory,
+ /*should_wipe=*/fReset || fReindexChainState);
if (coins_error_cb) {
chainstate->CoinsErrorCatcher().AddReadErrCallback(coins_error_cb);
}
- // If necessary, upgrade from older database format.
+ // Refuse to load unsupported database format.
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
- if (!chainstate->CoinsDB().Upgrade()) {
+ if (chainstate->CoinsDB().NeedsUpgrade()) {
return ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED;
}
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index cb063ae9f8..73d15652b1 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -90,7 +90,7 @@ public:
uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
bool baseInitialize() override
{
- return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(gArgs) && AppInitSanityChecks() &&
+ return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(gArgs, /*use_syscall_sandbox=*/false) && AppInitSanityChecks() &&
AppInitLockDataDirectory() && AppInitInterfaces(*m_context);
}
bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override
@@ -490,7 +490,7 @@ public:
{
LOCK(cs_main);
const CChainState& active = Assert(m_node.chainman)->ActiveChainstate();
- if (CBlockIndex* fork = active.FindForkInGlobalIndex(locator)) {
+ if (const CBlockIndex* fork = active.FindForkInGlobalIndex(locator)) {
return fork->nHeight;
}
return std::nullopt;
@@ -557,7 +557,7 @@ public:
// used to limit the range, and passing min_height that's too low or
// max_height that's too high will not crash or change the result.
LOCK(::cs_main);
- if (CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) {
+ if (const CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) {
if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height);
for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) {
// Check pprev to not segfault if min_height is too low
@@ -590,7 +590,7 @@ public:
bool relay,
std::string& err_string) override
{
- const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback*/ false);
+ const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback=*/false);
// Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures.
// Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures
// that Chain clients do not need to know about.
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index 54afbb8839..be5d58527b 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -50,7 +50,7 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
tx.vout.erase(tx.vout.begin() + GetWitnessCommitmentIndex(block));
block.vtx.at(0) = MakeTransactionRef(tx);
- CBlockIndex* prev_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock));
+ const CBlockIndex* prev_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock));
GenerateCoinbaseCommitment(block, prev_block, Params().GetConsensus());
block.hashMerkleRoot = BlockMerkleRoot(block);
@@ -430,22 +430,4 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx);
}
}
-
-void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
-{
- // Update nExtraNonce
- static uint256 hashPrevBlock;
- if (hashPrevBlock != pblock->hashPrevBlock) {
- nExtraNonce = 0;
- hashPrevBlock = pblock->hashPrevBlock;
- }
- ++nExtraNonce;
- unsigned int nHeight = pindexPrev->nHeight + 1; // Height first in coinbase required for block.version=2
- CMutableTransaction txCoinbase(*pblock->vtx[0]);
- txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce));
- assert(txCoinbase.vin[0].scriptSig.size() <= 100);
-
- pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
- pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
-}
} // namespace node
diff --git a/src/node/miner.h b/src/node/miner.h
index 97c55f2864..c8093ec883 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -45,7 +45,7 @@ struct CTxMemPoolModifiedEntry {
nSigOpCostWithAncestors = entry->GetSigOpCostWithAncestors();
}
- int64_t GetModifiedFee() const { return iter->GetModifiedFee(); }
+ CAmount GetModifiedFee() const { return iter->GetModifiedFee(); }
uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; }
CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; }
size_t GetTxSize() const { return iter->GetTxSize(); }
@@ -200,8 +200,6 @@ private:
int UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set& mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs);
};
-/** Modify the extranonce in a block */
-void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce);
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev);
/** Update an old GenerateCoinbaseCommitment from CreateNewBlock after the block txs have changed */
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 7c22880dd1..85e3c23085 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -46,6 +46,7 @@
#include <QCursor>
#include <QDateTime>
#include <QDragEnterEvent>
+#include <QKeySequence>
#include <QListWidget>
#include <QMenu>
#include <QMenuBar>
@@ -251,28 +252,28 @@ void BitcoinGUI::createActions()
overviewAction->setStatusTip(tr("Show general overview of wallet"));
overviewAction->setToolTip(overviewAction->statusTip());
overviewAction->setCheckable(true);
- overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1));
+ overviewAction->setShortcut(QKeySequence(QStringLiteral("Alt+1")));
tabGroup->addAction(overviewAction);
sendCoinsAction = new QAction(platformStyle->SingleColorIcon(":/icons/send"), tr("&Send"), this);
sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address"));
sendCoinsAction->setToolTip(sendCoinsAction->statusTip());
sendCoinsAction->setCheckable(true);
- sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2));
+ sendCoinsAction->setShortcut(QKeySequence(QStringLiteral("Alt+2")));
tabGroup->addAction(sendCoinsAction);
receiveCoinsAction = new QAction(platformStyle->SingleColorIcon(":/icons/receiving_addresses"), tr("&Receive"), this);
receiveCoinsAction->setStatusTip(tr("Request payments (generates QR codes and bitcoin: URIs)"));
receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip());
receiveCoinsAction->setCheckable(true);
- receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
+ receiveCoinsAction->setShortcut(QKeySequence(QStringLiteral("Alt+3")));
tabGroup->addAction(receiveCoinsAction);
historyAction = new QAction(platformStyle->SingleColorIcon(":/icons/history"), tr("&Transactions"), this);
historyAction->setStatusTip(tr("Browse transaction history"));
historyAction->setToolTip(historyAction->statusTip());
historyAction->setCheckable(true);
- historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4));
+ historyAction->setShortcut(QKeySequence(QStringLiteral("Alt+4")));
tabGroup->addAction(historyAction);
#ifdef ENABLE_WALLET
@@ -290,7 +291,7 @@ void BitcoinGUI::createActions()
quitAction = new QAction(tr("E&xit"), this);
quitAction->setStatusTip(tr("Quit application"));
- quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
+ quitAction->setShortcut(QKeySequence(tr("Ctrl+Q")));
quitAction->setMenuRole(QAction::QuitRole);
aboutAction = new QAction(tr("&About %1").arg(PACKAGE_NAME), this);
aboutAction->setStatusTip(tr("Show information about %1").arg(PACKAGE_NAME));
@@ -472,7 +473,7 @@ void BitcoinGUI::createMenuBar()
QMenu* window_menu = appMenuBar->addMenu(tr("&Window"));
QAction* minimize_action = window_menu->addAction(tr("&Minimize"));
- minimize_action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M));
+ minimize_action->setShortcut(QKeySequence(tr("Ctrl+M")));
connect(minimize_action, &QAction::triggered, [] {
QApplication::activeWindow()->showMinimized();
});
@@ -852,7 +853,7 @@ void BitcoinGUI::aboutClicked()
if(!clientModel)
return;
- auto dlg = new HelpMessageDialog(this, /* about */ true);
+ auto dlg = new HelpMessageDialog(this, /*about=*/true);
GUIUtil::ShowModalDialogAsynchronously(dlg);
}
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index 0ae5f7331e..d2b29ba27b 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -303,7 +303,7 @@ public Q_SLOTS:
/** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */
void showNormalIfMinimized() { showNormalIfMinimized(false); }
void showNormalIfMinimized(bool fToggleHidden);
- /** Simply calls showNormalIfMinimized(true) for use in SLOT() macro */
+ /** Simply calls showNormalIfMinimized(true) */
void toggleHidden();
/** called by a timer to check if ShutdownRequested() has been set **/
diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp
index d7a2aaaf19..d3103492a4 100644
--- a/src/qt/coincontroldialog.cpp
+++ b/src/qt/coincontroldialog.cpp
@@ -16,10 +16,11 @@
#include <qt/platformstyle.h>
#include <qt/walletmodel.h>
-#include <wallet/coincontrol.h>
#include <interfaces/node.h>
#include <key_io.h>
#include <policy/policy.h>
+#include <wallet/coincontrol.h>
+#include <wallet/coinselection.h>
#include <wallet/wallet.h>
#include <QApplication>
@@ -32,7 +33,6 @@
#include <QTreeWidget>
using wallet::CCoinControl;
-using wallet::MIN_CHANGE;
QList<CAmount> CoinControlDialog::payAmounts;
bool CoinControlDialog::fSubtractFeeFromAmount = false;
@@ -485,11 +485,10 @@ void CoinControlDialog::updateLabels(CCoinControl& m_coin_control, WalletModel *
if (!CoinControlDialog::fSubtractFeeFromAmount)
nChange -= nPayFee;
- // Never create dust outputs; if we would, just add the dust to the fee.
- if (nChange > 0 && nChange < MIN_CHANGE)
- {
+ if (nChange > 0) {
// Assumes a p2pkh script size
CTxOut txout(nChange, CScript() << std::vector<unsigned char>(24, 0));
+ // Never create dust outputs; if we would, just add the dust to the fee.
if (IsDust(txout, model->node().getDustRelayFee()))
{
nPayFee += nChange;
diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui
index 2196801023..ead977296a 100644
--- a/src/qt/forms/debugwindow.ui
+++ b/src/qt/forms/debugwindow.ui
@@ -1594,10 +1594,10 @@
<item row="23" column="0">
<widget class="QLabel" name="peerAddrRelayEnabledLabel">
<property name="toolTip">
- <string extracomment="Tooltip text for the Address Relay field in the peer details area.">Whether we relay addresses to this peer.</string>
+ <string extracomment="Tooltip text for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).">Whether we relay addresses to this peer.</string>
</property>
<property name="text">
- <string>Address Relay</string>
+ <string extracomment="Text title for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).">Address Relay</string>
</property>
</widget>
</item>
@@ -1620,10 +1620,10 @@
<item row="24" column="0">
<widget class="QLabel" name="peerAddrProcessedLabel">
<property name="toolTip">
- <string extracomment="Tooltip text for the Addresses Processed field in the peer details area.">Total number of addresses processed, excluding those dropped due to rate-limiting.</string>
+ <string extracomment="Tooltip text for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).">The total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</string>
</property>
<property name="text">
- <string>Addresses Processed</string>
+ <string extracomment="Text title for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).">Addresses Processed</string>
</property>
</widget>
</item>
@@ -1646,10 +1646,10 @@
<item row="25" column="0">
<widget class="QLabel" name="peerAddrRateLimitedLabel">
<property name="toolTip">
- <string extracomment="Tooltip text for the Addresses Rate-Limited field in the peer details area.">Total number of addresses dropped due to rate-limiting.</string>
+ <string extracomment="Tooltip text for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.">The total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</string>
</property>
<property name="text">
- <string>Addresses Rate-Limited</string>
+ <string extracomment="Text title for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.">Addresses Rate-Limited</string>
</property>
</widget>
</item>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 130f424e43..95b0a390e0 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -47,6 +47,7 @@
#include <QGuiApplication>
#include <QJsonObject>
#include <QKeyEvent>
+#include <QKeySequence>
#include <QLatin1String>
#include <QLineEdit>
#include <QList>
@@ -416,7 +417,7 @@ void bringToFront(QWidget* w)
void handleCloseWindowShortcut(QWidget* w)
{
- QObject::connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), w), &QShortcut::activated, w, &QWidget::close);
+ QObject::connect(new QShortcut(QKeySequence(QObject::tr("Ctrl+W")), w), &QShortcut::activated, w, &QWidget::close);
}
void openDebugLogfile()
diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp
index dcde7adec4..63b4055092 100644
--- a/src/qt/intro.cpp
+++ b/src/qt/intro.cpp
@@ -298,12 +298,12 @@ void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable
void Intro::UpdateFreeSpaceLabel()
{
- QString freeString = tr("%1 GB of space available").arg(m_bytes_available / GB_BYTES);
+ QString freeString = tr("%n GB of space available", "", m_bytes_available / GB_BYTES);
if (m_bytes_available < m_required_space_gb * GB_BYTES) {
- freeString += " " + tr("(of %1 GB needed)").arg(m_required_space_gb);
+ freeString += " " + tr("(of %n GB needed)", "", m_required_space_gb);
ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
} else if (m_bytes_available / GB_BYTES - m_required_space_gb < 10) {
- freeString += " " + tr("(%1 GB needed for full chain)").arg(m_required_space_gb);
+ freeString += " " + tr("(%n GB needed for full chain)", "", m_required_space_gb);
ui->freeSpace->setStyleSheet("QLabel { color: #999900 }");
} else {
ui->freeSpace->setStyleSheet("");
diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp
index 5d9ed5bf23..52bda59748 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -151,8 +151,28 @@ void OptionsModel::Init(bool resetSettings)
if (!settings.contains("fListen"))
settings.setValue("fListen", DEFAULT_LISTEN);
- if (!gArgs.SoftSetBoolArg("-listen", settings.value("fListen").toBool()))
+ const bool listen{settings.value("fListen").toBool()};
+ if (!gArgs.SoftSetBoolArg("-listen", listen)) {
addOverriddenOption("-listen");
+ } else if (!listen) {
+ // We successfully set -listen=0, thus mimic the logic from InitParameterInteraction():
+ // "parameter interaction: -listen=0 -> setting -listenonion=0".
+ //
+ // Both -listen and -listenonion default to true.
+ //
+ // The call order is:
+ //
+ // InitParameterInteraction()
+ // would set -listenonion=0 if it sees -listen=0, but for bitcoin-qt with
+ // fListen=false -listen is 1 at this point
+ //
+ // OptionsModel::Init()
+ // (this method) can flip -listen from 1 to 0 if fListen=false
+ //
+ // AppInitParameterInteraction()
+ // raises an error if -listen=0 and -listenonion=1
+ gArgs.SoftSetBoolArg("-listenonion", false);
+ }
if (!settings.contains("server")) {
settings.setValue("server", false);
diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp
index 32cb1f6ba0..41c389d9cc 100644
--- a/src/qt/peertablemodel.cpp
+++ b/src/qt/peertablemodel.cpp
@@ -82,7 +82,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
//: An Outbound Connection to a Peer.
tr("Outbound"));
case ConnectionType:
- return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /* prepend_direction */ false);
+ return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /*prepend_direction=*/false);
case Network:
return GUIUtil::NetworkToQString(rec->nodeStats.m_network);
case Ping:
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 5544eaa1a6..8fee359758 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -40,6 +40,7 @@
#include <QDateTime>
#include <QFont>
#include <QKeyEvent>
+#include <QKeySequence>
#include <QLatin1String>
#include <QLocale>
#include <QMenu>
@@ -849,7 +850,7 @@ void RPCConsole::setFontSize(int newSize)
// clear console (reset icon sizes, default stylesheet) and re-add the content
float oldPosFactor = 1.0 / ui->messagesWidget->verticalScrollBar()->maximum() * ui->messagesWidget->verticalScrollBar()->value();
- clear(/* keep_prompt */ true);
+ clear(/*keep_prompt=*/true);
ui->messagesWidget->setHtml(str);
ui->messagesWidget->verticalScrollBar()->setValue(oldPosFactor * ui->messagesWidget->verticalScrollBar()->maximum());
}
@@ -1170,7 +1171,6 @@ void RPCConsole::updateDetailWidget()
peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal));
ui->peerHeading->setText(peerAddrDetails);
ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStats.nServices));
- ui->peerRelayTxes->setText(stats->nodeStats.fRelayTxes ? ts.yes : ts.no);
QString bip152_hb_settings;
if (stats->nodeStats.m_bip152_highbandwidth_to) bip152_hb_settings = ts.to;
if (stats->nodeStats.m_bip152_highbandwidth_from) bip152_hb_settings += (bip152_hb_settings.isEmpty() ? ts.from : QLatin1Char('/') + ts.from);
@@ -1189,7 +1189,7 @@ void RPCConsole::updateDetailWidget()
ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion));
ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
- ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /* prepend_direction */ true));
+ ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /*prepend_direction=*/true));
ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network));
if (stats->nodeStats.m_permissionFlags == NetPermissionFlags::None) {
ui->peerPermissions->setText(ts.na);
@@ -1222,6 +1222,7 @@ void RPCConsole::updateDetailWidget()
ui->peerAddrRelayEnabled->setText(stats->nodeStateStats.m_addr_relay_enabled ? ts.yes : ts.no);
ui->peerAddrProcessed->setText(QString::number(stats->nodeStateStats.m_addr_processed));
ui->peerAddrRateLimited->setText(QString::number(stats->nodeStateStats.m_addr_rate_limited));
+ ui->peerRelayTxes->setText(stats->nodeStateStats.m_relay_txs ? ts.yes : ts.no);
}
ui->peersTabRightPanel->show();
@@ -1355,10 +1356,10 @@ QString RPCConsole::tabTitle(TabTypes tab_type) const
QKeySequence RPCConsole::tabShortcut(TabTypes tab_type) const
{
switch (tab_type) {
- case TabTypes::INFO: return QKeySequence(Qt::CTRL + Qt::Key_I);
- case TabTypes::CONSOLE: return QKeySequence(Qt::CTRL + Qt::Key_T);
- case TabTypes::GRAPH: return QKeySequence(Qt::CTRL + Qt::Key_N);
- case TabTypes::PEERS: return QKeySequence(Qt::CTRL + Qt::Key_P);
+ case TabTypes::INFO: return QKeySequence(tr("Ctrl+I"));
+ case TabTypes::CONSOLE: return QKeySequence(tr("Ctrl+T"));
+ case TabTypes::GRAPH: return QKeySequence(tr("Ctrl+N"));
+ case TabTypes::PEERS: return QKeySequence(tr("Ctrl+P"));
} // no default case, so the compiler can warn about missing cases
assert(false);
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 579ef0c3fd..c924789796 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -401,6 +401,80 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
return true;
}
+void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
+{
+ // Serialize the PSBT
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+ GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
+ QMessageBox msgBox;
+ msgBox.setText("Unsigned Transaction");
+ msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
+ msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
+ msgBox.setDefaultButton(QMessageBox::Discard);
+ switch (msgBox.exec()) {
+ case QMessageBox::Save: {
+ QString selectedFilter;
+ QString fileNameSuggestion = "";
+ bool first = true;
+ for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
+ if (!first) {
+ fileNameSuggestion.append(" - ");
+ }
+ QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
+ QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
+ fileNameSuggestion.append(labelOrAddress + "-" + amount);
+ first = false;
+ }
+ fileNameSuggestion.append(".psbt");
+ QString filename = GUIUtil::getSaveFileName(this,
+ tr("Save Transaction Data"), fileNameSuggestion,
+ //: Expanded name of the binary PSBT file format. See: BIP 174.
+ tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
+ if (filename.isEmpty()) {
+ return;
+ }
+ std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
+ out << ssTx.str();
+ out.close();
+ Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
+ break;
+ }
+ case QMessageBox::Discard:
+ break;
+ default:
+ assert(false);
+ } // msgBox.exec()
+}
+
+bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {
+ TransactionError err;
+ try {
+ err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
+ } catch (const std::runtime_error& e) {
+ QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
+ return false;
+ }
+ if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
+ //: "External signer" means using devices such as hardware wallets.
+ QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
+ return false;
+ }
+ if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
+ //: "External signer" means using devices such as hardware wallets.
+ QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
+ return false;
+ }
+ if (err != TransactionError::OK) {
+ tfm::format(std::cerr, "Failed to sign PSBT");
+ processSendCoinsReturn(WalletModel::TransactionCreationFailed);
+ return false;
+ }
+ // fillPSBT does not always properly finalize
+ complete = FinalizeAndExtractPSBT(psbtx, mtx);
+ return true;
+}
+
void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
{
if(!model || !model->getOptionsModel())
@@ -411,7 +485,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
assert(m_current_transaction);
const QString confirmation = tr("Confirm send coins");
- auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, !model->wallet().privateKeysDisabled(), model->getOptionsModel()->getEnablePSBTControls(), this);
+ const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
+ const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};
+ auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
// TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
@@ -424,49 +500,50 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
bool send_failure = false;
if (retval == QMessageBox::Save) {
+ // "Create Unsigned" clicked
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
- // Always fill without signing first. This prevents an external signer
- // from being called prematurely and is not expensive.
- TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
+ // Fill without signing
+ TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
assert(!complete);
assert(err == TransactionError::OK);
+
+ // Copy PSBT to clipboard and offer to save
+ presentPSBT(psbtx);
+ } else {
+ // "Send" clicked
+ assert(!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner());
+ bool broadcast = true;
if (model->wallet().hasExternalSigner()) {
- try {
- err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
- } catch (const std::runtime_error& e) {
- QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
- send_failure = true;
- return;
- }
- if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
- //: "External signer" means using devices such as hardware wallets.
- QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
- send_failure = true;
- return;
- }
- if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
- //: "External signer" means using devices such as hardware wallets.
- QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
- send_failure = true;
- return;
- }
- if (err != TransactionError::OK) {
- tfm::format(std::cerr, "Failed to sign PSBT");
- processSendCoinsReturn(WalletModel::TransactionCreationFailed);
- send_failure = true;
- return;
+ CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
+ PartiallySignedTransaction psbtx(mtx);
+ bool complete = false;
+ // Always fill without signing first. This prevents an external signer
+ // from being called prematurely and is not expensive.
+ TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
+ assert(!complete);
+ assert(err == TransactionError::OK);
+ send_failure = !signWithExternalSigner(psbtx, mtx, complete);
+ // Don't broadcast when user rejects it on the device or there's a failure:
+ broadcast = complete && !send_failure;
+ if (!send_failure) {
+ // A transaction signed with an external signer is not always complete,
+ // e.g. in a multisig wallet.
+ if (complete) {
+ // Prepare transaction for broadcast transaction if complete
+ const CTransactionRef tx = MakeTransactionRef(mtx);
+ m_current_transaction->setWtx(tx);
+ } else {
+ presentPSBT(psbtx);
+ }
}
- // fillPSBT does not always properly finalize
- complete = FinalizeAndExtractPSBT(psbtx, mtx);
}
- // Broadcast transaction if complete (even with an external signer this
- // is not always the case, e.g. in a multisig wallet).
- if (complete) {
- const CTransactionRef tx = MakeTransactionRef(mtx);
- m_current_transaction->setWtx(tx);
+ // Broadcast the transaction, unless an external signer was used and it
+ // failed, or more signatures are needed.
+ if (broadcast) {
+ // now send the prepared transaction
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
// process sendStatus and on error generate message shown to user
processSendCoinsReturn(sendStatus);
@@ -476,64 +553,6 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
} else {
send_failure = true;
}
- return;
- }
-
- // Copy PSBT to clipboard and offer to save
- assert(!complete);
- // Serialize the PSBT
- CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
- ssTx << psbtx;
- GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
- QMessageBox msgBox;
- msgBox.setText("Unsigned Transaction");
- msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
- msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
- msgBox.setDefaultButton(QMessageBox::Discard);
- switch (msgBox.exec()) {
- case QMessageBox::Save: {
- QString selectedFilter;
- QString fileNameSuggestion = "";
- bool first = true;
- for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
- if (!first) {
- fileNameSuggestion.append(" - ");
- }
- QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
- QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
- fileNameSuggestion.append(labelOrAddress + "-" + amount);
- first = false;
- }
- fileNameSuggestion.append(".psbt");
- QString filename = GUIUtil::getSaveFileName(this,
- tr("Save Transaction Data"), fileNameSuggestion,
- //: Expanded name of the binary PSBT file format. See: BIP 174.
- tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
- if (filename.isEmpty()) {
- return;
- }
- std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
- out << ssTx.str();
- out.close();
- Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
- break;
- }
- case QMessageBox::Discard:
- break;
- default:
- assert(false);
- } // msgBox.exec()
- } else {
- assert(!model->wallet().privateKeysDisabled());
- // now send the prepared transaction
- WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
- // process sendStatus and on error generate message shown to user
- processSendCoinsReturn(sendStatus);
-
- if (sendStatus.status == WalletModel::OK) {
- Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
- } else {
- send_failure = true;
}
}
if (!send_failure) {
diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h
index 4a16702756..400503d0c0 100644
--- a/src/qt/sendcoinsdialog.h
+++ b/src/qt/sendcoinsdialog.h
@@ -70,6 +70,8 @@ private:
bool fFeeMinimized;
const PlatformStyle *platformStyle;
+ // Copy PSBT to clipboard and offer to save it.
+ void presentPSBT(PartiallySignedTransaction& psbt);
// Process WalletModel::SendCoinsReturn and generate a pair consisting
// of a message and message flags for use in Q_EMIT message().
// Additional parameter msgArg can be used via .arg(msgArg).
@@ -77,6 +79,15 @@ private:
void minimizeFeeSection(bool fMinimize);
// Format confirmation message
bool PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text);
+ /* Sign PSBT using external signer.
+ *
+ * @param[in,out] psbtx the PSBT to sign
+ * @param[in,out] mtx needed to attempt to finalize
+ * @param[in,out] complete whether the PSBT is complete (a successfully signed multisig transaction may not be complete)
+ *
+ * @returns false if any failure occurred, which may include the user rejection of a transaction on the device.
+ */
+ bool signWithExternalSigner(PartiallySignedTransaction& psbt, CMutableTransaction& mtx, bool& complete);
void updateFeeMinimizedLabel();
void updateCoinControlState();
@@ -117,6 +128,8 @@ class SendConfirmationDialog : public QMessageBox
public:
SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, bool enable_send = true, bool always_show_unsigned = true, QWidget* parent = nullptr);
+ /* Returns QMessageBox::Cancel, QMessageBox::Yes when "Send" is
+ clicked and QMessageBox::Save when "Create Unsigned" is clicked. */
int exec() override;
private Q_SLOTS:
diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp
index 74bedbf020..1f4b30534b 100644
--- a/src/qt/signverifymessagedialog.cpp
+++ b/src/qt/signverifymessagedialog.cpp
@@ -91,7 +91,7 @@ void SignVerifyMessageDialog::on_addressBookButton_SM_clicked()
{
if (model && model->getAddressTableModel())
{
- model->refresh(/* pk_hash_only */ true);
+ model->refresh(/*pk_hash_only=*/true);
AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this);
dlg.setModel(model->getAddressTableModel());
if (dlg.exec())
diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp
index 51894e1915..4a943a6343 100644
--- a/src/qt/test/optiontests.cpp
+++ b/src/qt/test/optiontests.cpp
@@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <init.h>
#include <qt/bitcoin.h>
#include <qt/test/optiontests.h>
#include <test/util/setup_common.h>
@@ -29,3 +30,39 @@ void OptionTests::optionTests()
});
gArgs.WriteSettingsFile();
}
+
+void OptionTests::parametersInteraction()
+{
+ // Test that the bug https://github.com/bitcoin-core/gui/issues/567 does not resurface.
+ // It was fixed via https://github.com/bitcoin-core/gui/pull/568.
+ // With fListen=false in ~/.config/Bitcoin/Bitcoin-Qt.conf and all else left as default,
+ // bitcoin-qt should set both -listen and -listenonion to false and start successfully.
+ gArgs.ClearPathCache();
+
+ gArgs.LockSettings([&](util::Settings& s) {
+ s.forced_settings.erase("listen");
+ s.forced_settings.erase("listenonion");
+ });
+ QVERIFY(!gArgs.IsArgSet("-listen"));
+ QVERIFY(!gArgs.IsArgSet("-listenonion"));
+
+ QSettings settings;
+ settings.setValue("fListen", false);
+
+ OptionsModel{};
+
+ const bool expected{false};
+
+ QVERIFY(gArgs.IsArgSet("-listen"));
+ QCOMPARE(gArgs.GetBoolArg("-listen", !expected), expected);
+
+ QVERIFY(gArgs.IsArgSet("-listenonion"));
+ QCOMPARE(gArgs.GetBoolArg("-listenonion", !expected), expected);
+
+ QVERIFY(AppInitParameterInteraction(gArgs));
+
+ // cleanup
+ settings.remove("fListen");
+ QVERIFY(!settings.contains("fListen"));
+ gArgs.ClearPathCache();
+}
diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h
index 779d4cc209..39c1612c8f 100644
--- a/src/qt/test/optiontests.h
+++ b/src/qt/test/optiontests.h
@@ -17,6 +17,7 @@ public:
private Q_SLOTS:
void optionTests();
+ void parametersInteraction();
private:
interfaces::Node& m_node;
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index 44b4fee2e7..6b0495f5a8 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -32,11 +32,11 @@
// Amount column is right-aligned it contains numbers
static int column_alignments[] = {
- Qt::AlignLeft|Qt::AlignVCenter, /* status */
- Qt::AlignLeft|Qt::AlignVCenter, /* watchonly */
- Qt::AlignLeft|Qt::AlignVCenter, /* date */
- Qt::AlignLeft|Qt::AlignVCenter, /* type */
- Qt::AlignLeft|Qt::AlignVCenter, /* address */
+ Qt::AlignLeft|Qt::AlignVCenter, /*status=*/
+ Qt::AlignLeft|Qt::AlignVCenter, /*watchonly=*/
+ Qt::AlignLeft|Qt::AlignVCenter, /*date=*/
+ Qt::AlignLeft|Qt::AlignVCenter, /*type=*/
+ Qt::AlignLeft|Qt::AlignVCenter, /*address=*/
Qt::AlignRight|Qt::AlignVCenter /* amount */
};
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 778ef04b77..47f3ba7e7f 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -550,7 +550,7 @@ void TransactionView::openThirdPartyTxUrl(QString url)
QWidget *TransactionView::createDateRangeWidget()
{
dateRangeWidget = new QFrame();
- dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
+ dateRangeWidget->setFrameStyle(static_cast<int>(QFrame::Panel) | static_cast<int>(QFrame::Raised));
dateRangeWidget->setContentsMargins(1,1,1,1);
QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
layout->setContentsMargins(0,0,0,0);
diff --git a/src/rest.cpp b/src/rest.cpp
index d86a62030c..a8eba05c3f 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -3,6 +3,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <rest.h>
+
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
@@ -28,6 +30,7 @@
#include <version.h>
#include <any>
+#include <string>
#include <boost/algorithm/string.hpp>
@@ -41,21 +44,14 @@ using node::ReadBlockFromDisk;
static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
static constexpr unsigned int MAX_REST_HEADERS_RESULTS = 2000;
-enum class RetFormat {
- UNDEF,
- BINARY,
- HEX,
- JSON,
-};
-
static const struct {
- RetFormat rf;
+ RESTResponseFormat rf;
const char* name;
} rf_names[] = {
- {RetFormat::UNDEF, ""},
- {RetFormat::BINARY, "bin"},
- {RetFormat::HEX, "hex"},
- {RetFormat::JSON, "json"},
+ {RESTResponseFormat::UNDEF, ""},
+ {RESTResponseFormat::BINARY, "bin"},
+ {RESTResponseFormat::HEX, "hex"},
+ {RESTResponseFormat::JSON, "json"},
};
struct CCoin {
@@ -138,25 +134,28 @@ static ChainstateManager* GetChainman(const std::any& context, HTTPRequest* req)
return node_context->chainman.get();
}
-static RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
+RESTResponseFormat ParseDataFormat(std::string& param, const std::string& strReq)
{
- const std::string::size_type pos = strReq.rfind('.');
- if (pos == std::string::npos)
- {
- param = strReq;
+ // Remove query string (if any, separated with '?') as it should not interfere with
+ // parsing param and data format
+ param = strReq.substr(0, strReq.rfind('?'));
+ const std::string::size_type pos_format{param.rfind('.')};
+
+ // No format string is found
+ if (pos_format == std::string::npos) {
return rf_names[0].rf;
}
- param = strReq.substr(0, pos);
- const std::string suff(strReq, pos + 1);
-
+ // Match format string to available formats
+ const std::string suffix(param, pos_format + 1);
for (const auto& rf_name : rf_names) {
- if (suff == rf_name.name)
+ if (suffix == rf_name.name) {
+ param.erase(pos_format);
return rf_name.rf;
+ }
}
- /* If no suffix is found, return original string. */
- param = strReq;
+ // If no suffix is found, return RESTResponseFormat::UNDEF and original string without query string
return rf_names[0].rf;
}
@@ -192,19 +191,29 @@ static bool rest_headers(const std::any& context,
if (!CheckWarmup(req))
return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> path;
boost::split(path, param, boost::is_any_of("/"));
- if (path.size() != 2)
- return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
-
- const auto parsed_count{ToIntegral<size_t>(path[0])};
+ std::string raw_count;
+ std::string hashStr;
+ if (path.size() == 2) {
+ // deprecated path: /rest/headers/<count>/<hash>
+ hashStr = path[1];
+ raw_count = path[0];
+ } else if (path.size() == 1) {
+ // new path with query parameter: /rest/headers/<hash>?count=<count>
+ hashStr = path[0];
+ raw_count = req->GetQueryParameter("count").value_or("5");
+ } else {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/headers/<hash>.<ext>?count=<count>");
+ }
+
+ const auto parsed_count{ToIntegral<size_t>(raw_count)};
if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
- return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, path[0]));
+ return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count));
}
- std::string hashStr = path[1];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
@@ -230,7 +239,7 @@ static bool rest_headers(const std::any& context,
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
@@ -242,7 +251,7 @@ static bool rest_headers(const std::any& context,
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
@@ -253,7 +262,7 @@ static bool rest_headers(const std::any& context,
req->WriteReply(HTTP_OK, strHex);
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue jsonHeaders(UniValue::VARR);
for (const CBlockIndex *pindex : headers) {
jsonHeaders.push_back(blockheaderToJSON(tip, pindex));
@@ -277,15 +286,15 @@ static bool rest_block(const std::any& context,
if (!CheckWarmup(req))
return false;
std::string hashStr;
- const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash;
if (!ParseHashStr(hashStr, hash))
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
CBlock block;
- CBlockIndex* pblockindex = nullptr;
- CBlockIndex* tip = nullptr;
+ const CBlockIndex* pblockindex = nullptr;
+ const CBlockIndex* tip = nullptr;
{
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
@@ -305,7 +314,7 @@ static bool rest_block(const std::any& context,
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string binaryBlock = ssBlock.str();
@@ -314,7 +323,7 @@ static bool rest_block(const std::any& context,
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string strHex = HexStr(ssBlock) + "\n";
@@ -323,7 +332,7 @@ static bool rest_block(const std::any& context,
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue objBlock = blockToJSON(block, tip, pblockindex, tx_verbosity);
std::string strJSON = objBlock.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
@@ -352,17 +361,32 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
if (!CheckWarmup(req)) return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> uri_parts;
boost::split(uri_parts, param, boost::is_any_of("/"));
- if (uri_parts.size() != 3) {
- return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<count>/<blockhash>");
+ std::string raw_count;
+ std::string raw_blockhash;
+ if (uri_parts.size() == 3) {
+ // deprecated path: /rest/blockfilterheaders/<filtertype>/<count>/<blockhash>
+ raw_blockhash = uri_parts[2];
+ raw_count = uri_parts[1];
+ } else if (uri_parts.size() == 2) {
+ // new path with query parameter: /rest/blockfilterheaders/<filtertype>/<blockhash>?count=<count>
+ raw_blockhash = uri_parts[1];
+ raw_count = req->GetQueryParameter("count").value_or("5");
+ } else {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<blockhash>.<ext>?count=<count>");
+ }
+
+ const auto parsed_count{ToIntegral<size_t>(raw_count)};
+ if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
+ return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count));
}
uint256 block_hash;
- if (!ParseHashStr(uri_parts[2], block_hash)) {
- return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[2]);
+ if (!ParseHashStr(raw_blockhash, block_hash)) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash);
}
BlockFilterType filtertype;
@@ -375,11 +399,6 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]);
}
- const auto parsed_count{ToIntegral<size_t>(uri_parts[1])};
- if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
- return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, uri_parts[1]));
- }
-
std::vector<const CBlockIndex*> headers;
headers.reserve(*parsed_count);
{
@@ -418,7 +437,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION};
for (const uint256& header : filter_headers) {
ssHeader << header;
@@ -429,7 +448,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
req->WriteReply(HTTP_OK, binaryHeader);
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION};
for (const uint256& header : filter_headers) {
ssHeader << header;
@@ -440,7 +459,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
req->WriteReply(HTTP_OK, strHex);
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue jsonHeaders(UniValue::VARR);
for (const uint256& header : filter_headers) {
jsonHeaders.push_back(header.GetHex());
@@ -462,7 +481,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
if (!CheckWarmup(req)) return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
// request is sent over URI scheme /rest/blockfilter/filtertype/blockhash
std::vector<std::string> uri_parts;
@@ -518,7 +537,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION};
ssResp << filter;
@@ -527,7 +546,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
req->WriteReply(HTTP_OK, binaryResp);
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION};
ssResp << filter;
@@ -536,7 +555,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
req->WriteReply(HTTP_OK, strHex);
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue ret(UniValue::VOBJ);
ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
std::string strJSON = ret.write() + "\n";
@@ -558,10 +577,10 @@ static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std:
if (!CheckWarmup(req))
return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
JSONRPCRequest jsonRequest;
jsonRequest.context = context;
jsonRequest.params = UniValue(UniValue::VARR);
@@ -584,10 +603,10 @@ static bool rest_mempool_info(const std::any& context, HTTPRequest* req, const s
const CTxMemPool* mempool = GetMemPool(context, req);
if (!mempool) return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue mempoolInfoObject = MempoolInfoToJSON(*mempool);
std::string strJSON = mempoolInfoObject.write() + "\n";
@@ -607,10 +626,10 @@ static bool rest_mempool_contents(const std::any& context, HTTPRequest* req, con
const CTxMemPool* mempool = GetMemPool(context, req);
if (!mempool) return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue mempoolObject = MempoolToJSON(*mempool, true);
std::string strJSON = mempoolObject.write() + "\n";
@@ -629,7 +648,7 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
if (!CheckWarmup(req))
return false;
std::string hashStr;
- const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash;
if (!ParseHashStr(hashStr, hash))
@@ -642,13 +661,13 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
const NodeContext* const node = GetNodeContext(context, req);
if (!node) return false;
uint256 hashBlock = uint256();
- const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock);
+ const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock);
if (!tx) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssTx << tx;
@@ -658,7 +677,7 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssTx << tx;
@@ -668,9 +687,9 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue objTx(UniValue::VOBJ);
- TxToUniv(*tx, hashBlock, objTx);
+ TxToUniv(*tx, /*block_hash=*/hashBlock, /*entry=*/ objTx);
std::string strJSON = objTx.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
@@ -688,7 +707,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
if (!CheckWarmup(req))
return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> uriParts;
if (param.length() > 1)
@@ -735,14 +754,14 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
}
switch (rf) {
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
// convert hex to bin, continue then with bin part
std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
[[fallthrough]];
}
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
try {
//deserialize only if user sent a request
if (strRequestMutable.size() > 0)
@@ -762,7 +781,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
break;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
if (!fInputParsed)
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
break;
@@ -816,7 +835,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
// serialize data
// use exact same output as mentioned in Bip64
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
@@ -828,7 +847,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
@@ -838,7 +857,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue objGetUTXOResponse(UniValue::VOBJ);
// pack in some essentials
@@ -855,7 +874,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
// include the script in a json output
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
+ ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
utxo.pushKV("scriptPubKey", o);
utxos.push_back(utxo);
}
@@ -878,7 +897,7 @@ static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req,
{
if (!CheckWarmup(req)) return false;
std::string height_str;
- const RetFormat rf = ParseDataFormat(height_str, str_uri_part);
+ const RESTResponseFormat rf = ParseDataFormat(height_str, str_uri_part);
int32_t blockheight = -1; // Initialization done only to prevent valgrind false positive, see https://github.com/bitcoin/bitcoin/pull/18785
if (!ParseInt32(height_str, &blockheight) || blockheight < 0) {
@@ -898,19 +917,19 @@ static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req,
pblockindex = active_chain[blockheight];
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION);
ss_blockhash << pblockindex->GetBlockHash();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, ss_blockhash.str());
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n");
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
req->WriteHeader("Content-Type", "application/json");
UniValue resp = UniValue(UniValue::VOBJ);
resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex());
diff --git a/src/rest.h b/src/rest.h
new file mode 100644
index 0000000000..49b1c333d0
--- /dev/null
+++ b/src/rest.h
@@ -0,0 +1,28 @@
+// 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.
+
+#ifndef BITCOIN_REST_H
+#define BITCOIN_REST_H
+
+#include <string>
+
+enum class RESTResponseFormat {
+ UNDEF,
+ BINARY,
+ HEX,
+ JSON,
+};
+
+/**
+ * Parse a URI to get the data format and URI without data format
+ * and query string.
+ *
+ * @param[out] param The strReq without the data format string and
+ * without the query string (if any).
+ * @param[in] strReq The URI to be parsed.
+ * @return RESTResponseFormat that was parsed from the URI.
+ */
+RESTResponseFormat ParseDataFormat(std::string& param, const std::string& strReq);
+
+#endif // BITCOIN_REST_H
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index e8ede2f0ee..a124e0267c 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -104,7 +104,8 @@ static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* b
return blockindex == tip ? 1 : -1;
}
-CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman) {
+static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman)
+{
LOCK(::cs_main);
CChain& active_chain = chainman.ActiveChain();
@@ -121,7 +122,7 @@ CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainma
return active_chain[height];
} else {
const uint256 hash{ParseHashV(param, "hash_or_height")};
- CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
+ const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
@@ -186,7 +187,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn
// coinbase transaction (i.e. i == 0) doesn't have undo data
const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
UniValue objTx(UniValue::VOBJ);
- TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo, verbosity);
+ TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity);
txs.push_back(objTx);
}
break;
@@ -429,7 +430,7 @@ static RPCHelpMan getblockfrompeer()
"Subsequent calls for the same block and a new peer will cause the response from the previous peer to be ignored.\n\n"
"Returns an empty JSON object if the request was successfully scheduled.",
{
- {"block_hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
+ {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
{"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"},
},
RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
@@ -443,7 +444,7 @@ static RPCHelpMan getblockfrompeer()
ChainstateManager& chainman = EnsureChainman(node);
PeerManager& peerman = EnsurePeerman(node);
- const uint256& block_hash{ParseHashV(request.params[0], "block_hash")};
+ const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
const NodeId peer_id{request.params[1].get_int64()};
const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
@@ -488,7 +489,7 @@ static RPCHelpMan getblockhash()
if (nHeight < 0 || nHeight > active_chain.Height())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
- CBlockIndex* pblockindex = active_chain[nHeight];
+ const CBlockIndex* pblockindex = active_chain[nHeight];
return pblockindex->GetBlockHash().GetHex();
},
};
@@ -670,7 +671,7 @@ static RPCHelpMan getblock()
{
{RPCResult::Type::STR, "asm", "The asm"},
{RPCResult::Type::STR, "hex", "The hex"},
- {RPCResult::Type::STR, "address", /* optional */ true, "The Bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
}},
}},
@@ -766,7 +767,7 @@ static RPCHelpMan pruneblockchain()
// too low to be a block time (corresponds to timestamp from Sep 2001).
if (heightParam > 1000000000) {
// Add a 2 hour buffer to include blocks which might have had old timestamps
- CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0);
+ const CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp.");
}
@@ -860,7 +861,7 @@ static RPCHelpMan gettxoutsetinfo()
{
UniValue ret(UniValue::VOBJ);
- CBlockIndex* pindex{nullptr};
+ const CBlockIndex* pindex{nullptr};
const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
CCoinsStats stats{hash_type};
stats.index_requested = request.params[2].isNull() || request.params[2].get_bool();
@@ -1025,7 +1026,7 @@ static RPCHelpMan gettxout()
}
ret.pushKV("value", ValueFromAmount(coin.out.nValue));
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
+ ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
ret.pushKV("scriptPubKey", o);
ret.pushKV("coinbase", (bool)coin.fCoinBase);
@@ -1161,38 +1162,38 @@ RPCHelpMan getblockchaininfo()
{
/* TODO: from v24, remove -deprecatedrpc=softforks */
return RPCHelpMan{"getblockchaininfo",
- "Returns an object containing various state info regarding blockchain processing.\n",
- {},
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"},
- {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"},
- {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
- {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
- {RPCResult::Type::NUM, "difficulty", "the current difficulty"},
- {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
- {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
- {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"},
- {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"},
- {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
- {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
- {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
- {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"},
- {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"},
- {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"},
- {RPCResult::Type::OBJ_DYN, "softforks", "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks",
- {
- {RPCResult::Type::OBJ, "xxxx", "name of the softfork",
- RPCHelpForDeployment
- },
- }},
- {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
- }},
- RPCExamples{
- HelpExampleCli("getblockchaininfo", "")
+ "Returns an object containing various state info regarding blockchain processing.\n",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"},
+ {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"},
+ {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
+ {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
+ {RPCResult::Type::NUM, "difficulty", "the current difficulty"},
+ {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
+ {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
+ {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"},
+ {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"},
+ {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
+ {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
+ {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
+ {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"},
+ {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"},
+ {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"},
+ {RPCResult::Type::OBJ_DYN, "softforks", /*optional=*/true, "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks",
+ {
+ {RPCResult::Type::OBJ, "xxxx", "name of the softfork",
+ RPCHelpForDeployment
+ },
+ }},
+ {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"},
+ }},
+ RPCExamples{
+ HelpExampleCli("getblockchaininfo", "")
+ HelpExampleRpc("getblockchaininfo", "")
- },
+ },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const ArgsManager& args{EnsureAnyArgsman(request.context)};
@@ -1266,7 +1267,7 @@ const std::vector<RPCResult> RPCHelpForDeployment{
{RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"},
{RPCResult::Type::BOOL, "possible", /*optional=*/true, "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"},
}},
- {RPCResult::Type::STR, "signalling", "indicates blocks that signalled with a # and blocks that did not with a -"},
+ {RPCResult::Type::STR, "signalling", /*optional=*/true, "indicates blocks that signalled with a # and blocks that did not with a -"},
}},
};
@@ -1295,7 +1296,7 @@ static RPCHelpMan getdeploymentinfo()
RPCResult::Type::OBJ, "", "", {
{RPCResult::Type::STR, "hash", "requested block hash (or tip)"},
{RPCResult::Type::NUM, "height", "requested block height (or tip)"},
- {RPCResult::Type::OBJ, "deployments", "", {
+ {RPCResult::Type::OBJ_DYN, "deployments", "", {
{RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment}
}},
}
@@ -1764,7 +1765,7 @@ static RPCHelpMan getblockstats()
{
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)};
+ const CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)};
CHECK_NONFATAL(pindex != nullptr);
std::set<std::string> stats;
@@ -2124,7 +2125,7 @@ static RPCHelpMan scantxoutset()
g_should_abort_scan = false;
int64_t count = 0;
std::unique_ptr<CCoinsViewCursor> pcursor;
- CBlockIndex* tip;
+ const CBlockIndex* tip;
NodeContext& node = EnsureAnyNodeContext(request.context);
{
ChainstateManager& chainman = EnsureChainman(node);
@@ -2312,7 +2313,7 @@ UniValue CreateUTXOSnapshot(
{
std::unique_ptr<CCoinsViewCursor> pcursor;
CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED};
- CBlockIndex* tip;
+ const CBlockIndex* tip;
{
// We need to lock cs_main to ensure that the coinsdb isn't written to
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index c480a093a4..23e9d4074c 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -142,6 +142,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "send", 1, "conf_target" },
{ "send", 3, "fee_rate"},
{ "send", 4, "options" },
+ { "sendall", 0, "recipients" },
+ { "sendall", 1, "conf_target" },
+ { "sendall", 3, "fee_rate"},
+ { "sendall", 4, "options" },
{ "importprivkey", 2, "rescan" },
{ "importaddress", 2, "rescan" },
{ "importaddress", 3, "p2sh" },
diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp
index 60ec15e904..82aa6f9516 100644
--- a/src/rpc/external_signer.cpp
+++ b/src/rpc/external_signer.cpp
@@ -22,7 +22,7 @@ static RPCHelpMan enumeratesigners()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::ARR, "signers", /* optional */ false, "",
+ {RPCResult::Type::ARR, "signers", /*optional=*/false, "",
{
{RPCResult::Type::OBJ, "", "",
{
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index bc7ef0c08e..1caf4ad96c 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -14,9 +14,219 @@
#include <rpc/util.h>
#include <txmempool.h>
#include <univalue.h>
+#include <util/moneystr.h>
#include <validation.h>
-static std::vector<RPCResult> MempoolEntryDescription() { return {
+using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
+using node::NodeContext;
+
+static RPCHelpMan sendrawtransaction()
+{
+ return RPCHelpMan{"sendrawtransaction",
+ "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
+ "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
+ "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
+ "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
+ "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
+ "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
+ {
+ {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
+ {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
+ "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
+ "/kvB.\nSet to 0 to accept any fee rate.\n"},
+ },
+ RPCResult{
+ RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
+ },
+ RPCExamples{
+ "\nCreate a transaction\n"
+ + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
+ "Sign the transaction, and get back the hex\n"
+ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
+ "\nSend the transaction (signed hex)\n"
+ + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {
+ UniValue::VSTR,
+ UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
+ });
+
+ CMutableTransaction mtx;
+ if (!DecodeHexTx(mtx, request.params[0].get_str())) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
+ }
+ CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
+
+ const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
+ DEFAULT_MAX_RAW_TX_FEE_RATE :
+ CFeeRate(AmountFromValue(request.params[1]));
+
+ int64_t virtual_size = GetVirtualTransactionSize(*tx);
+ CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
+
+ std::string err_string;
+ AssertLockNotHeld(cs_main);
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay=*/true, /*wait_callback=*/true);
+ if (TransactionError::OK != err) {
+ throw JSONRPCTransactionError(err, err_string);
+ }
+
+ return tx->GetHash().GetHex();
+ },
+ };
+}
+
+static RPCHelpMan testmempoolaccept()
+{
+ return RPCHelpMan{"testmempoolaccept",
+ "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
+ "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n"
+ "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
+ "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
+ "\nThis checks if transactions violate the consensus or policy rules.\n"
+ "\nSee sendrawtransaction call.\n",
+ {
+ {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
+ {
+ {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
+ },
+ },
+ {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
+ "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
+ "Returns results for each transaction in the same order they were passed in.\n"
+ "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
+ {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
+ {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
+ {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
+ "If not present, the tx was not fully validated due to a failure in another tx in the list."},
+ {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"},
+ {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
+ {
+ {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
+ }},
+ {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
+ }},
+ }
+ },
+ RPCExamples{
+ "\nCreate a transaction\n"
+ + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
+ "Sign the transaction, and get back the hex\n"
+ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
+ "\nTest acceptance of the transaction (signed hex)\n"
+ + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {
+ UniValue::VARR,
+ UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
+ });
+ const UniValue raw_transactions = request.params[0].get_array();
+ if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER,
+ "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
+ }
+
+ const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
+ DEFAULT_MAX_RAW_TX_FEE_RATE :
+ CFeeRate(AmountFromValue(request.params[1]));
+
+ std::vector<CTransactionRef> txns;
+ txns.reserve(raw_transactions.size());
+ for (const auto& rawtx : raw_transactions.getValues()) {
+ CMutableTransaction mtx;
+ if (!DecodeHexTx(mtx, rawtx.get_str())) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
+ "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
+ }
+ txns.emplace_back(MakeTransactionRef(std::move(mtx)));
+ }
+
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ CTxMemPool& mempool = EnsureMemPool(node);
+ ChainstateManager& chainman = EnsureChainman(node);
+ CChainState& chainstate = chainman.ActiveChainstate();
+ const PackageMempoolAcceptResult package_result = [&] {
+ LOCK(::cs_main);
+ if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true);
+ return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
+ chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
+ }();
+
+ UniValue rpc_result(UniValue::VARR);
+ // We will check transaction fees while we iterate through txns in order. If any transaction fee
+ // exceeds maxfeerate, we will leave the rest of the validation results blank, because it
+ // doesn't make sense to return a validation result for a transaction if its ancestor(s) would
+ // not be submitted.
+ bool exit_early{false};
+ for (const auto& tx : txns) {
+ UniValue result_inner(UniValue::VOBJ);
+ result_inner.pushKV("txid", tx->GetHash().GetHex());
+ result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex());
+ if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
+ result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
+ }
+ auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
+ if (exit_early || it == package_result.m_tx_results.end()) {
+ // Validation unfinished. Just return the txid and wtxid.
+ rpc_result.push_back(result_inner);
+ continue;
+ }
+ const auto& tx_result = it->second;
+ // Package testmempoolaccept doesn't allow transactions to already be in the mempool.
+ CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
+ if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
+ const CAmount fee = tx_result.m_base_fees.value();
+ // Check that fee does not exceed maximum fee
+ const int64_t virtual_size = tx_result.m_vsize.value();
+ const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
+ if (max_raw_tx_fee && fee > max_raw_tx_fee) {
+ result_inner.pushKV("allowed", false);
+ result_inner.pushKV("reject-reason", "max-fee-exceeded");
+ exit_early = true;
+ } else {
+ // Only return the fee and vsize if the transaction would pass ATMP.
+ // These can be used to calculate the feerate.
+ result_inner.pushKV("allowed", true);
+ result_inner.pushKV("vsize", virtual_size);
+ UniValue fees(UniValue::VOBJ);
+ fees.pushKV("base", ValueFromAmount(fee));
+ result_inner.pushKV("fees", fees);
+ }
+ } else {
+ result_inner.pushKV("allowed", false);
+ const TxValidationState state = tx_result.m_state;
+ if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
+ result_inner.pushKV("reject-reason", "missing-inputs");
+ } else {
+ result_inner.pushKV("reject-reason", state.GetRejectReason());
+ }
+ }
+ rpc_result.push_back(result_inner);
+ }
+ return rpc_result;
+ },
+ };
+}
+
+static std::vector<RPCResult> MempoolEntryDescription()
+{
+ return {
RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
@@ -50,7 +260,8 @@ static std::vector<RPCResult> MempoolEntryDescription() { return {
{RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"},
-};}
+ };
+}
static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
{
@@ -164,7 +375,7 @@ UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempoo
}
}
-RPCHelpMan getrawmempool()
+static RPCHelpMan getrawmempool()
{
return RPCHelpMan{"getrawmempool",
"\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
@@ -214,7 +425,7 @@ RPCHelpMan getrawmempool()
};
}
-RPCHelpMan getmempoolancestors()
+static RPCHelpMan getmempoolancestors()
{
return RPCHelpMan{"getmempoolancestors",
"\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
@@ -278,7 +489,7 @@ RPCHelpMan getmempoolancestors()
};
}
-RPCHelpMan getmempooldescendants()
+static RPCHelpMan getmempooldescendants()
{
return RPCHelpMan{"getmempooldescendants",
"\nIf txid is in the mempool, returns all in-mempool descendants.\n",
@@ -343,7 +554,7 @@ RPCHelpMan getmempooldescendants()
};
}
-RPCHelpMan getmempoolentry()
+static RPCHelpMan getmempoolentry()
{
return RPCHelpMan{"getmempoolentry",
"\nReturns mempool data for given transaction\n",
@@ -394,7 +605,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
return ret;
}
-RPCHelpMan getmempoolinfo()
+static RPCHelpMan getmempoolinfo()
{
return RPCHelpMan{"getmempoolinfo",
"\nReturns details on the active state of the TX memory pool.\n",
@@ -423,7 +634,7 @@ RPCHelpMan getmempoolinfo()
};
}
-RPCHelpMan savemempool()
+static RPCHelpMan savemempool()
{
return RPCHelpMan{"savemempool",
"\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
@@ -463,6 +674,8 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
static const CRPCCommand commands[]{
// category actor (function)
// -------- ----------------
+ {"rawtransactions", &sendrawtransaction},
+ {"rawtransactions", &testmempoolaccept},
{"blockchain", &getmempoolancestors},
{"blockchain", &getmempooldescendants},
{"blockchain", &getmempoolentry},
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 1d1ae92c58..211026c8d9 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -7,6 +7,7 @@
#include <chainparams.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
+#include <consensus/merkle.h>
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
@@ -43,7 +44,6 @@
using node::BlockAssembler;
using node::CBlockTemplate;
-using node::IncrementExtraNonce;
using node::NodeContext;
using node::RegenerateCommitments;
using node::UpdateTime;
@@ -116,14 +116,10 @@ static RPCHelpMan getnetworkhashps()
};
}
-static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash)
+static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, uint256& block_hash)
{
block_hash.SetNull();
-
- {
- LOCK(cs_main);
- IncrementExtraNonce(&block, chainman.ActiveChain().Tip(), extra_nonce);
- }
+ block.hashMerkleRoot = BlockMerkleRoot(block);
CChainParams chainparams(Params());
@@ -149,30 +145,20 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t&
static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
{
- int nHeightEnd = 0;
- int nHeight = 0;
-
- { // Don't keep cs_main locked
- LOCK(cs_main);
- nHeight = chainman.ActiveChain().Height();
- nHeightEnd = nHeight+nGenerate;
- }
- unsigned int nExtraNonce = 0;
UniValue blockHashes(UniValue::VARR);
- while (nHeight < nHeightEnd && !ShutdownRequested())
- {
+ while (nGenerate > 0 && !ShutdownRequested()) {
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script));
if (!pblocktemplate.get())
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
CBlock *pblock = &pblocktemplate->block;
uint256 block_hash;
- if (!GenerateBlock(chainman, *pblock, nMaxTries, nExtraNonce, block_hash)) {
+ if (!GenerateBlock(chainman, *pblock, nMaxTries, block_hash)) {
break;
}
if (!block_hash.IsNull()) {
- ++nHeight;
+ --nGenerate;
blockHashes.push_back(block_hash.GetHex());
}
}
@@ -397,9 +383,8 @@ static RPCHelpMan generateblock()
uint256 block_hash;
uint64_t max_tries{DEFAULT_MAX_TRIES};
- unsigned int extra_nonce{0};
- if (!GenerateBlock(chainman, block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {
+ if (!GenerateBlock(chainman, block, max_tries, block_hash) || block_hash.IsNull()) {
throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block.");
}
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index 8d7b48d697..99671ee6ac 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -116,7 +116,7 @@ static RPCHelpMan createmultisig()
{RPCResult::Type::STR, "address", "The value of the new multisig address."},
{RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."},
{RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
- {RPCResult::Type::ARR, "warnings", /* optional */ true, "Any warnings resulting from the creation of this multisig",
+ {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig",
{
{RPCResult::Type::STR, "", ""},
}},
@@ -646,17 +646,8 @@ static RPCHelpMan logging()
uint32_t changed_log_categories = original_log_categories ^ updated_log_categories;
// Update libevent logging if BCLog::LIBEVENT has changed.
- // If the library version doesn't allow it, UpdateHTTPServerLogging() returns false,
- // in which case we should clear the BCLog::LIBEVENT flag.
- // Throw an error if the user has explicitly asked to change only the libevent
- // flag and it failed.
if (changed_log_categories & BCLog::LIBEVENT) {
- if (!UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT))) {
- LogInstance().DisableCategory(BCLog::LIBEVENT);
- if (changed_log_categories == BCLog::LIBEVENT) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "libevent logging cannot be updated when using libevent before v2.1.1.");
- }
- }
+ UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT));
}
UniValue result(UniValue::VOBJ);
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 1bde4fccbb..225feabf14 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -93,7 +93,7 @@ static RPCHelpMan getpeerinfo()
{
return RPCHelpMan{
"getpeerinfo",
- "\nReturns data about each connected network node as a json array of objects.\n",
+ "Returns data about each connected network peer as a json array of objects.",
{},
RPCResult{
RPCResult::Type::ARR, "", "",
@@ -105,7 +105,7 @@ static RPCHelpMan getpeerinfo()
{RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"},
{RPCResult::Type::STR, "addrbind", /*optional=*/true, "(ip:port) Bind address of the connection to the peer"},
{RPCResult::Type::STR, "addrlocal", /*optional=*/true, "(ip:port) Local address as reported by the peer"},
- {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"},
+ {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/*append_unroutable=*/true), ", ") + ")"},
{RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The AS in the BGP route to the peer used for diversifying\n"
"peer selection (only available if the asmap config flag is set)"},
{RPCResult::Type::STR_HEX, "services", "The services offered"},
@@ -113,7 +113,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"}
}},
- {RPCResult::Type::BOOL, "relaytxes", "Whether peer has asked us to relay transactions to it"},
+ {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether peer has asked us to relay transactions to it"},
{RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"},
{RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"},
{RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"},
@@ -144,7 +144,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"},
}},
- {RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"},
+ {RPCResult::Type::NUM, "minfeefilter", /*optional=*/true, "The minimum fee rate for transactions this peer accepts"},
{RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "",
{
{RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n"
@@ -197,7 +197,6 @@ static RPCHelpMan getpeerinfo()
}
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
- obj.pushKV("relaytxes", stats.fRelayTxes);
obj.pushKV("lastsend", count_seconds(stats.m_last_send));
obj.pushKV("lastrecv", count_seconds(stats.m_last_recv));
obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time));
@@ -232,6 +231,8 @@ static RPCHelpMan getpeerinfo()
heights.push_back(height);
}
obj.pushKV("inflight", heights);
+ obj.pushKV("relaytxes", statestats.m_relay_txs);
+ obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received));
obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled);
obj.pushKV("addr_processed", statestats.m_addr_processed);
obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited);
@@ -241,7 +242,6 @@ static RPCHelpMan getpeerinfo()
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
- obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter));
UniValue sendPerMsgCmd(UniValue::VOBJ);
for (const auto& i : stats.mapSendBytesPerMsgCmd) {
@@ -888,7 +888,7 @@ static RPCHelpMan getnodeaddresses()
}
// returns a shuffled list of CAddress
- const std::vector<CAddress> vAddr{connman.GetAddresses(count, /* max_pct */ 0, network)};
+ const std::vector<CAddress> vAddr{connman.GetAddresses(count, /*max_pct=*/0, network)};
UniValue ret(UniValue::VARR);
for (const CAddress& addr : vAddr) {
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 6272a7c8cf..8e4b396da9 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -11,7 +11,6 @@
#include <core_io.h>
#include <index/txindex.h>
#include <key_io.h>
-#include <merkleblock.h>
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
@@ -34,9 +33,9 @@
#include <script/standard.h>
#include <uint256.h>
#include <util/bip32.h>
-#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/string.h>
+#include <util/vector.h>
#include <validation.h>
#include <validationinterface.h>
@@ -47,7 +46,6 @@
using node::AnalyzePSBT;
using node::BroadcastTransaction;
-using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
using node::FindCoins;
using node::GetTransaction;
using node::NodeContext;
@@ -61,13 +59,13 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
// Blockchain contextual information (confirmations and blocktime) is not
// available to code in bitcoin-common, so we query them here and push the
// data into the returned UniValue.
- TxToUniv(tx, uint256(), entry, true, RPCSerializationFlags());
+ TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags());
if (!hashBlock.IsNull()) {
LOCK(cs_main);
entry.pushKV("blockhash", hashBlock.GetHex());
- CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock);
+ const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock);
if (pindex) {
if (active_chainstate.m_chain.Contains(pindex)) {
entry.pushKV("confirmations", 1 + active_chainstate.m_chain.Height() - pindex->nHeight);
@@ -80,6 +78,54 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
}
}
+static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc)
+{
+ return {
+ {RPCResult::Type::STR_HEX, "txid", txid_field_doc},
+ {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
+ {RPCResult::Type::NUM, "size", "The serialized transaction size"},
+ {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
+ {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"},
+ {RPCResult::Type::NUM, "version", "The version"},
+ {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
+ {RPCResult::Type::ARR, "vin", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, "The coinbase value (only if coinbase transaction)"},
+ {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id (if not coinbase transaction)"},
+ {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number (if not coinbase transaction)"},
+ {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script (if not coinbase transaction)",
+ {
+ {RPCResult::Type::STR, "asm", "asm"},
+ {RPCResult::Type::STR_HEX, "hex", "hex"},
+ }},
+ {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
+ {
+ {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
+ }},
+ {RPCResult::Type::NUM, "sequence", "The script sequence number"},
+ }},
+ }},
+ {RPCResult::Type::ARR, "vout", "",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
+ {RPCResult::Type::NUM, "n", "index"},
+ {RPCResult::Type::OBJ, "scriptPubKey", "",
+ {
+ {RPCResult::Type::STR, "asm", "the asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
+ {RPCResult::Type::STR_HEX, "hex", "the hex"},
+ {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
+ }},
+ }},
+ }},
+ };
+}
+
static std::vector<RPCArg> CreateTxDoc()
{
return {
@@ -121,7 +167,7 @@ static RPCHelpMan getrawtransaction()
{
return RPCHelpMan{
"getrawtransaction",
- "\nReturn the raw transaction data.\n"
+ "Return the raw transaction data.\n"
"\nBy default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n"
"and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n"
@@ -130,7 +176,7 @@ static RPCHelpMan getrawtransaction()
"\nHint: Use gettransaction for wallet transactions.\n"
"\nIf verbose is 'true', returns an Object with information about 'txid'.\n"
- "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.\n",
+ "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If false, return a string, otherwise return a json object"},
@@ -142,55 +188,16 @@ static RPCHelpMan getrawtransaction()
},
RPCResult{"if verbose is set to true",
RPCResult::Type::OBJ, "", "",
+ Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "in_active_chain", /*optional=*/true, "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"},
- {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
- {RPCResult::Type::STR_HEX, "txid", "The transaction id (same as provided)"},
- {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
- {RPCResult::Type::NUM, "size", "The serialized transaction size"},
- {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
- {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"},
- {RPCResult::Type::NUM, "version", "The version"},
- {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
- {RPCResult::Type::ARR, "vin", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
- {RPCResult::Type::NUM, "vout", "The output number"},
- {RPCResult::Type::OBJ, "scriptSig", "The script",
- {
- {RPCResult::Type::STR, "asm", "asm"},
- {RPCResult::Type::STR_HEX, "hex", "hex"},
- }},
- {RPCResult::Type::NUM, "sequence", "The script sequence number"},
- {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
- {
- {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
- }},
- }},
- }},
- {RPCResult::Type::ARR, "vout", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT},
- {RPCResult::Type::NUM, "n", "index"},
- {RPCResult::Type::OBJ, "scriptPubKey", "",
- {
- {RPCResult::Type::STR, "asm", "the asm"},
- {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR, "hex", "the hex"},
- {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
- }},
- }},
- }},
{RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "the block hash"},
{RPCResult::Type::NUM, "confirmations", /*optional=*/true, "The confirmations"},
{RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "time", /*optional=*/true, "Same as \"blocktime\""},
- }
+ {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
+ },
+ DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")),
},
},
RPCExamples{
@@ -207,7 +214,7 @@ static RPCHelpMan getrawtransaction()
bool in_active_chain = true;
uint256 hash = ParseHashV(request.params[0], "parameter 1");
- CBlockIndex* blockindex = nullptr;
+ const CBlockIndex* blockindex = nullptr;
if (hash == Params().GenesisBlock().hashMerkleRoot) {
// Special exception for the genesis block coinbase transaction
@@ -268,155 +275,6 @@ static RPCHelpMan getrawtransaction()
};
}
-static RPCHelpMan gettxoutproof()
-{
- return RPCHelpMan{"gettxoutproof",
- "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
- "\nNOTE: By default this function only works sometimes. This is when there is an\n"
- "unspent output in the utxo for this transaction. To make it always work,\n"
- "you need to maintain a transaction index, using the -txindex command line option or\n"
- "specify the block in which the transaction is included manually (by blockhash).\n",
- {
- {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
- },
- },
- {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
- },
- RPCResult{
- RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
- },
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- std::set<uint256> setTxids;
- UniValue txids = request.params[0].get_array();
- if (txids.empty()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
- }
- for (unsigned int idx = 0; idx < txids.size(); idx++) {
- auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
- if (!ret.second) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
- }
- }
-
- CBlockIndex* pblockindex = nullptr;
- uint256 hashBlock;
- ChainstateManager& chainman = EnsureAnyChainman(request.context);
- if (!request.params[1].isNull()) {
- LOCK(cs_main);
- hashBlock = ParseHashV(request.params[1], "blockhash");
- pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
- if (!pblockindex) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
- }
- } else {
- LOCK(cs_main);
- CChainState& active_chainstate = chainman.ActiveChainstate();
-
- // Loop through txids and try to find which block they're in. Exit loop once a block is found.
- for (const auto& tx : setTxids) {
- const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
- if (!coin.IsSpent()) {
- pblockindex = active_chainstate.m_chain[coin.nHeight];
- break;
- }
- }
- }
-
-
- // Allow txindex to catch up if we need to query it and before we acquire cs_main.
- if (g_txindex && !pblockindex) {
- g_txindex->BlockUntilSyncedToCurrentChain();
- }
-
- LOCK(cs_main);
-
- if (pblockindex == nullptr) {
- const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
- if (!tx || hashBlock.IsNull()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
- }
- pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
- if (!pblockindex) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
- }
- }
-
- CBlock block;
- if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
- }
-
- unsigned int ntxFound = 0;
- for (const auto& tx : block.vtx) {
- if (setTxids.count(tx->GetHash())) {
- ntxFound++;
- }
- }
- if (ntxFound != setTxids.size()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
- }
-
- CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
- CMerkleBlock mb(block, setTxids);
- ssMB << mb;
- std::string strHex = HexStr(ssMB);
- return strHex;
-},
- };
-}
-
-static RPCHelpMan verifytxoutproof()
-{
- return RPCHelpMan{"verifytxoutproof",
- "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
- "and throwing an RPC error if the block is not in our best chain\n",
- {
- {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
- },
- RPCResult{
- RPCResult::Type::ARR, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
- }
- },
- RPCExamples{""},
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
- CMerkleBlock merkleBlock;
- ssMB >> merkleBlock;
-
- UniValue res(UniValue::VARR);
-
- std::vector<uint256> vMatch;
- std::vector<unsigned int> vIndex;
- if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
- return res;
-
- ChainstateManager& chainman = EnsureAnyChainman(request.context);
- LOCK(cs_main);
-
- const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
- if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
- }
-
- // Check if proof is valid, only add results if so
- if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
- for (const uint256& hash : vMatch) {
- res.push_back(hash.GetHex());
- }
- }
-
- return res;
-},
- };
-}
-
static RPCHelpMan createrawtransaction()
{
return RPCHelpMan{"createrawtransaction",
@@ -459,7 +317,7 @@ static RPCHelpMan createrawtransaction()
static RPCHelpMan decoderawtransaction()
{
return RPCHelpMan{"decoderawtransaction",
- "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n",
+ "Return a JSON object representing the serialized, hex-encoded transaction.",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"},
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
@@ -472,50 +330,7 @@ static RPCHelpMan decoderawtransaction()
},
RPCResult{
RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
- {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
- {RPCResult::Type::NUM, "size", "The transaction size"},
- {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
- {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4 - 3 and vsize*4)"},
- {RPCResult::Type::NUM, "version", "The version"},
- {RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
- {RPCResult::Type::ARR, "vin", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, ""},
- {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id"},
- {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number"},
- {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script",
- {
- {RPCResult::Type::STR, "asm", "asm"},
- {RPCResult::Type::STR_HEX, "hex", "hex"},
- }},
- {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
- {
- {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
- }},
- {RPCResult::Type::NUM, "sequence", "The script sequence number"},
- }},
- }},
- {RPCResult::Type::ARR, "vout", "",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT},
- {RPCResult::Type::NUM, "n", "index"},
- {RPCResult::Type::OBJ, "scriptPubKey", "",
- {
- {RPCResult::Type::STR, "asm", "the asm"},
- {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "the hex"},
- {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
- {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
- }},
- }},
- }},
- }
+ DecodeTxDoc(/*txid_field_doc=*/"The transaction id"),
},
RPCExamples{
HelpExampleCli("decoderawtransaction", "\"hexstring\"")
@@ -535,7 +350,7 @@ static RPCHelpMan decoderawtransaction()
}
UniValue result(UniValue::VOBJ);
- TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false);
+ TxToUniv(CTransaction(std::move(mtx)), /*block_hash=*/uint256(), /*entry=*/result, /*include_hex=*/false);
return result;
},
@@ -587,7 +402,7 @@ static RPCHelpMan decodescript()
} else {
// Empty scripts are valid
}
- ScriptPubKeyToUniv(script, r, /* include_hex */ false);
+ ScriptToUniv(script, /*out=*/r, /*include_hex=*/false, /*include_address=*/true);
std::vector<std::vector<unsigned char>> solutions_data;
const TxoutType which_type{Solver(script, solutions_data)};
@@ -664,7 +479,7 @@ static RPCHelpMan decodescript()
// Scripts that are not fit for P2WPKH are encoded as P2WSH.
segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script));
}
- ScriptPubKeyToUniv(segwitScr, sr, /* include_hex */ true);
+ ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true);
sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr)));
r.pushKV("segwit", sr);
}
@@ -864,210 +679,6 @@ static RPCHelpMan signrawtransactionwithkey()
};
}
-static RPCHelpMan sendrawtransaction()
-{
- return RPCHelpMan{"sendrawtransaction",
- "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
- "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
- "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
- "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
- "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
- "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
- {
- {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
- {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
- "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
- "/kvB.\nSet to 0 to accept any fee rate.\n"},
- },
- RPCResult{
- RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
- },
- RPCExamples{
- "\nCreate a transaction\n"
- + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
- "Sign the transaction, and get back the hex\n"
- + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
- "\nSend the transaction (signed hex)\n"
- + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {
- UniValue::VSTR,
- UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
- });
-
- CMutableTransaction mtx;
- if (!DecodeHexTx(mtx, request.params[0].get_str())) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
- }
- CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
-
- const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
- DEFAULT_MAX_RAW_TX_FEE_RATE :
- CFeeRate(AmountFromValue(request.params[1]));
-
- int64_t virtual_size = GetVirtualTransactionSize(*tx);
- CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
-
- std::string err_string;
- AssertLockNotHeld(cs_main);
- NodeContext& node = EnsureAnyNodeContext(request.context);
- const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true);
- if (TransactionError::OK != err) {
- throw JSONRPCTransactionError(err, err_string);
- }
-
- return tx->GetHash().GetHex();
-},
- };
-}
-
-static RPCHelpMan testmempoolaccept()
-{
- return RPCHelpMan{"testmempoolaccept",
- "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n"
- "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n"
- "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n"
- "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n"
- "\nThis checks if transactions violate the consensus or policy rules.\n"
- "\nSee sendrawtransaction call.\n",
- {
- {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.",
- {
- {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
- },
- },
- {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
- "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"},
- },
- RPCResult{
- RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
- "Returns results for each transaction in the same order they were passed in.\n"
- "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n",
- {
- {RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
- {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
- {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."},
- {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. "
- "If not present, the tx was not fully validated due to a failure in another tx in the list."},
- {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"},
- {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)",
- {
- {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT},
- }},
- {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"},
- }},
- }
- },
- RPCExamples{
- "\nCreate a transaction\n"
- + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
- "Sign the transaction, and get back the hex\n"
- + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
- "\nTest acceptance of the transaction (signed hex)\n"
- + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") +
- "\nAs a JSON-RPC call\n"
- + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- RPCTypeCheck(request.params, {
- UniValue::VARR,
- UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
- });
- const UniValue raw_transactions = request.params[0].get_array();
- if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
- throw JSONRPCError(RPC_INVALID_PARAMETER,
- "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
- }
-
- const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
- DEFAULT_MAX_RAW_TX_FEE_RATE :
- CFeeRate(AmountFromValue(request.params[1]));
-
- std::vector<CTransactionRef> txns;
- txns.reserve(raw_transactions.size());
- for (const auto& rawtx : raw_transactions.getValues()) {
- CMutableTransaction mtx;
- if (!DecodeHexTx(mtx, rawtx.get_str())) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
- "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
- }
- txns.emplace_back(MakeTransactionRef(std::move(mtx)));
- }
-
- NodeContext& node = EnsureAnyNodeContext(request.context);
- CTxMemPool& mempool = EnsureMemPool(node);
- ChainstateManager& chainman = EnsureChainman(node);
- CChainState& chainstate = chainman.ActiveChainstate();
- const PackageMempoolAcceptResult package_result = [&] {
- LOCK(::cs_main);
- if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true);
- return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
- chainman.ProcessTransaction(txns[0], /*test_accept=*/ true));
- }();
-
- UniValue rpc_result(UniValue::VARR);
- // We will check transaction fees while we iterate through txns in order. If any transaction fee
- // exceeds maxfeerate, we will leave the rest of the validation results blank, because it
- // doesn't make sense to return a validation result for a transaction if its ancestor(s) would
- // not be submitted.
- bool exit_early{false};
- for (const auto& tx : txns) {
- UniValue result_inner(UniValue::VOBJ);
- result_inner.pushKV("txid", tx->GetHash().GetHex());
- result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex());
- if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) {
- result_inner.pushKV("package-error", package_result.m_state.GetRejectReason());
- }
- auto it = package_result.m_tx_results.find(tx->GetWitnessHash());
- if (exit_early || it == package_result.m_tx_results.end()) {
- // Validation unfinished. Just return the txid and wtxid.
- rpc_result.push_back(result_inner);
- continue;
- }
- const auto& tx_result = it->second;
- // Package testmempoolaccept doesn't allow transactions to already be in the mempool.
- CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
- if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
- const CAmount fee = tx_result.m_base_fees.value();
- // Check that fee does not exceed maximum fee
- const int64_t virtual_size = tx_result.m_vsize.value();
- const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
- if (max_raw_tx_fee && fee > max_raw_tx_fee) {
- result_inner.pushKV("allowed", false);
- result_inner.pushKV("reject-reason", "max-fee-exceeded");
- exit_early = true;
- } else {
- // Only return the fee and vsize if the transaction would pass ATMP.
- // These can be used to calculate the feerate.
- result_inner.pushKV("allowed", true);
- result_inner.pushKV("vsize", virtual_size);
- UniValue fees(UniValue::VOBJ);
- fees.pushKV("base", ValueFromAmount(fee));
- result_inner.pushKV("fees", fees);
- }
- } else {
- result_inner.pushKV("allowed", false);
- const TxValidationState state = tx_result.m_state;
- if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
- result_inner.pushKV("reject-reason", "missing-inputs");
- } else {
- result_inner.pushKV("reject-reason", state.GetRejectReason());
- }
- }
- rpc_result.push_back(result_inner);
- }
- return rpc_result;
-},
- };
-}
-
static RPCHelpMan decodepsbt()
{
return RPCHelpMan{
@@ -1121,6 +732,7 @@ static RPCHelpMan decodepsbt()
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
{RPCResult::Type::STR, "asm", "The asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR_HEX, "hex", "The hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
@@ -1255,7 +867,7 @@ static RPCHelpMan decodepsbt()
// Add the decoded tx
UniValue tx_univ(UniValue::VOBJ);
- TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false);
+ TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
result.pushKV("tx", tx_univ);
// Add the global xpubs
@@ -1311,7 +923,7 @@ static RPCHelpMan decodepsbt()
txout = input.witness_utxo;
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(txout.scriptPubKey, o, /* include_hex */ true);
+ ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
UniValue out(UniValue::VOBJ);
out.pushKV("amount", ValueFromAmount(txout.nValue));
@@ -1325,7 +937,7 @@ static RPCHelpMan decodepsbt()
txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n];
UniValue non_wit(UniValue::VOBJ);
- TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false);
+ TxToUniv(*input.non_witness_utxo, /*block_hash=*/uint256(), /*entry=*/non_wit, /*include_hex=*/false);
in.pushKV("non_witness_utxo", non_wit);
have_a_utxo = true;
@@ -1358,12 +970,12 @@ static RPCHelpMan decodepsbt()
// Redeem script and witness script
if (!input.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(input.redeem_script, r);
+ ScriptToUniv(input.redeem_script, /*out=*/r);
in.pushKV("redeem_script", r);
}
if (!input.witness_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(input.witness_script, r);
+ ScriptToUniv(input.witness_script, /*out=*/r);
in.pushKV("witness_script", r);
}
@@ -1468,12 +1080,12 @@ static RPCHelpMan decodepsbt()
// Redeem script and witness script
if (!output.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(output.redeem_script, r);
+ ScriptToUniv(output.redeem_script, /*out=*/r);
out.pushKV("redeem_script", r);
}
if (!output.witness_script.empty()) {
UniValue r(UniValue::VOBJ);
- ScriptToUniv(output.witness_script, r);
+ ScriptToUniv(output.witness_script, /*out=*/r);
out.pushKV("witness_script", r);
}
@@ -2077,10 +1689,8 @@ static const CRPCCommand commands[] =
{ "rawtransactions", &createrawtransaction, },
{ "rawtransactions", &decoderawtransaction, },
{ "rawtransactions", &decodescript, },
- { "rawtransactions", &sendrawtransaction, },
{ "rawtransactions", &combinerawtransaction, },
{ "rawtransactions", &signrawtransactionwithkey, },
- { "rawtransactions", &testmempoolaccept, },
{ "rawtransactions", &decodepsbt, },
{ "rawtransactions", &combinepsbt, },
{ "rawtransactions", &finalizepsbt, },
@@ -2089,9 +1699,6 @@ static const CRPCCommand commands[] =
{ "rawtransactions", &utxoupdatepsbt, },
{ "rawtransactions", &joinpsbts, },
{ "rawtransactions", &analyzepsbt, },
-
- { "blockchain", &gettxoutproof, },
- { "blockchain", &verifytxoutproof, },
};
// clang-format on
for (const auto& c : commands) {
diff --git a/src/rpc/register.h b/src/rpc/register.h
index cc3a5e0a63..5a604ad428 100644
--- a/src/rpc/register.h
+++ b/src/rpc/register.h
@@ -9,25 +9,20 @@
* headers for everything under src/rpc/ */
class CRPCTable;
-/** Register block chain RPC commands */
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
-/** Register mempool RPC commands */
void RegisterMempoolRPCCommands(CRPCTable&);
-/** Register P2P networking RPC commands */
+void RegisterTxoutProofRPCCommands(CRPCTable&);
void RegisterNetRPCCommands(CRPCTable &tableRPC);
-/** Register miscellaneous RPC commands */
void RegisterMiscRPCCommands(CRPCTable &tableRPC);
-/** Register mining RPC commands */
void RegisterMiningRPCCommands(CRPCTable &tableRPC);
-/** Register raw transaction RPC commands */
void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC);
-/** Register raw transaction RPC commands */
void RegisterSignerRPCCommands(CRPCTable &tableRPC);
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);
RegisterMempoolRPCCommands(t);
+ RegisterTxoutProofRPCCommands(t);
RegisterNetRPCCommands(t);
RegisterMiscRPCCommands(t);
RegisterMiningRPCCommands(t);
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index c70236cc1c..333ed6f5da 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -167,7 +167,7 @@ static RPCHelpMan stop()
// to the client (intended for testing)
"\nRequest a graceful shutdown of " PACKAGE_NAME ".",
{
- {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /* hidden */ true},
+ {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /*hidden=*/true},
},
RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
RPCExamples{""},
diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp
new file mode 100644
index 0000000000..a5443b0329
--- /dev/null
+++ b/src/rpc/txoutproof.cpp
@@ -0,0 +1,183 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-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.
+
+#include <chain.h>
+#include <chainparams.h>
+#include <coins.h>
+#include <index/txindex.h>
+#include <merkleblock.h>
+#include <node/blockstorage.h>
+#include <primitives/transaction.h>
+#include <rpc/server.h>
+#include <rpc/server_util.h>
+#include <rpc/util.h>
+#include <univalue.h>
+#include <util/strencodings.h>
+#include <validation.h>
+
+using node::GetTransaction;
+using node::ReadBlockFromDisk;
+
+static RPCHelpMan gettxoutproof()
+{
+ return RPCHelpMan{"gettxoutproof",
+ "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n"
+ "\nNOTE: By default this function only works sometimes. This is when there is an\n"
+ "unspent output in the utxo for this transaction. To make it always work,\n"
+ "you need to maintain a transaction index, using the -txindex command line option or\n"
+ "specify the block in which the transaction is included manually (by blockhash).\n",
+ {
+ {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
+ },
+ },
+ {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
+ },
+ RPCResult{
+ RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
+ },
+ RPCExamples{""},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::set<uint256> setTxids;
+ UniValue txids = request.params[0].get_array();
+ if (txids.empty()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
+ }
+ for (unsigned int idx = 0; idx < txids.size(); idx++) {
+ auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
+ if (!ret.second) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
+ }
+ }
+
+ const CBlockIndex* pblockindex = nullptr;
+ uint256 hashBlock;
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
+ if (!request.params[1].isNull()) {
+ LOCK(cs_main);
+ hashBlock = ParseHashV(request.params[1], "blockhash");
+ pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
+ if (!pblockindex) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
+ }
+ } else {
+ LOCK(cs_main);
+ CChainState& active_chainstate = chainman.ActiveChainstate();
+
+ // Loop through txids and try to find which block they're in. Exit loop once a block is found.
+ for (const auto& tx : setTxids) {
+ const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx);
+ if (!coin.IsSpent()) {
+ pblockindex = active_chainstate.m_chain[coin.nHeight];
+ break;
+ }
+ }
+ }
+
+
+ // Allow txindex to catch up if we need to query it and before we acquire cs_main.
+ if (g_txindex && !pblockindex) {
+ g_txindex->BlockUntilSyncedToCurrentChain();
+ }
+
+ LOCK(cs_main);
+
+ if (pblockindex == nullptr) {
+ const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
+ if (!tx || hashBlock.IsNull()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
+ }
+ pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock);
+ if (!pblockindex) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
+ }
+ }
+
+ CBlock block;
+ if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
+ }
+
+ unsigned int ntxFound = 0;
+ for (const auto& tx : block.vtx) {
+ if (setTxids.count(tx->GetHash())) {
+ ntxFound++;
+ }
+ }
+ if (ntxFound != setTxids.size()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
+ }
+
+ CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
+ CMerkleBlock mb(block, setTxids);
+ ssMB << mb;
+ std::string strHex = HexStr(ssMB);
+ return strHex;
+ },
+ };
+}
+
+static RPCHelpMan verifytxoutproof()
+{
+ return RPCHelpMan{"verifytxoutproof",
+ "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n"
+ "and throwing an RPC error if the block is not in our best chain\n",
+ {
+ {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."},
+ }
+ },
+ RPCExamples{""},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
+ CMerkleBlock merkleBlock;
+ ssMB >> merkleBlock;
+
+ UniValue res(UniValue::VARR);
+
+ std::vector<uint256> vMatch;
+ std::vector<unsigned int> vIndex;
+ if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot)
+ return res;
+
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
+ LOCK(cs_main);
+
+ const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash());
+ if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
+ }
+
+ // Check if proof is valid, only add results if so
+ if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
+ for (const uint256& hash : vMatch) {
+ res.push_back(hash.GetHex());
+ }
+ }
+
+ return res;
+ },
+ };
+}
+
+void RegisterTxoutProofRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ // category actor (function)
+ // -------- ----------------
+ {"blockchain", &gettxoutproof},
+ {"blockchain", &verifytxoutproof},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 7c859268be..01fae140cc 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -421,7 +421,7 @@ struct Sections {
if (arg.m_type_str.size() != 0 && push_name) {
left += "\"" + arg.GetName() + "\": " + arg.m_type_str.at(0);
} else {
- left += push_name ? arg.ToStringObj(/* oneline */ false) : arg.ToString(/* oneline */ false);
+ left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false);
}
left += ",";
PushSection({left, arg.ToDescriptionString()});
@@ -627,7 +627,7 @@ std::string RPCHelpMan::ToString() const
if (was_optional) ret += ") ";
was_optional = false;
}
- ret += arg.ToString(/* oneline */ true);
+ ret += arg.ToString(/*oneline=*/true);
}
if (was_optional) ret += " )";
@@ -774,7 +774,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
// Elements in a JSON structure (dictionary or array) are separated by a comma
const std::string maybe_separator{outer_type != OuterType::NONE ? "," : ""};
- // The key name if recursed into an dictionary
+ // The key name if recursed into a dictionary
const std::string maybe_key{
outer_type == OuterType::OBJ ?
"\"" + this->m_key_name + "\" : " :
@@ -865,10 +865,11 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
bool RPCResult::MatchesType(const UniValue& result) const
{
- switch (m_type) {
- case Type::ELISION: {
- return false;
+ if (m_skip_type_check) {
+ return true;
}
+ switch (m_type) {
+ case Type::ELISION:
case Type::ANY: {
return true;
}
@@ -889,11 +890,52 @@ bool RPCResult::MatchesType(const UniValue& result) const
}
case Type::ARR_FIXED:
case Type::ARR: {
- return UniValue::VARR == result.getType();
+ if (UniValue::VARR != result.getType()) return false;
+ for (size_t i{0}; i < result.get_array().size(); ++i) {
+ // If there are more results than documented, re-use the last doc_inner.
+ const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))};
+ if (!doc_inner.MatchesType(result.get_array()[i])) return false;
+ }
+ return true; // empty result array is valid
}
case Type::OBJ_DYN:
case Type::OBJ: {
- return UniValue::VOBJ == result.getType();
+ if (UniValue::VOBJ != result.getType()) return false;
+ if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true;
+ if (m_type == Type::OBJ_DYN) {
+ const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first
+ for (size_t i{0}; i < result.get_obj().size(); ++i) {
+ if (!doc_inner.MatchesType(result.get_obj()[i])) {
+ return false;
+ }
+ }
+ return true; // empty result obj is valid
+ }
+ std::set<std::string> doc_keys;
+ for (const auto& doc_entry : m_inner) {
+ doc_keys.insert(doc_entry.m_key_name);
+ }
+ std::map<std::string, UniValue> result_obj;
+ result.getObjMap(result_obj);
+ for (const auto& result_entry : result_obj) {
+ if (doc_keys.find(result_entry.first) == doc_keys.end()) {
+ return false; // missing documentation
+ }
+ }
+
+ for (const auto& doc_entry : m_inner) {
+ const auto result_it{result_obj.find(doc_entry.m_key_name)};
+ if (result_it == result_obj.end()) {
+ if (!doc_entry.m_optional) {
+ return false; // result is missing a required key
+ }
+ continue;
+ }
+ if (!doc_entry.MatchesType(result_it->second)) {
+ return false; // wrong type
+ }
+ }
+ return true;
}
} // no default case, so the compiler can warn about missing cases
CHECK_NONFATAL(false);
diff --git a/src/rpc/util.h b/src/rpc/util.h
index 89d32d4193..e16fed75bc 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -256,6 +256,7 @@ struct RPCResult {
const std::string m_key_name; //!< Only used for dicts
const std::vector<RPCResult> m_inner; //!< Only used for arrays or dicts
const bool m_optional;
+ const bool m_skip_type_check;
const std::string m_description;
const std::string m_cond;
@@ -270,6 +271,7 @@ struct RPCResult {
m_key_name{std::move(m_key_name)},
m_inner{std::move(inner)},
m_optional{optional},
+ m_skip_type_check{false},
m_description{std::move(description)},
m_cond{std::move(cond)}
{
@@ -290,11 +292,13 @@ struct RPCResult {
const std::string m_key_name,
const bool optional,
const std::string description,
- const std::vector<RPCResult> inner = {})
+ const std::vector<RPCResult> inner = {},
+ bool skip_type_check = false)
: m_type{std::move(type)},
m_key_name{std::move(m_key_name)},
m_inner{std::move(inner)},
m_optional{optional},
+ m_skip_type_check{skip_type_check},
m_description{std::move(description)},
m_cond{}
{
@@ -305,8 +309,9 @@ struct RPCResult {
const Type type,
const std::string m_key_name,
const std::string description,
- const std::vector<RPCResult> inner = {})
- : RPCResult{type, m_key_name, false, description, inner} {}
+ const std::vector<RPCResult> inner = {},
+ bool skip_type_check = false)
+ : RPCResult{type, m_key_name, false, description, inner, skip_type_check} {}
/** Append the sections of the result. */
void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, const int current_indent = 0) const;
diff --git a/src/scheduler.cpp b/src/scheduler.cpp
index 0b2ad3c553..197d009f7c 100644
--- a/src/scheduler.cpp
+++ b/src/scheduler.cpp
@@ -111,7 +111,7 @@ static void Repeat(CScheduler& s, CScheduler::Function f, std::chrono::milliseco
void CScheduler::scheduleEvery(CScheduler::Function f, std::chrono::milliseconds delta)
{
- scheduleFromNow([=] { Repeat(*this, f, delta); }, delta);
+ scheduleFromNow([this, f, delta] { Repeat(*this, f, delta); }, delta);
}
size_t CScheduler::getQueueInfo(std::chrono::system_clock::time_point& first,
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 23540f6aef..cece0b60ce 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -1066,13 +1066,19 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
auto expr = Expr(sp);
if (Func("pk", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
- if (!pubkey) return nullptr;
+ if (!pubkey) {
+ error = strprintf("pk(): %s", error);
+ return nullptr;
+ }
++key_exp_index;
return std::make_unique<PKDescriptor>(std::move(pubkey), ctx == ParseScriptContext::P2TR);
}
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && Func("pkh", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
- if (!pubkey) return nullptr;
+ if (!pubkey) {
+ error = strprintf("pkh(): %s", error);
+ return nullptr;
+ }
++key_exp_index;
return std::make_unique<PKHDescriptor>(std::move(pubkey));
} else if (Func("pkh", expr)) {
@@ -1081,7 +1087,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
}
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
- if (!pubkey) return nullptr;
+ if (!pubkey) {
+ error = strprintf("combo(): %s", error);
+ return nullptr;
+ }
++key_exp_index;
return std::make_unique<ComboDescriptor>(std::move(pubkey));
} else if (Func("combo", expr)) {
@@ -1109,7 +1118,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
}
auto arg = Expr(expr);
auto pk = ParsePubkey(key_exp_index, arg, ctx, out, error);
- if (!pk) return nullptr;
+ if (!pk) {
+ error = strprintf("Multi: %s", error);
+ return nullptr;
+ }
script_size += pk->GetSize() + 1;
providers.emplace_back(std::move(pk));
key_exp_index++;
@@ -1154,7 +1166,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
}
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error);
- if (!pubkey) return nullptr;
+ if (!pubkey) {
+ error = strprintf("wpkh(): %s", error);
+ return nullptr;
+ }
key_exp_index++;
return std::make_unique<WPKHDescriptor>(std::move(pubkey));
} else if (Func("wpkh", expr)) {
@@ -1191,7 +1206,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
if (ctx == ParseScriptContext::TOP && Func("tr", expr)) {
auto arg = Expr(expr);
auto internal_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error);
- if (!internal_key) return nullptr;
+ if (!internal_key) {
+ error = strprintf("tr(): %s", error);
+ return nullptr;
+ }
++key_exp_index;
std::vector<std::unique_ptr<DescriptorImpl>> subscripts; //!< list of script subexpressions
std::vector<int> depths; //!< depth in the tree of each subexpression (same length subscripts)
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index 11b1a1c887..c4d13d7283 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -225,31 +225,6 @@ bool static CheckPubKeyEncoding(const valtype &vchPubKey, unsigned int flags, co
return true;
}
-bool CheckMinimalPush(const valtype& data, opcodetype opcode) {
- // Excludes OP_1NEGATE, OP_1-16 since they are by definition minimal
- assert(0 <= opcode && opcode <= OP_PUSHDATA4);
- if (data.size() == 0) {
- // Should have used OP_0.
- return opcode == OP_0;
- } else if (data.size() == 1 && data[0] >= 1 && data[0] <= 16) {
- // Should have used OP_1 .. OP_16.
- return false;
- } else if (data.size() == 1 && data[0] == 0x81) {
- // Should have used OP_1NEGATE.
- return false;
- } else if (data.size() <= 75) {
- // Must have used a direct push (opcode indicating number of bytes pushed + those bytes).
- return opcode == data.size();
- } else if (data.size() <= 255) {
- // Must have used OP_PUSHDATA.
- return opcode == OP_PUSHDATA1;
- } else if (data.size() <= 65535) {
- // Must have used OP_PUSHDATA2.
- return opcode == OP_PUSHDATA2;
- }
- return true;
-}
-
int FindAndDelete(CScript& script, const CScript& b)
{
int nFound = 0;
@@ -2009,7 +1984,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
}
- if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ false)) {
+ if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/false)) {
return false;
}
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
@@ -2054,7 +2029,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
// reintroduce malleability.
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
}
- if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ true)) {
+ if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/true)) {
return false;
}
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index cf1953ad22..fbd904fb7b 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -344,8 +344,6 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags);
-bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode);
-
int FindAndDelete(CScript& script, const CScript& b);
#endif // BITCOIN_SCRIPT_INTERPRETER_H
diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp
new file mode 100644
index 0000000000..d0bb937885
--- /dev/null
+++ b/src/script/miniscript.cpp
@@ -0,0 +1,348 @@
+// Copyright (c) 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.
+
+#include <string>
+#include <vector>
+#include <script/script.h>
+#include <script/standard.h>
+#include <script/miniscript.h>
+
+#include <assert.h>
+
+namespace miniscript {
+namespace internal {
+
+Type SanitizeType(Type e) {
+ int num_types = (e << "K"_mst) + (e << "V"_mst) + (e << "B"_mst) + (e << "W"_mst);
+ if (num_types == 0) return ""_mst; // No valid type, don't care about the rest
+ assert(num_types == 1); // K, V, B, W all conflict with each other
+ bool ok = // Work around a GCC 4.8 bug that breaks user-defined literals in macro calls.
+ (!(e << "z"_mst) || !(e << "o"_mst)) && // z conflicts with o
+ (!(e << "n"_mst) || !(e << "z"_mst)) && // n conflicts with z
+ (!(e << "n"_mst) || !(e << "W"_mst)) && // n conflicts with W
+ (!(e << "V"_mst) || !(e << "d"_mst)) && // V conflicts with d
+ (!(e << "K"_mst) || (e << "u"_mst)) && // K implies u
+ (!(e << "V"_mst) || !(e << "u"_mst)) && // V conflicts with u
+ (!(e << "e"_mst) || !(e << "f"_mst)) && // e conflicts with f
+ (!(e << "e"_mst) || (e << "d"_mst)) && // e implies d
+ (!(e << "V"_mst) || !(e << "e"_mst)) && // V conflicts with e
+ (!(e << "d"_mst) || !(e << "f"_mst)) && // d conflicts with f
+ (!(e << "V"_mst) || (e << "f"_mst)) && // V implies f
+ (!(e << "K"_mst) || (e << "s"_mst)) && // K implies s
+ (!(e << "z"_mst) || (e << "m"_mst)); // z implies m
+ assert(ok);
+ return e;
+}
+
+Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) {
+ // Sanity check on data
+ if (nodetype == Fragment::SHA256 || nodetype == Fragment::HASH256) {
+ assert(data_size == 32);
+ } else if (nodetype == Fragment::RIPEMD160 || nodetype == Fragment::HASH160) {
+ assert(data_size == 20);
+ } else {
+ assert(data_size == 0);
+ }
+ // Sanity check on k
+ if (nodetype == Fragment::OLDER || nodetype == Fragment::AFTER) {
+ assert(k >= 1 && k < 0x80000000UL);
+ } else if (nodetype == Fragment::MULTI) {
+ assert(k >= 1 && k <= n_keys);
+ } else if (nodetype == Fragment::THRESH) {
+ assert(k >= 1 && k <= n_subs);
+ } else {
+ assert(k == 0);
+ }
+ // Sanity check on subs
+ if (nodetype == Fragment::AND_V || nodetype == Fragment::AND_B || nodetype == Fragment::OR_B ||
+ nodetype == Fragment::OR_C || nodetype == Fragment::OR_I || nodetype == Fragment::OR_D) {
+ assert(n_subs == 2);
+ } else if (nodetype == Fragment::ANDOR) {
+ assert(n_subs == 3);
+ } else if (nodetype == Fragment::WRAP_A || nodetype == Fragment::WRAP_S || nodetype == Fragment::WRAP_C ||
+ nodetype == Fragment::WRAP_D || nodetype == Fragment::WRAP_V || nodetype == Fragment::WRAP_J ||
+ nodetype == Fragment::WRAP_N) {
+ assert(n_subs == 1);
+ } else if (nodetype != Fragment::THRESH) {
+ assert(n_subs == 0);
+ }
+ // Sanity check on keys
+ if (nodetype == Fragment::PK_K || nodetype == Fragment::PK_H) {
+ assert(n_keys == 1);
+ } else if (nodetype == Fragment::MULTI) {
+ assert(n_keys >= 1 && n_keys <= 20);
+ } else {
+ assert(n_keys == 0);
+ }
+
+ // Below is the per-nodetype logic for computing the expression types.
+ // It heavily relies on Type's << operator (where "X << a_mst" means
+ // "X has all properties listed in a").
+ switch (nodetype) {
+ case Fragment::PK_K: return "Konudemsxk"_mst;
+ case Fragment::PK_H: return "Knudemsxk"_mst;
+ case Fragment::OLDER: return
+ "g"_mst.If(k & CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) |
+ "h"_mst.If(!(k & CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG)) |
+ "Bzfmxk"_mst;
+ case Fragment::AFTER: return
+ "i"_mst.If(k >= LOCKTIME_THRESHOLD) |
+ "j"_mst.If(k < LOCKTIME_THRESHOLD) |
+ "Bzfmxk"_mst;
+ case Fragment::SHA256: return "Bonudmk"_mst;
+ case Fragment::RIPEMD160: return "Bonudmk"_mst;
+ case Fragment::HASH256: return "Bonudmk"_mst;
+ case Fragment::HASH160: return "Bonudmk"_mst;
+ case Fragment::JUST_1: return "Bzufmxk"_mst;
+ case Fragment::JUST_0: return "Bzudemsxk"_mst;
+ case Fragment::WRAP_A: return
+ "W"_mst.If(x << "B"_mst) | // W=B_x
+ (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
+ (x & "udfems"_mst) | // u=u_x, d=d_x, f=f_x, e=e_x, m=m_x, s=s_x
+ "x"_mst; // x
+ case Fragment::WRAP_S: return
+ "W"_mst.If(x << "Bo"_mst) | // W=B_x*o_x
+ (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
+ (x & "udfemsx"_mst); // u=u_x, d=d_x, f=f_x, e=e_x, m=m_x, s=s_x, x=x_x
+ case Fragment::WRAP_C: return
+ "B"_mst.If(x << "K"_mst) | // B=K_x
+ (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
+ (x & "ondfem"_mst) | // o=o_x, n=n_x, d=d_x, f=f_x, e=e_x, m=m_x
+ "us"_mst; // u, s
+ case Fragment::WRAP_D: return
+ "B"_mst.If(x << "Vz"_mst) | // B=V_x*z_x
+ "o"_mst.If(x << "z"_mst) | // o=z_x
+ "e"_mst.If(x << "f"_mst) | // e=f_x
+ (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
+ (x & "ms"_mst) | // m=m_x, s=s_x
+ "nudx"_mst; // n, u, d, x
+ case Fragment::WRAP_V: return
+ "V"_mst.If(x << "B"_mst) | // V=B_x
+ (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
+ (x & "zonms"_mst) | // z=z_x, o=o_x, n=n_x, m=m_x, s=s_x
+ "fx"_mst; // f, x
+ case Fragment::WRAP_J: return
+ "B"_mst.If(x << "Bn"_mst) | // B=B_x*n_x
+ "e"_mst.If(x << "f"_mst) | // e=f_x
+ (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
+ (x & "oums"_mst) | // o=o_x, u=u_x, m=m_x, s=s_x
+ "ndx"_mst; // n, d, x
+ case Fragment::WRAP_N: return
+ (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
+ (x & "Bzondfems"_mst) | // B=B_x, z=z_x, o=o_x, n=n_x, d=d_x, f=f_x, e=e_x, m=m_x, s=s_x
+ "ux"_mst; // u, x
+ case Fragment::AND_V: return
+ (y & "KVB"_mst).If(x << "V"_mst) | // B=V_x*B_y, V=V_x*V_y, K=V_x*K_y
+ (x & "n"_mst) | (y & "n"_mst).If(x << "z"_mst) | // n=n_x+z_x*n_y
+ ((x | y) & "o"_mst).If((x | y) << "z"_mst) | // o=o_x*z_y+z_x*o_y
+ (x & y & "dmz"_mst) | // d=d_x*d_y, m=m_x*m_y, z=z_x*z_y
+ ((x | y) & "s"_mst) | // s=s_x+s_y
+ "f"_mst.If((y << "f"_mst) || (x << "s"_mst)) | // f=f_y+s_x
+ (y & "ux"_mst) | // u=u_y, x=x_y
+ ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y
+ "k"_mst.If(((x & y) << "k"_mst) &&
+ !(((x << "g"_mst) && (y << "h"_mst)) ||
+ ((x << "h"_mst) && (y << "g"_mst)) ||
+ ((x << "i"_mst) && (y << "j"_mst)) ||
+ ((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*!(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y)
+ case Fragment::AND_B: return
+ (x & "B"_mst).If(y << "W"_mst) | // B=B_x*W_y
+ ((x | y) & "o"_mst).If((x | y) << "z"_mst) | // o=o_x*z_y+z_x*o_y
+ (x & "n"_mst) | (y & "n"_mst).If(x << "z"_mst) | // n=n_x+z_x*n_y
+ (x & y & "e"_mst).If((x & y) << "s"_mst) | // e=e_x*e_y*s_x*s_y
+ (x & y & "dzm"_mst) | // d=d_x*d_y, z=z_x*z_y, m=m_x*m_y
+ "f"_mst.If(((x & y) << "f"_mst) || (x << "sf"_mst) || (y << "sf"_mst)) | // f=f_x*f_y + f_x*s_x + f_y*s_y
+ ((x | y) & "s"_mst) | // s=s_x+s_y
+ "ux"_mst | // u, x
+ ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y
+ "k"_mst.If(((x & y) << "k"_mst) &&
+ !(((x << "g"_mst) && (y << "h"_mst)) ||
+ ((x << "h"_mst) && (y << "g"_mst)) ||
+ ((x << "i"_mst) && (y << "j"_mst)) ||
+ ((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*!(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y)
+ case Fragment::OR_B: return
+ "B"_mst.If(x << "Bd"_mst && y << "Wd"_mst) | // B=B_x*d_x*W_x*d_y
+ ((x | y) & "o"_mst).If((x | y) << "z"_mst) | // o=o_x*z_y+z_x*o_y
+ (x & y & "m"_mst).If((x | y) << "s"_mst && (x & y) << "e"_mst) | // m=m_x*m_y*e_x*e_y*(s_x+s_y)
+ (x & y & "zse"_mst) | // z=z_x*z_y, s=s_x*s_y, e=e_x*e_y
+ "dux"_mst | // d, u, x
+ ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y
+ (x & y & "k"_mst); // k=k_x*k_y
+ case Fragment::OR_D: return
+ (y & "B"_mst).If(x << "Bdu"_mst) | // B=B_y*B_x*d_x*u_x
+ (x & "o"_mst).If(y << "z"_mst) | // o=o_x*z_y
+ (x & y & "m"_mst).If(x << "e"_mst && (x | y) << "s"_mst) | // m=m_x*m_y*e_x*(s_x+s_y)
+ (x & y & "zes"_mst) | // z=z_x*z_y, e=e_x*e_y, s=s_x*s_y
+ (y & "ufd"_mst) | // u=u_y, f=f_y, d=d_y
+ "x"_mst | // x
+ ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y
+ (x & y & "k"_mst); // k=k_x*k_y
+ case Fragment::OR_C: return
+ (y & "V"_mst).If(x << "Bdu"_mst) | // V=V_y*B_x*u_x*d_x
+ (x & "o"_mst).If(y << "z"_mst) | // o=o_x*z_y
+ (x & y & "m"_mst).If(x << "e"_mst && (x | y) << "s"_mst) | // m=m_x*m_y*e_x*(s_x+s_y)
+ (x & y & "zs"_mst) | // z=z_x*z_y, s=s_x*s_y
+ "fx"_mst | // f, x
+ ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y
+ (x & y & "k"_mst); // k=k_x*k_y
+ case Fragment::OR_I: return
+ (x & y & "VBKufs"_mst) | // V=V_x*V_y, B=B_x*B_y, K=K_x*K_y, u=u_x*u_y, f=f_x*f_y, s=s_x*s_y
+ "o"_mst.If((x & y) << "z"_mst) | // o=z_x*z_y
+ ((x | y) & "e"_mst).If((x | y) << "f"_mst) | // e=e_x*f_y+f_x*e_y
+ (x & y & "m"_mst).If((x | y) << "s"_mst) | // m=m_x*m_y*(s_x+s_y)
+ ((x | y) & "d"_mst) | // d=d_x+d_y
+ "x"_mst | // x
+ ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y
+ (x & y & "k"_mst); // k=k_x*k_y
+ case Fragment::ANDOR: return
+ (y & z & "BKV"_mst).If(x << "Bdu"_mst) | // B=B_x*d_x*u_x*B_y*B_z, K=B_x*d_x*u_x*K_y*K_z, V=B_x*d_x*u_x*V_y*V_z
+ (x & y & z & "z"_mst) | // z=z_x*z_y*z_z
+ ((x | (y & z)) & "o"_mst).If((x | (y & z)) << "z"_mst) | // o=o_x*z_y*z_z+z_x*o_y*o_z
+ (y & z & "u"_mst) | // u=u_y*u_z
+ (z & "f"_mst).If((x << "s"_mst) || (y << "f"_mst)) | // f=(s_x+f_y)*f_z
+ (z & "d"_mst) | // d=d_z
+ (x & z & "e"_mst).If(x << "s"_mst || y << "f"_mst) | // e=e_x*e_z*(s_x+f_y)
+ (x & y & z & "m"_mst).If(x << "e"_mst && (x | y | z) << "s"_mst) | // m=m_x*m_y*m_z*e_x*(s_x+s_y+s_z)
+ (z & (x | y) & "s"_mst) | // s=s_z*(s_x+s_y)
+ "x"_mst | // x
+ ((x | y | z) & "ghij"_mst) | // g=g_x+g_y+g_z, h=h_x+h_y+h_z, i=i_x+i_y+i_z, j=j_x+j_y_j_z
+ "k"_mst.If(((x & y & z) << "k"_mst) &&
+ !(((x << "g"_mst) && (y << "h"_mst)) ||
+ ((x << "h"_mst) && (y << "g"_mst)) ||
+ ((x << "i"_mst) && (y << "j"_mst)) ||
+ ((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*k_z* !(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y)
+ case Fragment::MULTI: return "Bnudemsk"_mst;
+ case Fragment::THRESH: {
+ bool all_e = true;
+ bool all_m = true;
+ uint32_t args = 0;
+ uint32_t num_s = 0;
+ Type acc_tl = "k"_mst;
+ for (size_t i = 0; i < sub_types.size(); ++i) {
+ Type t = sub_types[i];
+ if (!(t << (i ? "Wdu"_mst : "Bdu"_mst))) return ""_mst; // Require Bdu, Wdu, Wdu, ...
+ if (!(t << "e"_mst)) all_e = false;
+ if (!(t << "m"_mst)) all_m = false;
+ if (t << "s"_mst) num_s += 1;
+ args += (t << "z"_mst) ? 0 : (t << "o"_mst) ? 1 : 2;
+ acc_tl = ((acc_tl | t) & "ghij"_mst) |
+ // Thresh contains a combination of timelocks if it has threshold > 1 and
+ // it contains two different children that have different types of timelocks
+ // Note how if any of the children don't have "k", the parent also does not have "k"
+ "k"_mst.If(((acc_tl & t) << "k"_mst) && ((k <= 1) ||
+ ((k > 1) && !(((acc_tl << "g"_mst) && (t << "h"_mst)) ||
+ ((acc_tl << "h"_mst) && (t << "g"_mst)) ||
+ ((acc_tl << "i"_mst) && (t << "j"_mst)) ||
+ ((acc_tl << "j"_mst) && (t << "i"_mst))))));
+ }
+ return "Bdu"_mst |
+ "z"_mst.If(args == 0) | // z=all z
+ "o"_mst.If(args == 1) | // o=all z except one o
+ "e"_mst.If(all_e && num_s == n_subs) | // e=all e and all s
+ "m"_mst.If(all_e && all_m && num_s >= n_subs - k) | // m=all e, >=(n-k) s
+ "s"_mst.If(num_s >= n_subs - k + 1) | // s= >=(n-k+1) s
+ acc_tl; // timelock info
+ }
+ }
+ assert(false);
+ return ""_mst;
+}
+
+size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) {
+ switch (nodetype) {
+ case Fragment::JUST_1:
+ case Fragment::JUST_0: return 1;
+ case Fragment::PK_K: return 34;
+ case Fragment::PK_H: return 3 + 21;
+ case Fragment::OLDER:
+ case Fragment::AFTER: return 1 + BuildScript(k).size();
+ case Fragment::HASH256:
+ case Fragment::SHA256: return 4 + 2 + 33;
+ case Fragment::HASH160:
+ case Fragment::RIPEMD160: return 4 + 2 + 21;
+ case Fragment::MULTI: return 3 + (n_keys > 16) + (k > 16) + 34 * n_keys;
+ case Fragment::AND_V: return subsize;
+ case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst);
+ case Fragment::WRAP_S:
+ case Fragment::WRAP_C:
+ case Fragment::WRAP_N:
+ case Fragment::AND_B:
+ case Fragment::OR_B: return subsize + 1;
+ case Fragment::WRAP_A:
+ case Fragment::OR_C: return subsize + 2;
+ case Fragment::WRAP_D:
+ case Fragment::OR_D:
+ case Fragment::OR_I:
+ case Fragment::ANDOR: return subsize + 3;
+ case Fragment::WRAP_J: return subsize + 4;
+ case Fragment::THRESH: return subsize + n_subs + BuildScript(k).size();
+ }
+ assert(false);
+ return 0;
+}
+
+bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, std::vector<unsigned char>>>& out)
+{
+ out.clear();
+ CScript::const_iterator it = script.begin(), itend = script.end();
+ while (it != itend) {
+ std::vector<unsigned char> push_data;
+ opcodetype opcode;
+ if (!script.GetOp(it, opcode, push_data)) {
+ out.clear();
+ return false;
+ } else if (opcode >= OP_1 && opcode <= OP_16) {
+ // Deal with OP_n (GetOp does not turn them into pushes).
+ push_data.assign(1, CScript::DecodeOP_N(opcode));
+ } else if (opcode == OP_CHECKSIGVERIFY) {
+ // Decompose OP_CHECKSIGVERIFY into OP_CHECKSIG OP_VERIFY
+ out.emplace_back(OP_CHECKSIG, std::vector<unsigned char>());
+ opcode = OP_VERIFY;
+ } else if (opcode == OP_CHECKMULTISIGVERIFY) {
+ // Decompose OP_CHECKMULTISIGVERIFY into OP_CHECKMULTISIG OP_VERIFY
+ out.emplace_back(OP_CHECKMULTISIG, std::vector<unsigned char>());
+ opcode = OP_VERIFY;
+ } else if (opcode == OP_EQUALVERIFY) {
+ // Decompose OP_EQUALVERIFY into OP_EQUAL OP_VERIFY
+ out.emplace_back(OP_EQUAL, std::vector<unsigned char>());
+ opcode = OP_VERIFY;
+ } else if (IsPushdataOp(opcode)) {
+ if (!CheckMinimalPush(push_data, opcode)) return false;
+ } else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL) && (*it == OP_VERIFY)) {
+ // Rule out non minimal VERIFY sequences
+ return false;
+ }
+ out.emplace_back(opcode, std::move(push_data));
+ }
+ std::reverse(out.begin(), out.end());
+ return true;
+}
+
+bool ParseScriptNumber(const std::pair<opcodetype, std::vector<unsigned char>>& in, int64_t& k) {
+ if (in.first == OP_0) {
+ k = 0;
+ return true;
+ }
+ if (!in.second.empty()) {
+ if (IsPushdataOp(in.first) && !CheckMinimalPush(in.second, in.first)) return false;
+ try {
+ k = CScriptNum(in.second, true).GetInt64();
+ return true;
+ } catch(const scriptnum_error&) {}
+ }
+ return false;
+}
+
+int FindNextChar(Span<const char> sp, const char m)
+{
+ for (int i = 0; i < (int)sp.size(); ++i) {
+ if (sp[i] == m) return i;
+ // We only search within the current parentheses
+ if (sp[i] == ')') break;
+ }
+ return -1;
+}
+
+} // namespace internal
+} // namespace miniscript
diff --git a/src/script/miniscript.h b/src/script/miniscript.h
new file mode 100644
index 0000000000..5c1cc316dc
--- /dev/null
+++ b/src/script/miniscript.h
@@ -0,0 +1,1652 @@
+// Copyright (c) 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.
+
+#ifndef BITCOIN_SCRIPT_MINISCRIPT_H
+#define BITCOIN_SCRIPT_MINISCRIPT_H
+
+#include <algorithm>
+#include <numeric>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <stdlib.h>
+#include <assert.h>
+
+#include <policy/policy.h>
+#include <primitives/transaction.h>
+#include <script/script.h>
+#include <span.h>
+#include <util/spanparsing.h>
+#include <util/strencodings.h>
+#include <util/string.h>
+#include <util/vector.h>
+
+namespace miniscript {
+
+/** This type encapsulates the miniscript type system properties.
+ *
+ * Every miniscript expression is one of 4 basic types, and additionally has
+ * a number of boolean type properties.
+ *
+ * The basic types are:
+ * - "B" Base:
+ * - Takes its inputs from the top of the stack.
+ * - When satisfied, pushes a nonzero value of up to 4 bytes onto the stack.
+ * - When dissatisfied, pushes a 0 onto the stack.
+ * - This is used for most expressions, and required for the top level one.
+ * - For example: older(n) = <n> OP_CHECKSEQUENCEVERIFY.
+ * - "V" Verify:
+ * - Takes its inputs from the top of the stack.
+ * - When satisfactied, pushes nothing.
+ * - Cannot be dissatisfied.
+ * - This can be obtained by adding an OP_VERIFY to a B, modifying the last opcode
+ * of a B to its -VERIFY version (only for OP_CHECKSIG, OP_CHECKSIGVERIFY
+ * and OP_EQUAL), or by combining a V fragment under some conditions.
+ * - For example vc:pk_k(key) = <key> OP_CHECKSIGVERIFY
+ * - "K" Key:
+ * - Takes its inputs from the top of the stack.
+ * - Becomes a B when followed by OP_CHECKSIG.
+ * - Always pushes a public key onto the stack, for which a signature is to be
+ * provided to satisfy the expression.
+ * - For example pk_h(key) = OP_DUP OP_HASH160 <Hash160(key)> OP_EQUALVERIFY
+ * - "W" Wrapped:
+ * - Takes its input from one below the top of the stack.
+ * - When satisfied, pushes a nonzero value (like B) on top of the stack, or one below.
+ * - When dissatisfied, pushes 0 op top of the stack or one below.
+ * - Is always "OP_SWAP [B]" or "OP_TOALTSTACK [B] OP_FROMALTSTACK".
+ * - For example sc:pk_k(key) = OP_SWAP <key> OP_CHECKSIG
+ *
+ * There a type properties that help reasoning about correctness:
+ * - "z" Zero-arg:
+ * - Is known to always consume exactly 0 stack elements.
+ * - For example after(n) = <n> OP_CHECKLOCKTIMEVERIFY
+ * - "o" One-arg:
+ * - Is known to always consume exactly 1 stack element.
+ * - Conflicts with property 'z'
+ * - For example sha256(hash) = OP_SIZE 32 OP_EQUALVERIFY OP_SHA256 <hash> OP_EQUAL
+ * - "n" Nonzero:
+ * - For every way this expression can be satisfied, a satisfaction exists that never needs
+ * a zero top stack element.
+ * - Conflicts with property 'z' and with type 'W'.
+ * - "d" Dissatisfiable:
+ * - There is an easy way to construct a dissatisfaction for this expression.
+ * - Conflicts with type 'V'.
+ * - "u" Unit:
+ * - In case of satisfaction, an exact 1 is put on the stack (rather than just nonzero).
+ * - Conflicts with type 'V'.
+ *
+ * Additional type properties help reasoning about nonmalleability:
+ * - "e" Expression:
+ * - This implies property 'd', but the dissatisfaction is nonmalleable.
+ * - This generally requires 'e' for all subexpressions which are invoked for that
+ * dissatifsaction, and property 'f' for the unexecuted subexpressions in that case.
+ * - Conflicts with type 'V'.
+ * - "f" Forced:
+ * - Dissatisfactions (if any) for this expression always involve at least one signature.
+ * - Is always true for type 'V'.
+ * - "s" Safe:
+ * - Satisfactions for this expression always involve at least one signature.
+ * - "m" Nonmalleable:
+ * - For every way this expression can be satisfied (which may be none),
+ * a nonmalleable satisfaction exists.
+ * - This generally requires 'm' for all subexpressions, and 'e' for all subexpressions
+ * which are dissatisfied when satisfying the parent.
+ *
+ * One type property is an implementation detail:
+ * - "x" Expensive verify:
+ * - Expressions with this property have a script whose last opcode is not EQUAL, CHECKSIG, or CHECKMULTISIG.
+ * - Not having this property means that it can be converted to a V at no cost (by switching to the
+ * -VERIFY version of the last opcode).
+ *
+ * Five more type properties for representing timelock information. Spend paths
+ * in miniscripts containing conflicting timelocks and heightlocks cannot be spent together.
+ * This helps users detect if miniscript does not match the semantic behaviour the
+ * user expects.
+ * - "g" Whether the branch contains a relative time timelock
+ * - "h" Whether the branch contains a relative height timelock
+ * - "i" Whether the branch contains an absolute time timelock
+ * - "j" Whether the branch contains an absolute height timelock
+ * - "k"
+ * - Whether all satisfactions of this expression don't contain a mix of heightlock and timelock
+ * of the same type.
+ * - If the miniscript does not have the "k" property, the miniscript template will not match
+ * the user expectation of the corresponding spending policy.
+ * For each of these properties the subset rule holds: an expression with properties X, Y, and Z, is also
+ * valid in places where an X, a Y, a Z, an XY, ... is expected.
+*/
+class Type {
+ //! Internal bitmap of properties (see ""_mst operator for details).
+ uint32_t m_flags;
+
+ //! Internal constructor used by the ""_mst operator.
+ explicit constexpr Type(uint32_t flags) : m_flags(flags) {}
+
+public:
+ //! The only way to publicly construct a Type is using this literal operator.
+ friend constexpr Type operator"" _mst(const char* c, size_t l);
+
+ //! Compute the type with the union of properties.
+ constexpr Type operator|(Type x) const { return Type(m_flags | x.m_flags); }
+
+ //! Compute the type with the intersection of properties.
+ constexpr Type operator&(Type x) const { return Type(m_flags & x.m_flags); }
+
+ //! Check whether the left hand's properties are superset of the right's (= left is a subtype of right).
+ constexpr bool operator<<(Type x) const { return (x.m_flags & ~m_flags) == 0; }
+
+ //! Comparison operator to enable use in sets/maps (total ordering incompatible with <<).
+ constexpr bool operator<(Type x) const { return m_flags < x.m_flags; }
+
+ //! Equality operator.
+ constexpr bool operator==(Type x) const { return m_flags == x.m_flags; }
+
+ //! The empty type if x is false, itself otherwise.
+ constexpr Type If(bool x) const { return Type(x ? m_flags : 0); }
+};
+
+//! Literal operator to construct Type objects.
+inline constexpr Type operator"" _mst(const char* c, size_t l) {
+ Type typ{0};
+
+ for (const char *p = c; p < c + l; p++) {
+ typ = typ | Type(
+ *p == 'B' ? 1 << 0 : // Base type
+ *p == 'V' ? 1 << 1 : // Verify type
+ *p == 'K' ? 1 << 2 : // Key type
+ *p == 'W' ? 1 << 3 : // Wrapped type
+ *p == 'z' ? 1 << 4 : // Zero-arg property
+ *p == 'o' ? 1 << 5 : // One-arg property
+ *p == 'n' ? 1 << 6 : // Nonzero arg property
+ *p == 'd' ? 1 << 7 : // Dissatisfiable property
+ *p == 'u' ? 1 << 8 : // Unit property
+ *p == 'e' ? 1 << 9 : // Expression property
+ *p == 'f' ? 1 << 10 : // Forced property
+ *p == 's' ? 1 << 11 : // Safe property
+ *p == 'm' ? 1 << 12 : // Nonmalleable property
+ *p == 'x' ? 1 << 13 : // Expensive verify
+ *p == 'g' ? 1 << 14 : // older: contains relative time timelock (csv_time)
+ *p == 'h' ? 1 << 15 : // older: contains relative height timelock (csv_height)
+ *p == 'i' ? 1 << 16 : // after: contains time timelock (cltv_time)
+ *p == 'j' ? 1 << 17 : // after: contains height timelock (cltv_height)
+ *p == 'k' ? 1 << 18 : // does not contain a combination of height and time locks
+ (throw std::logic_error("Unknown character in _mst literal"), 0)
+ );
+ }
+
+ return typ;
+}
+
+template<typename Key> struct Node;
+template<typename Key> using NodeRef = std::shared_ptr<const Node<Key>>;
+
+//! Construct a miniscript node as a shared_ptr.
+template<typename Key, typename... Args>
+NodeRef<Key> MakeNodeRef(Args&&... args) { return std::make_shared<const Node<Key>>(std::forward<Args>(args)...); }
+
+//! The different node types in miniscript.
+enum class Fragment {
+ JUST_0, //!< OP_0
+ JUST_1, //!< OP_1
+ PK_K, //!< [key]
+ PK_H, //!< OP_DUP OP_HASH160 [keyhash] OP_EQUALVERIFY
+ OLDER, //!< [n] OP_CHECKSEQUENCEVERIFY
+ AFTER, //!< [n] OP_CHECKLOCKTIMEVERIFY
+ SHA256, //!< OP_SIZE 32 OP_EQUALVERIFY OP_SHA256 [hash] OP_EQUAL
+ HASH256, //!< OP_SIZE 32 OP_EQUALVERIFY OP_HASH256 [hash] OP_EQUAL
+ RIPEMD160, //!< OP_SIZE 32 OP_EQUALVERIFY OP_RIPEMD160 [hash] OP_EQUAL
+ HASH160, //!< OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 [hash] OP_EQUAL
+ WRAP_A, //!< OP_TOALTSTACK [X] OP_FROMALTSTACK
+ WRAP_S, //!< OP_SWAP [X]
+ WRAP_C, //!< [X] OP_CHECKSIG
+ WRAP_D, //!< OP_DUP OP_IF [X] OP_ENDIF
+ WRAP_V, //!< [X] OP_VERIFY (or -VERIFY version of last opcode in X)
+ WRAP_J, //!< OP_SIZE OP_0NOTEQUAL OP_IF [X] OP_ENDIF
+ WRAP_N, //!< [X] OP_0NOTEQUAL
+ AND_V, //!< [X] [Y]
+ AND_B, //!< [X] [Y] OP_BOOLAND
+ OR_B, //!< [X] [Y] OP_BOOLOR
+ OR_C, //!< [X] OP_NOTIF [Y] OP_ENDIF
+ OR_D, //!< [X] OP_IFDUP OP_NOTIF [Y] OP_ENDIF
+ OR_I, //!< OP_IF [X] OP_ELSE [Y] OP_ENDIF
+ ANDOR, //!< [X] OP_NOTIF [Z] OP_ELSE [Y] OP_ENDIF
+ THRESH, //!< [X1] ([Xn] OP_ADD)* [k] OP_EQUAL
+ MULTI, //!< [k] [key_n]* [n] OP_CHECKMULTISIG
+ // AND_N(X,Y) is represented as ANDOR(X,Y,0)
+ // WRAP_T(X) is represented as AND_V(X,1)
+ // WRAP_L(X) is represented as OR_I(0,X)
+ // WRAP_U(X) is represented as OR_I(X,0)
+};
+
+
+namespace internal {
+
+//! Helper function for Node::CalcType.
+Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys);
+
+//! Helper function for Node::CalcScriptLen.
+size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys);
+
+//! A helper sanitizer/checker for the output of CalcType.
+Type SanitizeType(Type x);
+
+//! Class whose objects represent the maximum of a list of integers.
+template<typename I>
+struct MaxInt {
+ const bool valid;
+ const I value;
+
+ MaxInt() : valid(false), value(0) {}
+ MaxInt(I val) : valid(true), value(val) {}
+
+ friend MaxInt<I> operator+(const MaxInt<I>& a, const MaxInt<I>& b) {
+ if (!a.valid || !b.valid) return {};
+ return a.value + b.value;
+ }
+
+ friend MaxInt<I> operator|(const MaxInt<I>& a, const MaxInt<I>& b) {
+ if (!a.valid) return b;
+ if (!b.valid) return a;
+ return std::max(a.value, b.value);
+ }
+};
+
+struct Ops {
+ //! Non-push opcodes.
+ uint32_t count;
+ //! Number of keys in possibly executed OP_CHECKMULTISIG(VERIFY)s to satisfy.
+ MaxInt<uint32_t> sat;
+ //! Number of keys in possibly executed OP_CHECKMULTISIG(VERIFY)s to dissatisfy.
+ MaxInt<uint32_t> dsat;
+
+ Ops(uint32_t in_count, MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : count(in_count), sat(in_sat), dsat(in_dsat) {};
+};
+
+struct StackSize {
+ //! Maximum stack size to satisfy;
+ MaxInt<uint32_t> sat;
+ //! Maximum stack size to dissatisfy;
+ MaxInt<uint32_t> dsat;
+
+ StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {};
+};
+
+} // namespace internal
+
+//! A node in a miniscript expression.
+template<typename Key>
+struct Node {
+ //! What node type this node is.
+ const Fragment nodetype;
+ //! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M))
+ const uint32_t k = 0;
+ //! The keys used by this expression (only for PK_K/PK_H/MULTI)
+ const std::vector<Key> keys;
+ //! The data bytes in this expression (only for HASH160/HASH256/SHA256/RIPEMD10).
+ const std::vector<unsigned char> data;
+ //! Subexpressions (for WRAP_*/AND_*/OR_*/ANDOR/THRESH)
+ const std::vector<NodeRef<Key>> subs;
+
+private:
+ //! Cached ops counts.
+ const internal::Ops ops;
+ //! Cached stack size bounds.
+ const internal::StackSize ss;
+ //! Cached expression type (computed by CalcType and fed through SanitizeType).
+ const Type typ;
+ //! Cached script length (computed by CalcScriptLen).
+ const size_t scriptlen;
+
+ //! Compute the length of the script for this miniscript (including children).
+ size_t CalcScriptLen() const {
+ size_t subsize = 0;
+ for (const auto& sub : subs) {
+ subsize += sub->ScriptSize();
+ }
+ Type sub0type = subs.size() > 0 ? subs[0]->GetType() : ""_mst;
+ return internal::ComputeScriptLen(nodetype, sub0type, subsize, k, subs.size(), keys.size());
+ }
+
+ /* Apply a recursive algorithm to a Miniscript tree, without actual recursive calls.
+ *
+ * The algorithm is defined by two functions: downfn and upfn. Conceptually, the
+ * result can be thought of as first using downfn to compute a "state" for each node,
+ * from the root down to the leaves. Then upfn is used to compute a "result" for each
+ * node, from the leaves back up to the root, which is then returned. In the actual
+ * implementation, both functions are invoked in an interleaved fashion, performing a
+ * depth-first traversal of the tree.
+ *
+ * In more detail, it is invoked as node.TreeEvalMaybe<Result>(root, downfn, upfn):
+ * - root is the state of the root node, of type State.
+ * - downfn is a callable (State&, const Node&, size_t) -> State, which given a
+ * node, its state, and an index of one of its children, computes the state of that
+ * child. It can modify the state. Children of a given node will have downfn()
+ * called in order.
+ * - upfn is a callable (State&&, const Node&, Span<Result>) -> std::optional<Result>,
+ * which given a node, its state, and a Span of the results of its children,
+ * computes the result of the node. If std::nullopt is returned by upfn,
+ * TreeEvalMaybe() immediately returns std::nullopt.
+ * The return value of TreeEvalMaybe is the result of the root node.
+ */
+ template<typename Result, typename State, typename DownFn, typename UpFn>
+ std::optional<Result> TreeEvalMaybe(State root_state, DownFn downfn, UpFn upfn) const
+ {
+ /** Entries of the explicit stack tracked in this algorithm. */
+ struct StackElem
+ {
+ const Node& node; //!< The node being evaluated.
+ size_t expanded; //!< How many children of this node have been expanded.
+ State state; //!< The state for that node.
+
+ StackElem(const Node& node_, size_t exp_, State&& state_) :
+ node(node_), expanded(exp_), state(std::move(state_)) {}
+ };
+ /* Stack of tree nodes being explored. */
+ std::vector<StackElem> stack;
+ /* Results of subtrees so far. Their order and mapping to tree nodes
+ * is implicitly defined by stack. */
+ std::vector<Result> results;
+ stack.emplace_back(*this, 0, std::move(root_state));
+
+ /* Here is a demonstration of the algorithm, for an example tree A(B,C(D,E),F).
+ * State variables are omitted for simplicity.
+ *
+ * First: stack=[(A,0)] results=[]
+ * stack=[(A,1),(B,0)] results=[]
+ * stack=[(A,1)] results=[B]
+ * stack=[(A,2),(C,0)] results=[B]
+ * stack=[(A,2),(C,1),(D,0)] results=[B]
+ * stack=[(A,2),(C,1)] results=[B,D]
+ * stack=[(A,2),(C,2),(E,0)] results=[B,D]
+ * stack=[(A,2),(C,2)] results=[B,D,E]
+ * stack=[(A,2)] results=[B,C]
+ * stack=[(A,3),(F,0)] results=[B,C]
+ * stack=[(A,3)] results=[B,C,F]
+ * Final: stack=[] results=[A]
+ */
+ while (stack.size()) {
+ const Node& node = stack.back().node;
+ if (stack.back().expanded < node.subs.size()) {
+ /* We encounter a tree node with at least one unexpanded child.
+ * Expand it. By the time we hit this node again, the result of
+ * that child (and all earlier children) will be at the end of `results`. */
+ size_t child_index = stack.back().expanded++;
+ State child_state = downfn(stack.back().state, node, child_index);
+ stack.emplace_back(*node.subs[child_index], 0, std::move(child_state));
+ continue;
+ }
+ // Invoke upfn with the last node.subs.size() elements of results as input.
+ assert(results.size() >= node.subs.size());
+ std::optional<Result> result{upfn(std::move(stack.back().state), node,
+ Span<Result>{results}.last(node.subs.size()))};
+ // If evaluation returns std::nullopt, abort immediately.
+ if (!result) return {};
+ // Replace the last node.subs.size() elements of results with the new result.
+ results.erase(results.end() - node.subs.size(), results.end());
+ results.push_back(std::move(*result));
+ stack.pop_back();
+ }
+ // The final remaining results element is the root result, return it.
+ assert(results.size() == 1);
+ return std::move(results[0]);
+ }
+
+ /** Like TreeEvalMaybe, but always produces a result. upfn must return Result. */
+ template<typename Result, typename State, typename DownFn, typename UpFn>
+ Result TreeEval(State root_state, DownFn&& downfn, UpFn upfn) const
+ {
+ // Invoke TreeEvalMaybe with upfn wrapped to return std::optional<Result>, and then
+ // unconditionally dereference the result (it cannot be std::nullopt).
+ return std::move(*TreeEvalMaybe<Result>(std::move(root_state),
+ std::forward<DownFn>(downfn),
+ [&upfn](State&& state, const Node& node, Span<Result> subs) {
+ Result res{upfn(std::move(state), node, subs)};
+ return std::optional<Result>(std::move(res));
+ }
+ ));
+ }
+
+ //! Compute the type for this miniscript.
+ Type CalcType() const {
+ using namespace internal;
+
+ // THRESH has a variable number of subexpressions
+ std::vector<Type> sub_types;
+ if (nodetype == Fragment::THRESH) {
+ for (const auto& sub : subs) sub_types.push_back(sub->GetType());
+ }
+ // All other nodes than THRESH can be computed just from the types of the 0-3 subexpressions.
+ Type x = subs.size() > 0 ? subs[0]->GetType() : ""_mst;
+ Type y = subs.size() > 1 ? subs[1]->GetType() : ""_mst;
+ Type z = subs.size() > 2 ? subs[2]->GetType() : ""_mst;
+
+ return SanitizeType(ComputeType(nodetype, x, y, z, sub_types, k, data.size(), subs.size(), keys.size()));
+ }
+
+public:
+ template<typename Ctx>
+ CScript ToScript(const Ctx& ctx) const
+ {
+ // To construct the CScript for a Miniscript object, we use the TreeEval algorithm.
+ // The State is a boolean: whether or not the node's script expansion is followed
+ // by an OP_VERIFY (which may need to be combined with the last script opcode).
+ auto downfn = [](bool verify, const Node& node, size_t index) {
+ // For WRAP_V, the subexpression is certainly followed by OP_VERIFY.
+ if (node.nodetype == Fragment::WRAP_V) return true;
+ // The subexpression of WRAP_S, and the last subexpression of AND_V
+ // inherit the followed-by-OP_VERIFY property from the parent.
+ if (node.nodetype == Fragment::WRAP_S ||
+ (node.nodetype == Fragment::AND_V && index == 1)) return verify;
+ return false;
+ };
+ // The upward function computes for a node, given its followed-by-OP_VERIFY status
+ // and the CScripts of its child nodes, the CScript of the node.
+ auto upfn = [&ctx](bool verify, const Node& node, Span<CScript> subs) -> CScript {
+ switch (node.nodetype) {
+ case Fragment::PK_K: return BuildScript(ctx.ToPKBytes(node.keys[0]));
+ case Fragment::PK_H: return BuildScript(OP_DUP, OP_HASH160, ctx.ToPKHBytes(node.keys[0]), OP_EQUALVERIFY);
+ case Fragment::OLDER: return BuildScript(node.k, OP_CHECKSEQUENCEVERIFY);
+ case Fragment::AFTER: return BuildScript(node.k, OP_CHECKLOCKTIMEVERIFY);
+ case Fragment::SHA256: return BuildScript(OP_SIZE, 32, OP_EQUALVERIFY, OP_SHA256, node.data, verify ? OP_EQUALVERIFY : OP_EQUAL);
+ case Fragment::RIPEMD160: return BuildScript(OP_SIZE, 32, OP_EQUALVERIFY, OP_RIPEMD160, node.data, verify ? OP_EQUALVERIFY : OP_EQUAL);
+ case Fragment::HASH256: return BuildScript(OP_SIZE, 32, OP_EQUALVERIFY, OP_HASH256, node.data, verify ? OP_EQUALVERIFY : OP_EQUAL);
+ case Fragment::HASH160: return BuildScript(OP_SIZE, 32, OP_EQUALVERIFY, OP_HASH160, node.data, verify ? OP_EQUALVERIFY : OP_EQUAL);
+ case Fragment::WRAP_A: return BuildScript(OP_TOALTSTACK, subs[0], OP_FROMALTSTACK);
+ case Fragment::WRAP_S: return BuildScript(OP_SWAP, subs[0]);
+ case Fragment::WRAP_C: return BuildScript(std::move(subs[0]), verify ? OP_CHECKSIGVERIFY : OP_CHECKSIG);
+ case Fragment::WRAP_D: return BuildScript(OP_DUP, OP_IF, subs[0], OP_ENDIF);
+ case Fragment::WRAP_V: {
+ if (node.subs[0]->GetType() << "x"_mst) {
+ return BuildScript(std::move(subs[0]), OP_VERIFY);
+ } else {
+ return std::move(subs[0]);
+ }
+ }
+ case Fragment::WRAP_J: return BuildScript(OP_SIZE, OP_0NOTEQUAL, OP_IF, subs[0], OP_ENDIF);
+ case Fragment::WRAP_N: return BuildScript(std::move(subs[0]), OP_0NOTEQUAL);
+ case Fragment::JUST_1: return BuildScript(OP_1);
+ case Fragment::JUST_0: return BuildScript(OP_0);
+ case Fragment::AND_V: return BuildScript(std::move(subs[0]), subs[1]);
+ case Fragment::AND_B: return BuildScript(std::move(subs[0]), subs[1], OP_BOOLAND);
+ case Fragment::OR_B: return BuildScript(std::move(subs[0]), subs[1], OP_BOOLOR);
+ case Fragment::OR_D: return BuildScript(std::move(subs[0]), OP_IFDUP, OP_NOTIF, subs[1], OP_ENDIF);
+ case Fragment::OR_C: return BuildScript(std::move(subs[0]), OP_NOTIF, subs[1], OP_ENDIF);
+ case Fragment::OR_I: return BuildScript(OP_IF, subs[0], OP_ELSE, subs[1], OP_ENDIF);
+ case Fragment::ANDOR: return BuildScript(std::move(subs[0]), OP_NOTIF, subs[2], OP_ELSE, subs[1], OP_ENDIF);
+ case Fragment::MULTI: {
+ CScript script = BuildScript(node.k);
+ for (const auto& key : node.keys) {
+ script = BuildScript(std::move(script), ctx.ToPKBytes(key));
+ }
+ return BuildScript(std::move(script), node.keys.size(), verify ? OP_CHECKMULTISIGVERIFY : OP_CHECKMULTISIG);
+ }
+ case Fragment::THRESH: {
+ CScript script = std::move(subs[0]);
+ for (size_t i = 1; i < subs.size(); ++i) {
+ script = BuildScript(std::move(script), subs[i], OP_ADD);
+ }
+ return BuildScript(std::move(script), node.k, verify ? OP_EQUALVERIFY : OP_EQUAL);
+ }
+ }
+ assert(false);
+ return {};
+ };
+ return TreeEval<CScript>(false, downfn, upfn);
+ }
+
+ template<typename CTx>
+ bool ToString(const CTx& ctx, std::string& ret) const {
+ // To construct the std::string representation for a Miniscript object, we use
+ // the TreeEvalMaybe algorithm. The State is a boolean: whether the parent node is a
+ // wrapper. If so, non-wrapper expressions must be prefixed with a ":".
+ auto downfn = [](bool, const Node& node, size_t) {
+ return (node.nodetype == Fragment::WRAP_A || node.nodetype == Fragment::WRAP_S ||
+ node.nodetype == Fragment::WRAP_D || node.nodetype == Fragment::WRAP_V ||
+ node.nodetype == Fragment::WRAP_J || node.nodetype == Fragment::WRAP_N ||
+ node.nodetype == Fragment::WRAP_C ||
+ (node.nodetype == Fragment::AND_V && node.subs[1]->nodetype == Fragment::JUST_1) ||
+ (node.nodetype == Fragment::OR_I && node.subs[0]->nodetype == Fragment::JUST_0) ||
+ (node.nodetype == Fragment::OR_I && node.subs[1]->nodetype == Fragment::JUST_0));
+ };
+ // The upward function computes for a node, given whether its parent is a wrapper,
+ // and the string representations of its child nodes, the string representation of the node.
+ auto upfn = [&ctx](bool wrapped, const Node& node, Span<std::string> subs) -> std::optional<std::string> {
+ std::string ret = wrapped ? ":" : "";
+
+ switch (node.nodetype) {
+ case Fragment::WRAP_A: return "a" + std::move(subs[0]);
+ case Fragment::WRAP_S: return "s" + std::move(subs[0]);
+ case Fragment::WRAP_C:
+ if (node.subs[0]->nodetype == Fragment::PK_K) {
+ // pk(K) is syntactic sugar for c:pk_k(K)
+ std::string key_str;
+ if (!ctx.ToString(node.subs[0]->keys[0], key_str)) return {};
+ return std::move(ret) + "pk(" + std::move(key_str) + ")";
+ }
+ if (node.subs[0]->nodetype == Fragment::PK_H) {
+ // pkh(K) is syntactic sugar for c:pk_h(K)
+ std::string key_str;
+ if (!ctx.ToString(node.subs[0]->keys[0], key_str)) return {};
+ return std::move(ret) + "pkh(" + std::move(key_str) + ")";
+ }
+ return "c" + std::move(subs[0]);
+ case Fragment::WRAP_D: return "d" + std::move(subs[0]);
+ case Fragment::WRAP_V: return "v" + std::move(subs[0]);
+ case Fragment::WRAP_J: return "j" + std::move(subs[0]);
+ case Fragment::WRAP_N: return "n" + std::move(subs[0]);
+ case Fragment::AND_V:
+ // t:X is syntactic sugar for and_v(X,1).
+ if (node.subs[1]->nodetype == Fragment::JUST_1) return "t" + std::move(subs[0]);
+ break;
+ case Fragment::OR_I:
+ if (node.subs[0]->nodetype == Fragment::JUST_0) return "l" + std::move(subs[1]);
+ if (node.subs[1]->nodetype == Fragment::JUST_0) return "u" + std::move(subs[0]);
+ break;
+ default: break;
+ }
+ switch (node.nodetype) {
+ case Fragment::PK_K: {
+ std::string key_str;
+ if (!ctx.ToString(node.keys[0], key_str)) return {};
+ return std::move(ret) + "pk_k(" + std::move(key_str) + ")";
+ }
+ case Fragment::PK_H: {
+ std::string key_str;
+ if (!ctx.ToString(node.keys[0], key_str)) return {};
+ return std::move(ret) + "pk_h(" + std::move(key_str) + ")";
+ }
+ case Fragment::AFTER: return std::move(ret) + "after(" + ::ToString(node.k) + ")";
+ case Fragment::OLDER: return std::move(ret) + "older(" + ::ToString(node.k) + ")";
+ case Fragment::HASH256: return std::move(ret) + "hash256(" + HexStr(node.data) + ")";
+ case Fragment::HASH160: return std::move(ret) + "hash160(" + HexStr(node.data) + ")";
+ case Fragment::SHA256: return std::move(ret) + "sha256(" + HexStr(node.data) + ")";
+ case Fragment::RIPEMD160: return std::move(ret) + "ripemd160(" + HexStr(node.data) + ")";
+ case Fragment::JUST_1: return std::move(ret) + "1";
+ case Fragment::JUST_0: return std::move(ret) + "0";
+ case Fragment::AND_V: return std::move(ret) + "and_v(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
+ case Fragment::AND_B: return std::move(ret) + "and_b(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
+ case Fragment::OR_B: return std::move(ret) + "or_b(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
+ case Fragment::OR_D: return std::move(ret) + "or_d(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
+ case Fragment::OR_C: return std::move(ret) + "or_c(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
+ case Fragment::OR_I: return std::move(ret) + "or_i(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
+ case Fragment::ANDOR:
+ // and_n(X,Y) is syntactic sugar for andor(X,Y,0).
+ if (node.subs[2]->nodetype == Fragment::JUST_0) return std::move(ret) + "and_n(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")";
+ return std::move(ret) + "andor(" + std::move(subs[0]) + "," + std::move(subs[1]) + "," + std::move(subs[2]) + ")";
+ case Fragment::MULTI: {
+ auto str = std::move(ret) + "multi(" + ::ToString(node.k);
+ for (const auto& key : node.keys) {
+ std::string key_str;
+ if (!ctx.ToString(key, key_str)) return {};
+ str += "," + std::move(key_str);
+ }
+ return std::move(str) + ")";
+ }
+ case Fragment::THRESH: {
+ auto str = std::move(ret) + "thresh(" + ::ToString(node.k);
+ for (auto& sub : subs) {
+ str += "," + std::move(sub);
+ }
+ return std::move(str) + ")";
+ }
+ default: assert(false);
+ }
+ return ""; // Should never be reached.
+ };
+
+ auto res = TreeEvalMaybe<std::string>(false, downfn, upfn);
+ if (res.has_value()) ret = std::move(*res);
+ return res.has_value();
+ }
+
+ internal::Ops CalcOps() const {
+ switch (nodetype) {
+ case Fragment::JUST_1: return {0, 0, {}};
+ case Fragment::JUST_0: return {0, {}, 0};
+ case Fragment::PK_K: return {0, 0, 0};
+ case Fragment::PK_H: return {3, 0, 0};
+ case Fragment::OLDER:
+ case Fragment::AFTER: return {1, 0, {}};
+ case Fragment::SHA256:
+ case Fragment::RIPEMD160:
+ case Fragment::HASH256:
+ case Fragment::HASH160: return {4, 0, {}};
+ case Fragment::AND_V: return {subs[0]->ops.count + subs[1]->ops.count, subs[0]->ops.sat + subs[1]->ops.sat, {}};
+ case Fragment::AND_B: {
+ const auto count{1 + subs[0]->ops.count + subs[1]->ops.count};
+ const auto sat{subs[0]->ops.sat + subs[1]->ops.sat};
+ const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat};
+ return {count, sat, dsat};
+ }
+ case Fragment::OR_B: {
+ const auto count{1 + subs[0]->ops.count + subs[1]->ops.count};
+ const auto sat{(subs[0]->ops.sat + subs[1]->ops.dsat) | (subs[1]->ops.sat + subs[0]->ops.dsat)};
+ const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat};
+ return {count, sat, dsat};
+ }
+ case Fragment::OR_D: {
+ const auto count{3 + subs[0]->ops.count + subs[1]->ops.count};
+ const auto sat{subs[0]->ops.sat | (subs[1]->ops.sat + subs[0]->ops.dsat)};
+ const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat};
+ return {count, sat, dsat};
+ }
+ case Fragment::OR_C: {
+ const auto count{2 + subs[0]->ops.count + subs[1]->ops.count};
+ const auto sat{subs[0]->ops.sat | (subs[1]->ops.sat + subs[0]->ops.dsat)};
+ return {count, sat, {}};
+ }
+ case Fragment::OR_I: {
+ const auto count{3 + subs[0]->ops.count + subs[1]->ops.count};
+ const auto sat{subs[0]->ops.sat | subs[1]->ops.sat};
+ const auto dsat{subs[0]->ops.dsat | subs[1]->ops.dsat};
+ return {count, sat, dsat};
+ }
+ case Fragment::ANDOR: {
+ const auto count{3 + subs[0]->ops.count + subs[1]->ops.count + subs[2]->ops.count};
+ const auto sat{(subs[1]->ops.sat + subs[0]->ops.sat) | (subs[0]->ops.dsat + subs[2]->ops.sat)};
+ const auto dsat{subs[0]->ops.dsat + subs[2]->ops.dsat};
+ return {count, sat, dsat};
+ }
+ case Fragment::MULTI: return {1, (uint32_t)keys.size(), (uint32_t)keys.size()};
+ case Fragment::WRAP_S:
+ case Fragment::WRAP_C:
+ case Fragment::WRAP_N: return {1 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat};
+ case Fragment::WRAP_A: return {2 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat};
+ case Fragment::WRAP_D: return {3 + subs[0]->ops.count, subs[0]->ops.sat, 0};
+ case Fragment::WRAP_J: return {4 + subs[0]->ops.count, subs[0]->ops.sat, 0};
+ case Fragment::WRAP_V: return {subs[0]->ops.count + (subs[0]->GetType() << "x"_mst), subs[0]->ops.sat, {}};
+ case Fragment::THRESH: {
+ uint32_t count = 0;
+ auto sats = Vector(internal::MaxInt<uint32_t>(0));
+ for (const auto& sub : subs) {
+ count += sub->ops.count + 1;
+ auto next_sats = Vector(sats[0] + sub->ops.dsat);
+ for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ops.dsat) | (sats[j - 1] + sub->ops.sat));
+ next_sats.push_back(sats[sats.size() - 1] + sub->ops.sat);
+ sats = std::move(next_sats);
+ }
+ assert(k <= sats.size());
+ return {count, sats[k], sats[0]};
+ }
+ }
+ assert(false);
+ return {0, {}, {}};
+ }
+
+ internal::StackSize CalcStackSize() const {
+ switch (nodetype) {
+ case Fragment::JUST_0: return {{}, 0};
+ case Fragment::JUST_1:
+ case Fragment::OLDER:
+ case Fragment::AFTER: return {0, {}};
+ case Fragment::PK_K: return {1, 1};
+ case Fragment::PK_H: return {2, 2};
+ case Fragment::SHA256:
+ case Fragment::RIPEMD160:
+ case Fragment::HASH256:
+ case Fragment::HASH160: return {1, {}};
+ case Fragment::ANDOR: {
+ const auto sat{(subs[0]->ss.sat + subs[1]->ss.sat) | (subs[0]->ss.dsat + subs[2]->ss.sat)};
+ const auto dsat{subs[0]->ss.dsat + subs[2]->ss.dsat};
+ return {sat, dsat};
+ }
+ case Fragment::AND_V: return {subs[0]->ss.sat + subs[1]->ss.sat, {}};
+ case Fragment::AND_B: return {subs[0]->ss.sat + subs[1]->ss.sat, subs[0]->ss.dsat + subs[1]->ss.dsat};
+ case Fragment::OR_B: {
+ const auto sat{(subs[0]->ss.dsat + subs[1]->ss.sat) | (subs[0]->ss.sat + subs[1]->ss.dsat)};
+ const auto dsat{subs[0]->ss.dsat + subs[1]->ss.dsat};
+ return {sat, dsat};
+ }
+ case Fragment::OR_C: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), {}};
+ case Fragment::OR_D: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), subs[0]->ss.dsat + subs[1]->ss.dsat};
+ case Fragment::OR_I: return {(subs[0]->ss.sat + 1) | (subs[1]->ss.sat + 1), (subs[0]->ss.dsat + 1) | (subs[1]->ss.dsat + 1)};
+ case Fragment::MULTI: return {k + 1, k + 1};
+ case Fragment::WRAP_A:
+ case Fragment::WRAP_N:
+ case Fragment::WRAP_S:
+ case Fragment::WRAP_C: return subs[0]->ss;
+ case Fragment::WRAP_D: return {1 + subs[0]->ss.sat, 1};
+ case Fragment::WRAP_V: return {subs[0]->ss.sat, {}};
+ case Fragment::WRAP_J: return {subs[0]->ss.sat, 1};
+ case Fragment::THRESH: {
+ auto sats = Vector(internal::MaxInt<uint32_t>(0));
+ for (const auto& sub : subs) {
+ auto next_sats = Vector(sats[0] + sub->ss.dsat);
+ for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ss.dsat) | (sats[j - 1] + sub->ss.sat));
+ next_sats.push_back(sats[sats.size() - 1] + sub->ss.sat);
+ sats = std::move(next_sats);
+ }
+ assert(k <= sats.size());
+ return {sats[k], sats[0]};
+ }
+ }
+ assert(false);
+ return {{}, {}};
+ }
+
+public:
+ //! Return the size of the script for this expression (faster than ToScript().size()).
+ size_t ScriptSize() const { return scriptlen; }
+
+ //! Return the maximum number of ops needed to satisfy this script non-malleably.
+ uint32_t GetOps() const { return ops.count + ops.sat.value; }
+
+ //! Check the ops limit of this script against the consensus limit.
+ bool CheckOpsLimit() const { return GetOps() <= MAX_OPS_PER_SCRIPT; }
+
+ /** Return the maximum number of stack elements needed to satisfy this script non-malleably, including
+ * the script push. */
+ uint32_t GetStackSize() const { return ss.sat.value + 1; }
+
+ //! Check the maximum stack size for this script against the policy limit.
+ bool CheckStackSize() const { return GetStackSize() - 1 <= MAX_STANDARD_P2WSH_STACK_ITEMS; }
+
+ //! Return the expression type.
+ Type GetType() const { return typ; }
+
+ //! Check whether this node is valid at all.
+ bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; }
+
+ //! Check whether this node is valid as a script on its own.
+ bool IsValidTopLevel() const { return IsValid() && GetType() << "B"_mst; }
+
+ //! Check whether this script can always be satisfied in a non-malleable way.
+ bool IsNonMalleable() const { return GetType() << "m"_mst; }
+
+ //! Check whether this script always needs a signature.
+ bool NeedsSignature() const { return GetType() << "s"_mst; }
+
+ //! Do all sanity checks.
+ bool IsSane() const { return IsValid() && GetType() << "mk"_mst && CheckOpsLimit() && CheckStackSize(); }
+
+ //! Check whether this node is safe as a script on its own.
+ bool IsSaneTopLevel() const { return IsValidTopLevel() && IsSane() && NeedsSignature(); }
+
+ //! Equality testing.
+ bool operator==(const Node<Key>& arg) const
+ {
+ if (nodetype != arg.nodetype) return false;
+ if (k != arg.k) return false;
+ if (data != arg.data) return false;
+ if (keys != arg.keys) return false;
+ if (subs.size() != arg.subs.size()) return false;
+ for (size_t i = 0; i < subs.size(); ++i) {
+ if (!(*subs[i] == *arg.subs[i])) return false;
+ }
+ assert(scriptlen == arg.scriptlen);
+ assert(typ == arg.typ);
+ return true;
+ }
+
+ // Constructors with various argument combinations.
+ Node(Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(Fragment nt, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : nodetype(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(Fragment nt, uint32_t val = 0) : nodetype(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+};
+
+namespace internal {
+
+enum class ParseContext {
+ /** An expression which may be begin with wrappers followed by a colon. */
+ WRAPPED_EXPR,
+ /** A miniscript expression which does not begin with wrappers. */
+ EXPR,
+
+ /** SWAP wraps the top constructed node with s: */
+ SWAP,
+ /** ALT wraps the top constructed node with a: */
+ ALT,
+ /** CHECK wraps the top constructed node with c: */
+ CHECK,
+ /** DUP_IF wraps the top constructed node with d: */
+ DUP_IF,
+ /** VERIFY wraps the top constructed node with v: */
+ VERIFY,
+ /** NON_ZERO wraps the top constructed node with j: */
+ NON_ZERO,
+ /** ZERO_NOTEQUAL wraps the top constructed node with n: */
+ ZERO_NOTEQUAL,
+ /** WRAP_U will construct an or_i(X,0) node from the top constructed node. */
+ WRAP_U,
+ /** WRAP_T will construct an and_v(X,1) node from the top constructed node. */
+ WRAP_T,
+
+ /** AND_N will construct an andor(X,Y,0) node from the last two constructed nodes. */
+ AND_N,
+ /** AND_V will construct an and_v node from the last two constructed nodes. */
+ AND_V,
+ /** AND_B will construct an and_b node from the last two constructed nodes. */
+ AND_B,
+ /** ANDOR will construct an andor node from the last three constructed nodes. */
+ ANDOR,
+ /** OR_B will construct an or_b node from the last two constructed nodes. */
+ OR_B,
+ /** OR_C will construct an or_c node from the last two constructed nodes. */
+ OR_C,
+ /** OR_D will construct an or_d node from the last two constructed nodes. */
+ OR_D,
+ /** OR_I will construct an or_i node from the last two constructed nodes. */
+ OR_I,
+
+ /** THRESH will read a wrapped expression, and then look for a COMMA. If
+ * no comma follows, it will construct a thresh node from the appropriate
+ * number of constructed children. Otherwise, it will recurse with another
+ * THRESH. */
+ THRESH,
+
+ /** COMMA expects the next element to be ',' and fails if not. */
+ COMMA,
+ /** CLOSE_BRACKET expects the next element to be ')' and fails if not. */
+ CLOSE_BRACKET,
+};
+
+int FindNextChar(Span<const char> in, const char m);
+
+/** Parse a key string ending with a ')' or ','. */
+template<typename Key, typename Ctx>
+std::optional<std::pair<Key, int>> ParseKeyEnd(Span<const char> in, const Ctx& ctx)
+{
+ Key key;
+ int key_size = FindNextChar(in, ')');
+ if (key_size < 1) return {};
+ if (!ctx.FromString(in.begin(), in.begin() + key_size, key)) return {};
+ return {{std::move(key), key_size}};
+}
+
+/** Parse a hex string ending at the end of the fragment's text representation. */
+template<typename Ctx>
+std::optional<std::pair<std::vector<unsigned char>, int>> ParseHexStrEnd(Span<const char> in, const size_t expected_size,
+ const Ctx& ctx)
+{
+ int hash_size = FindNextChar(in, ')');
+ if (hash_size < 1) return {};
+ std::string val = std::string(in.begin(), in.begin() + hash_size);
+ if (!IsHex(val)) return {};
+ auto hash = ParseHex(val);
+ if (hash.size() != expected_size) return {};
+ return {{std::move(hash), hash_size}};
+}
+
+/** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */
+template<typename Key>
+void BuildBack(Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false)
+{
+ NodeRef<Key> child = std::move(constructed.back());
+ constructed.pop_back();
+ if (reverse) {
+ constructed.back() = MakeNodeRef<Key>(nt, Vector(std::move(child), std::move(constructed.back())));
+ } else {
+ constructed.back() = MakeNodeRef<Key>(nt, Vector(std::move(constructed.back()), std::move(child)));
+ }
+}
+
+//! Parse a miniscript from its textual descriptor form.
+template<typename Key, typename Ctx>
+inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
+{
+ using namespace spanparsing;
+
+ // The two integers are used to hold state for thresh()
+ std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse;
+ std::vector<NodeRef<Key>> constructed;
+
+ to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+
+ while (!to_parse.empty()) {
+ // Get the current context we are decoding within
+ auto [cur_context, n, k] = to_parse.back();
+ to_parse.pop_back();
+
+ switch (cur_context) {
+ case ParseContext::WRAPPED_EXPR: {
+ int colon_index = -1;
+ for (int i = 1; i < (int)in.size(); ++i) {
+ if (in[i] == ':') {
+ colon_index = i;
+ break;
+ }
+ if (in[i] < 'a' || in[i] > 'z') break;
+ }
+ // If there is no colon, this loop won't execute
+ for (int j = 0; j < colon_index; ++j) {
+ if (in[j] == 'a') {
+ to_parse.emplace_back(ParseContext::ALT, -1, -1);
+ } else if (in[j] == 's') {
+ to_parse.emplace_back(ParseContext::SWAP, -1, -1);
+ } else if (in[j] == 'c') {
+ to_parse.emplace_back(ParseContext::CHECK, -1, -1);
+ } else if (in[j] == 'd') {
+ to_parse.emplace_back(ParseContext::DUP_IF, -1, -1);
+ } else if (in[j] == 'j') {
+ to_parse.emplace_back(ParseContext::NON_ZERO, -1, -1);
+ } else if (in[j] == 'n') {
+ to_parse.emplace_back(ParseContext::ZERO_NOTEQUAL, -1, -1);
+ } else if (in[j] == 'v') {
+ to_parse.emplace_back(ParseContext::VERIFY, -1, -1);
+ } else if (in[j] == 'u') {
+ to_parse.emplace_back(ParseContext::WRAP_U, -1, -1);
+ } else if (in[j] == 't') {
+ to_parse.emplace_back(ParseContext::WRAP_T, -1, -1);
+ } else if (in[j] == 'l') {
+ // The l: wrapper is equivalent to or_i(0,X)
+ constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0));
+ to_parse.emplace_back(ParseContext::OR_I, -1, -1);
+ } else {
+ return {};
+ }
+ }
+ to_parse.emplace_back(ParseContext::EXPR, -1, -1);
+ in = in.subspan(colon_index + 1);
+ break;
+ }
+ case ParseContext::EXPR: {
+ if (Const("0", in)) {
+ constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0));
+ } else if (Const("1", in)) {
+ constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_1));
+ } else if (Const("pk(", in)) {
+ auto res = ParseKeyEnd<Key, Ctx>(in, ctx);
+ if (!res) return {};
+ auto& [key, key_size] = *res;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::WRAP_C, Vector(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key))))));
+ in = in.subspan(key_size + 1);
+ } else if (Const("pkh(", in)) {
+ auto res = ParseKeyEnd<Key>(in, ctx);
+ if (!res) return {};
+ auto& [key, key_size] = *res;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::WRAP_C, Vector(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key))))));
+ in = in.subspan(key_size + 1);
+ } else if (Const("pk_k(", in)) {
+ auto res = ParseKeyEnd<Key>(in, ctx);
+ if (!res) return {};
+ auto& [key, key_size] = *res;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key))));
+ in = in.subspan(key_size + 1);
+ } else if (Const("pk_h(", in)) {
+ auto res = ParseKeyEnd<Key>(in, ctx);
+ if (!res) return {};
+ auto& [key, key_size] = *res;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key))));
+ in = in.subspan(key_size + 1);
+ } else if (Const("sha256(", in)) {
+ auto res = ParseHexStrEnd(in, 32, ctx);
+ if (!res) return {};
+ auto& [hash, hash_size] = *res;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::SHA256, std::move(hash)));
+ in = in.subspan(hash_size + 1);
+ } else if (Const("ripemd160(", in)) {
+ auto res = ParseHexStrEnd(in, 20, ctx);
+ if (!res) return {};
+ auto& [hash, hash_size] = *res;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::RIPEMD160, std::move(hash)));
+ in = in.subspan(hash_size + 1);
+ } else if (Const("hash256(", in)) {
+ auto res = ParseHexStrEnd(in, 32, ctx);
+ if (!res) return {};
+ auto& [hash, hash_size] = *res;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::HASH256, std::move(hash)));
+ in = in.subspan(hash_size + 1);
+ } else if (Const("hash160(", in)) {
+ auto res = ParseHexStrEnd(in, 20, ctx);
+ if (!res) return {};
+ auto& [hash, hash_size] = *res;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::HASH160, std::move(hash)));
+ in = in.subspan(hash_size + 1);
+ } else if (Const("after(", in)) {
+ int arg_size = FindNextChar(in, ')');
+ if (arg_size < 1) return {};
+ int64_t num;
+ if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {};
+ if (num < 1 || num >= 0x80000000L) return {};
+ constructed.push_back(MakeNodeRef<Key>(Fragment::AFTER, num));
+ in = in.subspan(arg_size + 1);
+ } else if (Const("older(", in)) {
+ int arg_size = FindNextChar(in, ')');
+ if (arg_size < 1) return {};
+ int64_t num;
+ if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {};
+ if (num < 1 || num >= 0x80000000L) return {};
+ constructed.push_back(MakeNodeRef<Key>(Fragment::OLDER, num));
+ in = in.subspan(arg_size + 1);
+ } else if (Const("multi(", in)) {
+ // Get threshold
+ int next_comma = FindNextChar(in, ',');
+ if (next_comma < 1) return {};
+ if (!ParseInt64(std::string(in.begin(), in.begin() + next_comma), &k)) return {};
+ in = in.subspan(next_comma + 1);
+ // Get keys
+ std::vector<Key> keys;
+ while (next_comma != -1) {
+ Key key;
+ next_comma = FindNextChar(in, ',');
+ int key_length = (next_comma == -1) ? FindNextChar(in, ')') : next_comma;
+ if (key_length < 1) return {};
+ if (!ctx.FromString(in.begin(), in.begin() + key_length, key)) return {};
+ keys.push_back(std::move(key));
+ in = in.subspan(key_length + 1);
+ }
+ if (keys.size() < 1 || keys.size() > 20) return {};
+ if (k < 1 || k > (int64_t)keys.size()) return {};
+ constructed.push_back(MakeNodeRef<Key>(Fragment::MULTI, std::move(keys), k));
+ } else if (Const("thresh(", in)) {
+ int next_comma = FindNextChar(in, ',');
+ if (next_comma < 1) return {};
+ if (!ParseInt64(std::string(in.begin(), in.begin() + next_comma), &k)) return {};
+ if (k < 1) return {};
+ in = in.subspan(next_comma + 1);
+ // n = 1 here because we read the first WRAPPED_EXPR before reaching THRESH
+ to_parse.emplace_back(ParseContext::THRESH, 1, k);
+ to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ } else if (Const("andor(", in)) {
+ to_parse.emplace_back(ParseContext::ANDOR, -1, -1);
+ to_parse.emplace_back(ParseContext::CLOSE_BRACKET, -1, -1);
+ to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ to_parse.emplace_back(ParseContext::COMMA, -1, -1);
+ to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ to_parse.emplace_back(ParseContext::COMMA, -1, -1);
+ to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ } else {
+ if (Const("and_n(", in)) {
+ to_parse.emplace_back(ParseContext::AND_N, -1, -1);
+ } else if (Const("and_b(", in)) {
+ to_parse.emplace_back(ParseContext::AND_B, -1, -1);
+ } else if (Const("and_v(", in)) {
+ to_parse.emplace_back(ParseContext::AND_V, -1, -1);
+ } else if (Const("or_b(", in)) {
+ to_parse.emplace_back(ParseContext::OR_B, -1, -1);
+ } else if (Const("or_c(", in)) {
+ to_parse.emplace_back(ParseContext::OR_C, -1, -1);
+ } else if (Const("or_d(", in)) {
+ to_parse.emplace_back(ParseContext::OR_D, -1, -1);
+ } else if (Const("or_i(", in)) {
+ to_parse.emplace_back(ParseContext::OR_I, -1, -1);
+ } else {
+ return {};
+ }
+ to_parse.emplace_back(ParseContext::CLOSE_BRACKET, -1, -1);
+ to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ to_parse.emplace_back(ParseContext::COMMA, -1, -1);
+ to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ }
+ break;
+ }
+ case ParseContext::ALT: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_A, Vector(std::move(constructed.back())));
+ break;
+ }
+ case ParseContext::SWAP: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_S, Vector(std::move(constructed.back())));
+ break;
+ }
+ case ParseContext::CHECK: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_C, Vector(std::move(constructed.back())));
+ break;
+ }
+ case ParseContext::DUP_IF: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_D, Vector(std::move(constructed.back())));
+ break;
+ }
+ case ParseContext::NON_ZERO: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_J, Vector(std::move(constructed.back())));
+ break;
+ }
+ case ParseContext::ZERO_NOTEQUAL: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_N, Vector(std::move(constructed.back())));
+ break;
+ }
+ case ParseContext::VERIFY: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_V, Vector(std::move(constructed.back())));
+ break;
+ }
+ case ParseContext::WRAP_U: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(Fragment::JUST_0)));
+ break;
+ }
+ case ParseContext::WRAP_T: {
+ constructed.back() = MakeNodeRef<Key>(Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(Fragment::JUST_1)));
+ break;
+ }
+ case ParseContext::AND_B: {
+ BuildBack(Fragment::AND_B, constructed);
+ break;
+ }
+ case ParseContext::AND_N: {
+ auto mid = std::move(constructed.back());
+ constructed.pop_back();
+ constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(Fragment::JUST_0)));
+ break;
+ }
+ case ParseContext::AND_V: {
+ BuildBack(Fragment::AND_V, constructed);
+ break;
+ }
+ case ParseContext::OR_B: {
+ BuildBack(Fragment::OR_B, constructed);
+ break;
+ }
+ case ParseContext::OR_C: {
+ BuildBack(Fragment::OR_C, constructed);
+ break;
+ }
+ case ParseContext::OR_D: {
+ BuildBack(Fragment::OR_D, constructed);
+ break;
+ }
+ case ParseContext::OR_I: {
+ BuildBack(Fragment::OR_I, constructed);
+ break;
+ }
+ case ParseContext::ANDOR: {
+ auto right = std::move(constructed.back());
+ constructed.pop_back();
+ auto mid = std::move(constructed.back());
+ constructed.pop_back();
+ constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right)));
+ break;
+ }
+ case ParseContext::THRESH: {
+ if (in.size() < 1) return {};
+ if (in[0] == ',') {
+ in = in.subspan(1);
+ to_parse.emplace_back(ParseContext::THRESH, n+1, k);
+ to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ } else if (in[0] == ')') {
+ if (k > n) return {};
+ in = in.subspan(1);
+ // Children are constructed in reverse order, so iterate from end to beginning
+ std::vector<NodeRef<Key>> subs;
+ for (int i = 0; i < n; ++i) {
+ subs.push_back(std::move(constructed.back()));
+ constructed.pop_back();
+ }
+ std::reverse(subs.begin(), subs.end());
+ constructed.push_back(MakeNodeRef<Key>(Fragment::THRESH, std::move(subs), k));
+ } else {
+ return {};
+ }
+ break;
+ }
+ case ParseContext::COMMA: {
+ if (in.size() < 1 || in[0] != ',') return {};
+ in = in.subspan(1);
+ break;
+ }
+ case ParseContext::CLOSE_BRACKET: {
+ if (in.size() < 1 || in[0] != ')') return {};
+ in = in.subspan(1);
+ break;
+ }
+ }
+ }
+
+ // Sanity checks on the produced miniscript
+ assert(constructed.size() == 1);
+ if (in.size() > 0) return {};
+ const NodeRef<Key> tl_node = std::move(constructed.front());
+ if (!tl_node->IsValidTopLevel()) return {};
+ return tl_node;
+}
+
+/** Decode a script into opcode/push pairs.
+ *
+ * Construct a vector with one element per opcode in the script, in reverse order.
+ * Each element is a pair consisting of the opcode, as well as the data pushed by
+ * the opcode (including OP_n), if any. OP_CHECKSIGVERIFY, OP_CHECKMULTISIGVERIFY,
+ * and OP_EQUALVERIFY are decomposed into OP_CHECKSIG, OP_CHECKMULTISIG, OP_EQUAL
+ * respectively, plus OP_VERIFY.
+ */
+bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, std::vector<unsigned char>>>& out);
+
+/** Determine whether the passed pair (created by DecomposeScript) is pushing a number. */
+bool ParseScriptNumber(const std::pair<opcodetype, std::vector<unsigned char>>& in, int64_t& k);
+
+enum class DecodeContext {
+ /** A single expression of type B, K, or V. Specifically, this can't be an
+ * and_v or an expression of type W (a: and s: wrappers). */
+ SINGLE_BKV_EXPR,
+ /** Potentially multiple SINGLE_BKV_EXPRs as children of (potentially multiple)
+ * and_v expressions. Syntactic sugar for MAYBE_AND_V + SINGLE_BKV_EXPR. */
+ BKV_EXPR,
+ /** An expression of type W (a: or s: wrappers). */
+ W_EXPR,
+
+ /** SWAP expects the next element to be OP_SWAP (inside a W-type expression that
+ * didn't end with FROMALTSTACK), and wraps the top of the constructed stack
+ * with s: */
+ SWAP,
+ /** ALT expects the next element to be TOALTSTACK (we must have already read a
+ * FROMALTSTACK earlier), and wraps the top of the constructed stack with a: */
+ ALT,
+ /** CHECK wraps the top constructed node with c: */
+ CHECK,
+ /** DUP_IF wraps the top constructed node with d: */
+ DUP_IF,
+ /** VERIFY wraps the top constructed node with v: */
+ VERIFY,
+ /** NON_ZERO wraps the top constructed node with j: */
+ NON_ZERO,
+ /** ZERO_NOTEQUAL wraps the top constructed node with n: */
+ ZERO_NOTEQUAL,
+
+ /** MAYBE_AND_V will check if the next part of the script could be a valid
+ * miniscript sub-expression, and if so it will push AND_V and SINGLE_BKV_EXPR
+ * to decode it and construct the and_v node. This is recursive, to deal with
+ * multiple and_v nodes inside each other. */
+ MAYBE_AND_V,
+ /** AND_V will construct an and_v node from the last two constructed nodes. */
+ AND_V,
+ /** AND_B will construct an and_b node from the last two constructed nodes. */
+ AND_B,
+ /** ANDOR will construct an andor node from the last three constructed nodes. */
+ ANDOR,
+ /** OR_B will construct an or_b node from the last two constructed nodes. */
+ OR_B,
+ /** OR_C will construct an or_c node from the last two constructed nodes. */
+ OR_C,
+ /** OR_D will construct an or_d node from the last two constructed nodes. */
+ OR_D,
+
+ /** In a thresh expression, all sub-expressions other than the first are W-type,
+ * and end in OP_ADD. THRESH_W will check for this OP_ADD and either push a W_EXPR
+ * or a SINGLE_BKV_EXPR and jump to THRESH_E accordingly. */
+ THRESH_W,
+ /** THRESH_E constructs a thresh node from the appropriate number of constructed
+ * children. */
+ THRESH_E,
+
+ /** ENDIF signals that we are inside some sort of OP_IF structure, which could be
+ * or_d, or_c, or_i, andor, d:, or j: wrapper, depending on what follows. We read
+ * a BKV_EXPR and then deal with the next opcode case-by-case. */
+ ENDIF,
+ /** If, inside an ENDIF context, we find an OP_NOTIF before finding an OP_ELSE,
+ * we could either be in an or_d or an or_c node. We then check for IFDUP to
+ * distinguish these cases. */
+ ENDIF_NOTIF,
+ /** If, inside an ENDIF context, we find an OP_ELSE, then we could be in either an
+ * or_i or an andor node. Read the next BKV_EXPR and find either an OP_IF or an
+ * OP_NOTIF. */
+ ENDIF_ELSE,
+};
+
+//! Parse a miniscript from a bitcoin script
+template<typename Key, typename Ctx, typename I>
+inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
+{
+ // The two integers are used to hold state for thresh()
+ std::vector<std::tuple<DecodeContext, int64_t, int64_t>> to_parse;
+ std::vector<NodeRef<Key>> constructed;
+
+ // This is the top level, so we assume the type is B
+ // (in particular, disallowing top level W expressions)
+ to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1);
+
+ while (!to_parse.empty()) {
+ // Exit early if the Miniscript is not going to be valid.
+ if (!constructed.empty() && !constructed.back()->IsValid()) return {};
+
+ // Get the current context we are decoding within
+ auto [cur_context, n, k] = to_parse.back();
+ to_parse.pop_back();
+
+ switch(cur_context) {
+ case DecodeContext::SINGLE_BKV_EXPR: {
+ if (in >= last) return {};
+
+ // Constants
+ if (in[0].first == OP_1) {
+ ++in;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_1));
+ break;
+ }
+ if (in[0].first == OP_0) {
+ ++in;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::JUST_0));
+ break;
+ }
+ // Public keys
+ if (in[0].second.size() == 33) {
+ Key key;
+ if (!ctx.FromPKBytes(in[0].second.begin(), in[0].second.end(), key)) return {};
+ ++in;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::PK_K, Vector(std::move(key))));
+ break;
+ }
+ if (last - in >= 5 && in[0].first == OP_VERIFY && in[1].first == OP_EQUAL && in[3].first == OP_HASH160 && in[4].first == OP_DUP && in[2].second.size() == 20) {
+ Key key;
+ if (!ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end(), key)) return {};
+ in += 5;
+ constructed.push_back(MakeNodeRef<Key>(Fragment::PK_H, Vector(std::move(key))));
+ break;
+ }
+ // Time locks
+ if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && ParseScriptNumber(in[1], k)) {
+ in += 2;
+ if (k < 1 || k > 0x7FFFFFFFL) return {};
+ constructed.push_back(MakeNodeRef<Key>(Fragment::OLDER, k));
+ break;
+ }
+ if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && ParseScriptNumber(in[1], k)) {
+ in += 2;
+ if (k < 1 || k > 0x7FFFFFFFL) return {};
+ constructed.push_back(MakeNodeRef<Key>(Fragment::AFTER, k));
+ break;
+ }
+ // Hashes
+ if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && ParseScriptNumber(in[5], k) && k == 32 && in[6].first == OP_SIZE) {
+ if (in[2].first == OP_SHA256 && in[1].second.size() == 32) {
+ constructed.push_back(MakeNodeRef<Key>(Fragment::SHA256, in[1].second));
+ in += 7;
+ break;
+ } else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) {
+ constructed.push_back(MakeNodeRef<Key>(Fragment::RIPEMD160, in[1].second));
+ in += 7;
+ break;
+ } else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) {
+ constructed.push_back(MakeNodeRef<Key>(Fragment::HASH256, in[1].second));
+ in += 7;
+ break;
+ } else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) {
+ constructed.push_back(MakeNodeRef<Key>(Fragment::HASH160, in[1].second));
+ in += 7;
+ break;
+ }
+ }
+ // Multi
+ if (last - in >= 3 && in[0].first == OP_CHECKMULTISIG) {
+ std::vector<Key> keys;
+ if (!ParseScriptNumber(in[1], n)) return {};
+ if (last - in < 3 + n) return {};
+ if (n < 1 || n > 20) return {};
+ for (int i = 0; i < n; ++i) {
+ Key key;
+ if (in[2 + i].second.size() != 33) return {};
+ if (!ctx.FromPKBytes(in[2 + i].second.begin(), in[2 + i].second.end(), key)) return {};
+ keys.push_back(std::move(key));
+ }
+ if (!ParseScriptNumber(in[2 + n], k)) return {};
+ if (k < 1 || k > n) return {};
+ in += 3 + n;
+ std::reverse(keys.begin(), keys.end());
+ constructed.push_back(MakeNodeRef<Key>(Fragment::MULTI, std::move(keys), k));
+ break;
+ }
+ /** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather
+ * than BKV_EXPR, because and_v commutes with these wrappers. For example,
+ * c:and_v(X,Y) produces the same script as and_v(X,c:Y). */
+ // c: wrapper
+ if (in[0].first == OP_CHECKSIG) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::CHECK, -1, -1);
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ break;
+ }
+ // v: wrapper
+ if (in[0].first == OP_VERIFY) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::VERIFY, -1, -1);
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ break;
+ }
+ // n: wrapper
+ if (in[0].first == OP_0NOTEQUAL) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::ZERO_NOTEQUAL, -1, -1);
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ break;
+ }
+ // Thresh
+ if (last - in >= 3 && in[0].first == OP_EQUAL && ParseScriptNumber(in[1], k)) {
+ if (k < 1) return {};
+ in += 2;
+ to_parse.emplace_back(DecodeContext::THRESH_W, 0, k);
+ break;
+ }
+ // OP_ENDIF can be WRAP_J, WRAP_D, ANDOR, OR_C, OR_D, or OR_I
+ if (in[0].first == OP_ENDIF) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::ENDIF, -1, -1);
+ to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1);
+ break;
+ }
+ /** In and_b and or_b nodes, we only look for SINGLE_BKV_EXPR, because
+ * or_b(and_v(X,Y),Z) has script [X] [Y] [Z] OP_BOOLOR, the same as
+ * and_v(X,or_b(Y,Z)). In this example, the former of these is invalid as
+ * miniscript, while the latter is valid. So we leave the and_v "outside"
+ * while decoding. */
+ // and_b
+ if (in[0].first == OP_BOOLAND) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::AND_B, -1, -1);
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ to_parse.emplace_back(DecodeContext::W_EXPR, -1, -1);
+ break;
+ }
+ // or_b
+ if (in[0].first == OP_BOOLOR) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::OR_B, -1, -1);
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ to_parse.emplace_back(DecodeContext::W_EXPR, -1, -1);
+ break;
+ }
+ // Unrecognised expression
+ return {};
+ }
+ case DecodeContext::BKV_EXPR: {
+ to_parse.emplace_back(DecodeContext::MAYBE_AND_V, -1, -1);
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ break;
+ }
+ case DecodeContext::W_EXPR: {
+ // a: wrapper
+ if (in >= last) return {};
+ if (in[0].first == OP_FROMALTSTACK) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::ALT, -1, -1);
+ } else {
+ to_parse.emplace_back(DecodeContext::SWAP, -1, -1);
+ }
+ to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1);
+ break;
+ }
+ case DecodeContext::MAYBE_AND_V: {
+ // If we reach a potential AND_V top-level, check if the next part of the script could be another AND_V child
+ // These op-codes cannot end any well-formed miniscript so cannot be used in an and_v node.
+ if (in < last && in[0].first != OP_IF && in[0].first != OP_ELSE && in[0].first != OP_NOTIF && in[0].first != OP_TOALTSTACK && in[0].first != OP_SWAP) {
+ to_parse.emplace_back(DecodeContext::AND_V, -1, -1);
+ // BKV_EXPR can contain more AND_V nodes
+ to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1);
+ }
+ break;
+ }
+ case DecodeContext::SWAP: {
+ if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {};
+ ++in;
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_S, Vector(std::move(constructed.back())));
+ break;
+ }
+ case DecodeContext::ALT: {
+ if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {};
+ ++in;
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_A, Vector(std::move(constructed.back())));
+ break;
+ }
+ case DecodeContext::CHECK: {
+ if (constructed.empty()) return {};
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_C, Vector(std::move(constructed.back())));
+ break;
+ }
+ case DecodeContext::DUP_IF: {
+ if (constructed.empty()) return {};
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_D, Vector(std::move(constructed.back())));
+ break;
+ }
+ case DecodeContext::VERIFY: {
+ if (constructed.empty()) return {};
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_V, Vector(std::move(constructed.back())));
+ break;
+ }
+ case DecodeContext::NON_ZERO: {
+ if (constructed.empty()) return {};
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_J, Vector(std::move(constructed.back())));
+ break;
+ }
+ case DecodeContext::ZERO_NOTEQUAL: {
+ if (constructed.empty()) return {};
+ constructed.back() = MakeNodeRef<Key>(Fragment::WRAP_N, Vector(std::move(constructed.back())));
+ break;
+ }
+ case DecodeContext::AND_V: {
+ if (constructed.size() < 2) return {};
+ BuildBack(Fragment::AND_V, constructed, /*reverse=*/true);
+ break;
+ }
+ case DecodeContext::AND_B: {
+ if (constructed.size() < 2) return {};
+ BuildBack(Fragment::AND_B, constructed, /*reverse=*/true);
+ break;
+ }
+ case DecodeContext::OR_B: {
+ if (constructed.size() < 2) return {};
+ BuildBack(Fragment::OR_B, constructed, /*reverse=*/true);
+ break;
+ }
+ case DecodeContext::OR_C: {
+ if (constructed.size() < 2) return {};
+ BuildBack(Fragment::OR_C, constructed, /*reverse=*/true);
+ break;
+ }
+ case DecodeContext::OR_D: {
+ if (constructed.size() < 2) return {};
+ BuildBack(Fragment::OR_D, constructed, /*reverse=*/true);
+ break;
+ }
+ case DecodeContext::ANDOR: {
+ if (constructed.size() < 3) return {};
+ NodeRef<Key> left = std::move(constructed.back());
+ constructed.pop_back();
+ NodeRef<Key> right = std::move(constructed.back());
+ constructed.pop_back();
+ NodeRef<Key> mid = std::move(constructed.back());
+ constructed.back() = MakeNodeRef<Key>(Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right)));
+ break;
+ }
+ case DecodeContext::THRESH_W: {
+ if (in >= last) return {};
+ if (in[0].first == OP_ADD) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::THRESH_W, n+1, k);
+ to_parse.emplace_back(DecodeContext::W_EXPR, -1, -1);
+ } else {
+ to_parse.emplace_back(DecodeContext::THRESH_E, n+1, k);
+ // All children of thresh have type modifier d, so cannot be and_v
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ }
+ break;
+ }
+ case DecodeContext::THRESH_E: {
+ if (k < 1 || k > n || constructed.size() < static_cast<size_t>(n)) return {};
+ std::vector<NodeRef<Key>> subs;
+ for (int i = 0; i < n; ++i) {
+ NodeRef<Key> sub = std::move(constructed.back());
+ constructed.pop_back();
+ subs.push_back(std::move(sub));
+ }
+ constructed.push_back(MakeNodeRef<Key>(Fragment::THRESH, std::move(subs), k));
+ break;
+ }
+ case DecodeContext::ENDIF: {
+ if (in >= last) return {};
+
+ // could be andor or or_i
+ if (in[0].first == OP_ELSE) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::ENDIF_ELSE, -1, -1);
+ to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1);
+ }
+ // could be j: or d: wrapper
+ else if (in[0].first == OP_IF) {
+ if (last - in >= 2 && in[1].first == OP_DUP) {
+ in += 2;
+ to_parse.emplace_back(DecodeContext::DUP_IF, -1, -1);
+ } else if (last - in >= 3 && in[1].first == OP_0NOTEQUAL && in[2].first == OP_SIZE) {
+ in += 3;
+ to_parse.emplace_back(DecodeContext::NON_ZERO, -1, -1);
+ }
+ else {
+ return {};
+ }
+ // could be or_c or or_d
+ } else if (in[0].first == OP_NOTIF) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::ENDIF_NOTIF, -1, -1);
+ }
+ else {
+ return {};
+ }
+ break;
+ }
+ case DecodeContext::ENDIF_NOTIF: {
+ if (in >= last) return {};
+ if (in[0].first == OP_IFDUP) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::OR_D, -1, -1);
+ } else {
+ to_parse.emplace_back(DecodeContext::OR_C, -1, -1);
+ }
+ // or_c and or_d both require X to have type modifier d so, can't contain and_v
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ break;
+ }
+ case DecodeContext::ENDIF_ELSE: {
+ if (in >= last) return {};
+ if (in[0].first == OP_IF) {
+ ++in;
+ BuildBack(Fragment::OR_I, constructed, /*reverse=*/true);
+ } else if (in[0].first == OP_NOTIF) {
+ ++in;
+ to_parse.emplace_back(DecodeContext::ANDOR, -1, -1);
+ // andor requires X to have type modifier d, so it can't be and_v
+ to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1);
+ } else {
+ return {};
+ }
+ break;
+ }
+ }
+ }
+ if (constructed.size() != 1) return {};
+ const NodeRef<Key> tl_node = std::move(constructed.front());
+ // Note that due to how ComputeType works (only assign the type to the node if the
+ // subs' types are valid) this would fail if any node of tree is badly typed.
+ if (!tl_node->IsValidTopLevel()) return {};
+ return tl_node;
+}
+
+} // namespace internal
+
+template<typename Ctx>
+inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx& ctx) {
+ return internal::Parse<typename Ctx::Key>(str, ctx);
+}
+
+template<typename Ctx>
+inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) {
+ using namespace internal;
+ std::vector<std::pair<opcodetype, std::vector<unsigned char>>> decomposed;
+ if (!DecomposeScript(script, decomposed)) return {};
+ auto it = decomposed.begin();
+ auto ret = DecodeScript<typename Ctx::Key>(it, decomposed.end(), ctx);
+ if (!ret) return {};
+ if (it != decomposed.end()) return {};
+ return ret;
+}
+
+} // namespace miniscript
+
+#endif // BITCOIN_SCRIPT_MINISCRIPT_H
diff --git a/src/script/script.cpp b/src/script/script.cpp
index 9a6419088b..88b4bc2f44 100644
--- a/src/script/script.cpp
+++ b/src/script/script.cpp
@@ -339,3 +339,28 @@ bool IsOpSuccess(const opcodetype& opcode)
(opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
(opcode >= 187 && opcode <= 254);
}
+
+bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode) {
+ // Excludes OP_1NEGATE, OP_1-16 since they are by definition minimal
+ assert(0 <= opcode && opcode <= OP_PUSHDATA4);
+ if (data.size() == 0) {
+ // Should have used OP_0.
+ return opcode == OP_0;
+ } else if (data.size() == 1 && data[0] >= 1 && data[0] <= 16) {
+ // Should have used OP_1 .. OP_16.
+ return false;
+ } else if (data.size() == 1 && data[0] == 0x81) {
+ // Should have used OP_1NEGATE.
+ return false;
+ } else if (data.size() <= 75) {
+ // Must have used a direct push (opcode indicating number of bytes pushed + those bytes).
+ return opcode == data.size();
+ } else if (data.size() <= 255) {
+ // Must have used OP_PUSHDATA.
+ return opcode == OP_PUSHDATA1;
+ } else if (data.size() <= 65535) {
+ // Must have used OP_PUSHDATA2.
+ return opcode == OP_PUSHDATA2;
+ }
+ return true;
+}
diff --git a/src/script/script.h b/src/script/script.h
index a89c987306..3b799ad637 100644
--- a/src/script/script.h
+++ b/src/script/script.h
@@ -335,6 +335,8 @@ public:
return m_value;
}
+ int64_t GetInt64() const { return m_value; }
+
std::vector<unsigned char> getvch() const
{
return serialize(m_value);
@@ -576,4 +578,31 @@ struct CScriptWitness
/** Test for OP_SUCCESSx opcodes as defined by BIP342. */
bool IsOpSuccess(const opcodetype& opcode);
+bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode);
+
+/** Build a script by concatenating other scripts, or any argument accepted by CScript::operator<<. */
+template<typename... Ts>
+CScript BuildScript(Ts&&... inputs)
+{
+ CScript ret;
+ int cnt{0};
+
+ ([&ret, &cnt] (Ts&& input) {
+ cnt++;
+ if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<Ts>>, CScript>) {
+ // If it is a CScript, extend ret with it. Move or copy the first element instead.
+ if (cnt == 0) {
+ ret = std::forward<Ts>(input);
+ } else {
+ ret.insert(ret.end(), input.begin(), input.end());
+ }
+ } else {
+ // Otherwise invoke CScript::operator<<.
+ ret << input;
+ }
+ } (std::forward<Ts>(inputs)), ...);
+
+ return ret;
+}
+
#endif // BITCOIN_SCRIPT_SCRIPT_H
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 2e5c49e0b6..d77515f16c 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -655,7 +655,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
CTxIn& txin = mtx.vin[i];
auto coin = coins.find(txin.prevout);
if (coin == coins.end() || coin->second.IsSpent()) {
- txdata.Init(txConst, /* spent_outputs */ {}, /* force */ true);
+ txdata.Init(txConst, /*spent_outputs=*/{}, /*force=*/true);
break;
} else {
spent_outputs.emplace_back(coin->second.out.nValue, coin->second.out.scriptPubKey);
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index b77c78769f..e25155d3dd 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -91,11 +91,6 @@ static constexpr bool IsSmallInteger(opcodetype opcode)
return opcode >= OP_1 && opcode <= OP_16;
}
-static constexpr bool IsPushdataOp(opcodetype opcode)
-{
- return opcode > OP_FALSE && opcode <= OP_PUSHDATA4;
-}
-
/** Retrieve a minimally-encoded number in range [min,max] from an (opcode, data) pair,
* whether it's OP_n or through a push. */
static std::optional<int> GetScriptNumber(opcodetype opcode, valtype data, int min, int max)
diff --git a/src/script/standard.h b/src/script/standard.h
index 75bfe2db38..f0b143c52b 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -162,6 +162,11 @@ bool IsValidDestination(const CTxDestination& dest);
/** Get the name of a TxoutType as a string */
std::string GetTxnOutputType(TxoutType t);
+constexpr bool IsPushdataOp(opcodetype opcode)
+{
+ return opcode > OP_FALSE && opcode <= OP_PUSHDATA4;
+}
+
/**
* Parse a scriptPubKey and identify script type for standard scripts. If
* successful, returns script type and parsed pubkeys or hashes, depending on
diff --git a/src/secp256k1/.cirrus.yml b/src/secp256k1/.cirrus.yml
index 35a9a45367..a2e7f36d1f 100644
--- a/src/secp256k1/.cirrus.yml
+++ b/src/secp256k1/.cirrus.yml
@@ -4,10 +4,10 @@ env:
# Specific warnings can be disabled with -Wno-error=foo.
# -pedantic-errors is not equivalent to -Werror=pedantic and thus not implied by -Werror according to the GCC manual.
WERROR_CFLAGS: -Werror -pedantic-errors
- MAKEFLAGS: -j2
+ MAKEFLAGS: -j4
BUILD: check
### secp256k1 config
- STATICPRECOMPUTATION: yes
+ ECMULTWINDOW: auto
ECMULTGENPRECISION: auto
ASM: no
WIDEMUL: auto
@@ -23,6 +23,8 @@ env:
BENCH: yes
SECP256K1_BENCH_ITERS: 2
CTIMETEST: yes
+ # Compile and run the tests
+ EXAMPLES: yes
cat_logs_snippet: &CAT_LOGS
always:
@@ -50,28 +52,32 @@ merge_base_script_snippet: &MERGE_BASE
- git config --global user.name "ci"
- git merge FETCH_HEAD # Merge base to detect silent merge conflicts
-task:
- name: "x86_64: Linux (Debian stable)"
+linux_container_snippet: &LINUX_CONTAINER
container:
dockerfile: ci/linux-debian.Dockerfile
# Reduce number of CPUs to be able to do more builds in parallel.
cpu: 1
+ # Gives us more CPUs for free if they're available.
+ greedy: true
# More than enough for our scripts.
memory: 1G
+
+task:
+ name: "x86_64: Linux (Debian stable)"
+ << : *LINUX_CONTAINER
matrix: &ENV_MATRIX
- env: {WIDEMUL: int64, RECOVERY: yes}
- - env: {WIDEMUL: int64, ECDH: yes, EXPERIMENTAL: yes, SCHNORRSIG: yes}
+ - env: {WIDEMUL: int64, ECDH: yes, SCHNORRSIG: yes}
- env: {WIDEMUL: int128}
- - env: {WIDEMUL: int128, RECOVERY: yes, EXPERIMENTAL: yes, SCHNORRSIG: yes}
- - env: {WIDEMUL: int128, ECDH: yes, EXPERIMENTAL: yes, SCHNORRSIG: yes}
+ - env: {WIDEMUL: int128, RECOVERY: yes, SCHNORRSIG: yes}
+ - env: {WIDEMUL: int128, ECDH: yes, SCHNORRSIG: yes}
- env: {WIDEMUL: int128, ASM: x86_64}
- - env: { RECOVERY: yes, EXPERIMENTAL: yes, SCHNORRSIG: yes}
- - env: { STATICPRECOMPUTATION: no}
+ - env: { RECOVERY: yes, SCHNORRSIG: yes}
- env: {BUILD: distcheck, WITH_VALGRIND: no, CTIMETEST: no, BENCH: no}
- env: {CPPFLAGS: -DDETERMINISTIC}
- env: {CFLAGS: -O0, CTIMETEST: no}
- - env: { ECMULTGENPRECISION: 2 }
- - env: { ECMULTGENPRECISION: 8 }
+ - env: { ECMULTGENPRECISION: 2, ECMULTWINDOW: 2 }
+ - env: { ECMULTGENPRECISION: 8, ECMULTWINDOW: 4 }
matrix:
- env:
CC: gcc
@@ -84,15 +90,11 @@ task:
task:
name: "i686: Linux (Debian stable)"
- container:
- dockerfile: ci/linux-debian.Dockerfile
- cpu: 1
- memory: 1G
+ << : *LINUX_CONTAINER
env:
HOST: i686-linux-gnu
ECDH: yes
RECOVERY: yes
- EXPERIMENTAL: yes
SCHNORRSIG: yes
matrix:
- env:
@@ -134,8 +136,10 @@ task:
## - rm /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
##
brew_valgrind_pre_script:
+ # Retry a few times because this tends to fail randomly.
+ - for i in {1..5}; do brew update && break || sleep 15; done
- brew config
- - brew tap --shallow LouisBrunner/valgrind
+ - brew tap LouisBrunner/valgrind
# Fetch valgrind source but don't build it yet.
- brew fetch --HEAD LouisBrunner/valgrind/valgrind
brew_valgrind_cache:
@@ -165,10 +169,7 @@ task:
task:
name: "s390x (big-endian): Linux (Debian stable, QEMU)"
- container:
- dockerfile: ci/linux-debian.Dockerfile
- cpu: 1
- memory: 1G
+ << : *LINUX_CONTAINER
env:
WRAPPER_CMD: qemu-s390x
SECP256K1_TEST_ITERS: 16
@@ -176,7 +177,6 @@ task:
WITH_VALGRIND: no
ECDH: yes
RECOVERY: yes
- EXPERIMENTAL: yes
SCHNORRSIG: yes
CTIMETEST: no
<< : *MERGE_BASE
@@ -188,10 +188,7 @@ task:
task:
name: "ARM32: Linux (Debian stable, QEMU)"
- container:
- dockerfile: ci/linux-debian.Dockerfile
- cpu: 1
- memory: 1G
+ << : *LINUX_CONTAINER
env:
WRAPPER_CMD: qemu-arm
SECP256K1_TEST_ITERS: 16
@@ -199,12 +196,11 @@ task:
WITH_VALGRIND: no
ECDH: yes
RECOVERY: yes
- EXPERIMENTAL: yes
SCHNORRSIG: yes
CTIMETEST: no
matrix:
- env: {}
- - env: {ASM: arm}
+ - env: {EXPERIMENTAL: yes, ASM: arm}
<< : *MERGE_BASE
test_script:
- ./ci/cirrus.sh
@@ -212,10 +208,7 @@ task:
task:
name: "ARM64: Linux (Debian stable, QEMU)"
- container:
- dockerfile: ci/linux-debian.Dockerfile
- cpu: 1
- memory: 1G
+ << : *LINUX_CONTAINER
env:
WRAPPER_CMD: qemu-aarch64
SECP256K1_TEST_ITERS: 16
@@ -223,7 +216,6 @@ task:
WITH_VALGRIND: no
ECDH: yes
RECOVERY: yes
- EXPERIMENTAL: yes
SCHNORRSIG: yes
CTIMETEST: no
<< : *MERGE_BASE
@@ -233,10 +225,7 @@ task:
task:
name: "ppc64le: Linux (Debian stable, QEMU)"
- container:
- dockerfile: ci/linux-debian.Dockerfile
- cpu: 1
- memory: 1G
+ << : *LINUX_CONTAINER
env:
WRAPPER_CMD: qemu-ppc64le
SECP256K1_TEST_ITERS: 16
@@ -244,7 +233,6 @@ task:
WITH_VALGRIND: no
ECDH: yes
RECOVERY: yes
- EXPERIMENTAL: yes
SCHNORRSIG: yes
CTIMETEST: no
<< : *MERGE_BASE
@@ -254,10 +242,7 @@ task:
task:
name: "x86_64 (mingw32-w64): Windows (Debian stable, Wine)"
- container:
- dockerfile: ci/linux-debian.Dockerfile
- cpu: 1
- memory: 1G
+ << : *LINUX_CONTAINER
env:
WRAPPER_CMD: wine64-stable
SECP256K1_TEST_ITERS: 16
@@ -265,7 +250,6 @@ task:
WITH_VALGRIND: no
ECDH: yes
RECOVERY: yes
- EXPERIMENTAL: yes
SCHNORRSIG: yes
CTIMETEST: no
<< : *MERGE_BASE
@@ -275,23 +259,23 @@ task:
# Sanitizers
task:
- container:
- dockerfile: ci/linux-debian.Dockerfile
- cpu: 1
- memory: 2G
+ << : *LINUX_CONTAINER
env:
ECDH: yes
RECOVERY: yes
- EXPERIMENTAL: yes
SCHNORRSIG: yes
CTIMETEST: no
matrix:
- name: "Valgrind (memcheck)"
+ container:
+ cpu: 2
env:
# The `--error-exitcode` is required to make the test fail if valgrind found errors, otherwise it'll return 0 (https://www.valgrind.org/docs/manual/manual-core.html)
WRAPPER_CMD: "valgrind --error-exitcode=42"
SECP256K1_TEST_ITERS: 2
- name: "UBSan, ASan, LSan"
+ container:
+ memory: 2G
env:
CFLAGS: "-fsanitize=undefined,address -g"
UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
@@ -302,11 +286,10 @@ task:
matrix:
- env:
ASM: auto
- STATICPRECOMPUTATION: yes
- env:
ASM: no
- STATICPRECOMPUTATION: no
ECMULTGENPRECISION: 2
+ ECMULTWINDOW: 2
matrix:
- env:
CC: clang
@@ -320,17 +303,13 @@ task:
task:
name: "C++ -fpermissive"
- container:
- dockerfile: ci/linux-debian.Dockerfile
- cpu: 1
- memory: 1G
+ << : *LINUX_CONTAINER
env:
# ./configure correctly errors out when given CC=g++.
# We hack around this by passing CC=g++ only to make.
CC: gcc
- MAKEFLAGS: -j2 CC=g++ CFLAGS=-fpermissive\ -g
+ MAKEFLAGS: -j4 CC=g++ CFLAGS=-fpermissive\ -g
WERROR_CFLAGS:
- EXPERIMENTAL: yes
ECDH: yes
RECOVERY: yes
SCHNORRSIG: yes
@@ -338,3 +317,10 @@ task:
test_script:
- ./ci/cirrus.sh
<< : *CAT_LOGS
+
+task:
+ name: "sage prover"
+ << : *LINUX_CONTAINER
+ test_script:
+ - cd sage
+ - sage prove_group_implementations.sage
diff --git a/src/secp256k1/.gitattributes b/src/secp256k1/.gitattributes
index a0fa567da8..30efb2244f 100644
--- a/src/secp256k1/.gitattributes
+++ b/src/secp256k1/.gitattributes
@@ -1,2 +1,2 @@
-src/ecmult_static_pre_g.h linguist-generated
-src/ecmult_gen_static_prec_table.h linguist-generated
+src/precomputed_ecmult.c linguist-generated
+src/precomputed_ecmult_gen.c linguist-generated
diff --git a/src/secp256k1/.gitignore b/src/secp256k1/.gitignore
index 22cd500501..d88627d72e 100644
--- a/src/secp256k1/.gitignore
+++ b/src/secp256k1/.gitignore
@@ -3,14 +3,19 @@ bench_ecmult
bench_internal
tests
exhaustive_tests
-gen_ecmult_gen_static_prec_table
-gen_ecmult_static_pre_g
+precompute_ecmult_gen
+precompute_ecmult
valgrind_ctime_test
+ecdh_example
+ecdsa_example
+schnorr_example
*.exe
*.so
*.a
*.csv
!.gitignore
+*.log
+*.trs
Makefile
configure
@@ -41,6 +46,7 @@ coverage.*.html
src/libsecp256k1-config.h
src/libsecp256k1-config.h.in
+build-aux/ar-lib
build-aux/config.guess
build-aux/config.sub
build-aux/depcomp
diff --git a/src/secp256k1/Makefile.am b/src/secp256k1/Makefile.am
index 7ea29bc6e3..51c5960301 100644
--- a/src/secp256k1/Makefile.am
+++ b/src/secp256k1/Makefile.am
@@ -26,12 +26,14 @@ noinst_HEADERS += src/eckey.h
noinst_HEADERS += src/eckey_impl.h
noinst_HEADERS += src/ecmult.h
noinst_HEADERS += src/ecmult_impl.h
+noinst_HEADERS += src/ecmult_compute_table.h
+noinst_HEADERS += src/ecmult_compute_table_impl.h
noinst_HEADERS += src/ecmult_const.h
noinst_HEADERS += src/ecmult_const_impl.h
noinst_HEADERS += src/ecmult_gen.h
noinst_HEADERS += src/ecmult_gen_impl.h
-noinst_HEADERS += src/ecmult_gen_prec.h
-noinst_HEADERS += src/ecmult_gen_prec_impl.h
+noinst_HEADERS += src/ecmult_gen_compute_table.h
+noinst_HEADERS += src/ecmult_gen_compute_table_impl.h
noinst_HEADERS += src/field_10x26.h
noinst_HEADERS += src/field_10x26_impl.h
noinst_HEADERS += src/field_5x52.h
@@ -42,6 +44,8 @@ noinst_HEADERS += src/modinv32.h
noinst_HEADERS += src/modinv32_impl.h
noinst_HEADERS += src/modinv64.h
noinst_HEADERS += src/modinv64_impl.h
+noinst_HEADERS += src/precomputed_ecmult.h
+noinst_HEADERS += src/precomputed_ecmult_gen.h
noinst_HEADERS += src/assumptions.h
noinst_HEADERS += src/util.h
noinst_HEADERS += src/scratch.h
@@ -59,13 +63,19 @@ noinst_HEADERS += contrib/lax_der_parsing.h
noinst_HEADERS += contrib/lax_der_parsing.c
noinst_HEADERS += contrib/lax_der_privatekey_parsing.h
noinst_HEADERS += contrib/lax_der_privatekey_parsing.c
+noinst_HEADERS += examples/random.h
+
+PRECOMPUTED_LIB = libsecp256k1_precomputed.la
+noinst_LTLIBRARIES = $(PRECOMPUTED_LIB)
+libsecp256k1_precomputed_la_SOURCES = src/precomputed_ecmult.c src/precomputed_ecmult_gen.c
+libsecp256k1_precomputed_la_CPPFLAGS = $(SECP_INCLUDES)
if USE_EXTERNAL_ASM
COMMON_LIB = libsecp256k1_common.la
-noinst_LTLIBRARIES = $(COMMON_LIB)
else
COMMON_LIB =
endif
+noinst_LTLIBRARIES += $(COMMON_LIB)
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libsecp256k1.pc
@@ -78,8 +88,8 @@ endif
libsecp256k1_la_SOURCES = src/secp256k1.c
libsecp256k1_la_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src $(SECP_INCLUDES)
-libsecp256k1_la_LIBADD = $(SECP_LIBS) $(COMMON_LIB)
-libsecp256k1_la_LDFLAGS = -no-undefined
+libsecp256k1_la_LIBADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB)
+libsecp256k1_la_LDFLAGS = -no-undefined -version-info $(LIB_VERSION_CURRENT):$(LIB_VERSION_REVISION):$(LIB_VERSION_AGE)
if VALGRIND_ENABLED
libsecp256k1_la_CPPFLAGS += -DVALGRIND
@@ -91,10 +101,10 @@ noinst_PROGRAMS += bench bench_internal bench_ecmult
bench_SOURCES = src/bench.c
bench_LDADD = libsecp256k1.la $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB)
bench_internal_SOURCES = src/bench_internal.c
-bench_internal_LDADD = $(SECP_LIBS) $(COMMON_LIB)
+bench_internal_LDADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB)
bench_internal_CPPFLAGS = $(SECP_INCLUDES)
bench_ecmult_SOURCES = src/bench_ecmult.c
-bench_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB)
+bench_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB)
bench_ecmult_CPPFLAGS = $(SECP_INCLUDES)
endif
@@ -112,7 +122,7 @@ endif
if !ENABLE_COVERAGE
tests_CPPFLAGS += -DVERIFY
endif
-tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB)
+tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB)
tests_LDFLAGS = -static
TESTS += tests
endif
@@ -124,22 +134,57 @@ exhaustive_tests_CPPFLAGS = $(SECP_INCLUDES)
if !ENABLE_COVERAGE
exhaustive_tests_CPPFLAGS += -DVERIFY
endif
+# Note: do not include $(PRECOMPUTED_LIB) in exhaustive_tests (it uses runtime-generated tables).
exhaustive_tests_LDADD = $(SECP_LIBS) $(COMMON_LIB)
exhaustive_tests_LDFLAGS = -static
TESTS += exhaustive_tests
endif
+if USE_EXAMPLES
+noinst_PROGRAMS += ecdsa_example
+ecdsa_example_SOURCES = examples/ecdsa.c
+ecdsa_example_CPPFLAGS = -I$(top_srcdir)/include
+ecdsa_example_LDADD = libsecp256k1.la
+ecdsa_example_LDFLAGS = -static
+if BUILD_WINDOWS
+ecdsa_example_LDFLAGS += -lbcrypt
+endif
+TESTS += ecdsa_example
+if ENABLE_MODULE_ECDH
+noinst_PROGRAMS += ecdh_example
+ecdh_example_SOURCES = examples/ecdh.c
+ecdh_example_CPPFLAGS = -I$(top_srcdir)/include
+ecdh_example_LDADD = libsecp256k1.la
+ecdh_example_LDFLAGS = -static
+if BUILD_WINDOWS
+ecdh_example_LDFLAGS += -lbcrypt
+endif
+TESTS += ecdh_example
+endif
+if ENABLE_MODULE_SCHNORRSIG
+noinst_PROGRAMS += schnorr_example
+schnorr_example_SOURCES = examples/schnorr.c
+schnorr_example_CPPFLAGS = -I$(top_srcdir)/include
+schnorr_example_LDADD = libsecp256k1.la
+schnorr_example_LDFLAGS = -static
+if BUILD_WINDOWS
+schnorr_example_LDFLAGS += -lbcrypt
+endif
+TESTS += schnorr_example
+endif
+endif
+
### Precomputed tables
-EXTRA_PROGRAMS = gen_ecmult_static_pre_g gen_ecmult_gen_static_prec_table
+EXTRA_PROGRAMS = precompute_ecmult precompute_ecmult_gen
CLEANFILES = $(EXTRA_PROGRAMS)
-gen_ecmult_static_pre_g_SOURCES = src/gen_ecmult_static_pre_g.c
-gen_ecmult_static_pre_g_CPPFLAGS = $(SECP_INCLUDES)
-gen_ecmult_static_pre_g_LDADD = $(SECP_LIBS) $(COMMON_LIB)
+precompute_ecmult_SOURCES = src/precompute_ecmult.c
+precompute_ecmult_CPPFLAGS = $(SECP_INCLUDES)
+precompute_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB)
-gen_ecmult_gen_static_prec_table_SOURCES = src/gen_ecmult_gen_static_prec_table.c
-gen_ecmult_gen_static_prec_table_CPPFLAGS = $(SECP_INCLUDES)
-gen_ecmult_gen_static_prec_table_LDADD = $(SECP_LIBS) $(COMMON_LIB)
+precompute_ecmult_gen_SOURCES = src/precompute_ecmult_gen.c
+precompute_ecmult_gen_CPPFLAGS = $(SECP_INCLUDES)
+precompute_ecmult_gen_LDADD = $(SECP_LIBS) $(COMMON_LIB)
# See Automake manual, Section "Errors with distclean".
# We don't list any dependencies for the prebuilt files here because
@@ -147,15 +192,14 @@ gen_ecmult_gen_static_prec_table_LDADD = $(SECP_LIBS) $(COMMON_LIB)
# build by a normal user) depends on mtimes, and thus is very fragile.
# This means that rebuilds of the prebuilt files always need to be
# forced by deleting them, e.g., by invoking `make clean-precomp`.
-src/ecmult_static_pre_g.h:
- $(MAKE) $(AM_MAKEFLAGS) gen_ecmult_static_pre_g$(EXEEXT)
- ./gen_ecmult_static_pre_g$(EXEEXT)
-src/ecmult_gen_static_prec_table.h:
- $(MAKE) $(AM_MAKEFLAGS) gen_ecmult_gen_static_prec_table$(EXEEXT)
- ./gen_ecmult_gen_static_prec_table$(EXEEXT)
-
-PRECOMP = src/ecmult_gen_static_prec_table.h src/ecmult_static_pre_g.h
-noinst_HEADERS += $(PRECOMP)
+src/precomputed_ecmult.c:
+ $(MAKE) $(AM_MAKEFLAGS) precompute_ecmult$(EXEEXT)
+ ./precompute_ecmult$(EXEEXT)
+src/precomputed_ecmult_gen.c:
+ $(MAKE) $(AM_MAKEFLAGS) precompute_ecmult_gen$(EXEEXT)
+ ./precompute_ecmult_gen$(EXEEXT)
+
+PRECOMP = src/precomputed_ecmult_gen.c src/precomputed_ecmult.c
precomp: $(PRECOMP)
# Ensure the prebuilt files will be build first (only if they don't exist,
diff --git a/src/secp256k1/README.md b/src/secp256k1/README.md
index 5fc07dd4fa..f5db915e83 100644
--- a/src/secp256k1/README.md
+++ b/src/secp256k1/README.md
@@ -17,9 +17,7 @@ Features:
* Suitable for embedded systems.
* Optional module for public key recovery.
* Optional module for ECDH key exchange.
-* Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) (experimental).
-
-Experimental features have not received enough scrutiny to satisfy the standard of quality of this library but are made available for testing and review by the community. The APIs of these features should not be considered stable.
+* Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
Implementation details
----------------------
@@ -35,6 +33,7 @@ Implementation details
* Optimized implementation of arithmetic modulo the curve's field size (2^256 - 0x1000003D1).
* Using 5 52-bit limbs (including hand-optimized assembly for x86_64, by Diederik Huys).
* Using 10 26-bit limbs (including hand-optimized assembly for 32-bit ARM, by Wladimir J. van der Laan).
+ * This is an experimental feature that has not received enough scrutiny to satisfy the standard of quality of this library but is made available for testing and review by the community.
* Scalar operations
* Optimized implementation without data-dependent branches of arithmetic modulo the curve's order.
* Using 4 64-bit limbs (relying on __int128 support in the compiler).
@@ -69,6 +68,16 @@ libsecp256k1 is built using autotools:
$ make check # run the test suite
$ sudo make install # optional
+To compile optional modules (such as Schnorr signatures), you need to run `./configure` with additional flags (such as `--enable-module-schnorrsig`). Run `./configure --help` to see the full list of available flags.
+
+Usage examples
+-----------
+ Usage examples can be found in the [examples](examples) directory. To compile them you need to configure with `--enable-examples`.
+ * [ECDSA example](examples/ecdsa.c)
+ * [Schnorr signatures example](examples/schnorr.c)
+ * [Deriving a shared secret (ECDH) example](examples/ecdh.c)
+ To compile the Schnorr signature and ECDH examples, you also need to configure with `--enable-module-schnorrsig` and `--enable-module-ecdh`.
+
Test coverage
-----------
diff --git a/src/secp256k1/build-aux/m4/bitcoin_secp.m4 b/src/secp256k1/build-aux/m4/bitcoin_secp.m4
index c14d09fa1b..dda770e001 100644
--- a/src/secp256k1/build-aux/m4/bitcoin_secp.m4
+++ b/src/secp256k1/build-aux/m4/bitcoin_secp.m4
@@ -38,3 +38,16 @@ AC_DEFUN([SECP_TRY_APPEND_CFLAGS], [
unset flag_works
AC_SUBST($2)
])
+
+dnl SECP_SET_DEFAULT(VAR, default, default-dev-mode)
+dnl Set VAR to default or default-dev-mode, depending on whether dev mode is enabled
+AC_DEFUN([SECP_SET_DEFAULT], [
+ if test "${enable_dev_mode+set}" != set; then
+ AC_MSG_ERROR([[Set enable_dev_mode before calling SECP_SET_DEFAULT]])
+ fi
+ if test x"$enable_dev_mode" = x"yes"; then
+ $1="$3"
+ else
+ $1="$2"
+ fi
+])
diff --git a/src/secp256k1/ci/cirrus.sh b/src/secp256k1/ci/cirrus.sh
index e27b34782e..b85f012d3f 100755
--- a/src/secp256k1/ci/cirrus.sh
+++ b/src/secp256k1/ci/cirrus.sh
@@ -15,9 +15,11 @@ valgrind --version || true
./configure \
--enable-experimental="$EXPERIMENTAL" \
--with-test-override-wide-multiply="$WIDEMUL" --with-asm="$ASM" \
- --enable-ecmult-static-precomputation="$STATICPRECOMPUTATION" --with-ecmult-gen-precision="$ECMULTGENPRECISION" \
+ --with-ecmult-window="$ECMULTWINDOW" \
+ --with-ecmult-gen-precision="$ECMULTGENPRECISION" \
--enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \
--enable-module-schnorrsig="$SCHNORRSIG" \
+ --enable-examples="$EXAMPLES" \
--with-valgrind="$WITH_VALGRIND" \
--host="$HOST" $EXTRAFLAGS
diff --git a/src/secp256k1/ci/linux-debian.Dockerfile b/src/secp256k1/ci/linux-debian.Dockerfile
index fdba12aa00..5cccbb5565 100644
--- a/src/secp256k1/ci/linux-debian.Dockerfile
+++ b/src/secp256k1/ci/linux-debian.Dockerfile
@@ -19,7 +19,8 @@ RUN apt-get install --no-install-recommends --no-upgrade -y \
gcc-arm-linux-gnueabihf libc6-dev-armhf-cross libc6-dbg:armhf \
gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dbg:arm64 \
gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross libc6-dbg:ppc64el \
- wine gcc-mingw-w64-x86-64
+ wine gcc-mingw-w64-x86-64 \
+ sagemath
# Run a dummy command in wine to make it set up configuration
RUN wine64-stable xcopy || true
diff --git a/src/secp256k1/configure.ac b/src/secp256k1/configure.ac
index 94feea7bb7..2db59a8ff3 100644
--- a/src/secp256k1/configure.ac
+++ b/src/secp256k1/configure.ac
@@ -1,30 +1,47 @@
AC_PREREQ([2.60])
-AC_INIT([libsecp256k1],[0.1])
+
+# The package (a.k.a. release) version is based on semantic versioning 2.0.0 of
+# the API. All changes in experimental modules are treated as
+# backwards-compatible and therefore at most increase the minor version.
+define(_PKG_VERSION_MAJOR, 0)
+define(_PKG_VERSION_MINOR, 1)
+define(_PKG_VERSION_BUILD, 0)
+define(_PKG_VERSION_IS_RELEASE, false)
+
+# The library version is based on libtool versioning of the ABI. The set of
+# rules for updating the version can be found here:
+# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+# All changes in experimental modules are treated as if they don't affect the
+# interface and therefore only increase the revision.
+define(_LIB_VERSION_CURRENT, 0)
+define(_LIB_VERSION_REVISION, 0)
+define(_LIB_VERSION_AGE, 0)
+
+AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_BUILD)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-pre]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1])
+
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([build-aux/m4])
AC_CANONICAL_HOST
AH_TOP([#ifndef LIBSECP256K1_CONFIG_H])
AH_TOP([#define LIBSECP256K1_CONFIG_H])
AH_BOTTOM([#endif /*LIBSECP256K1_CONFIG_H*/])
-AM_INIT_AUTOMAKE([foreign subdir-objects])
-LT_INIT([win32-dll])
+# Require Automake 1.11.2 for AM_PROG_AR
+AM_INIT_AUTOMAKE([1.11.2 foreign subdir-objects])
# Make the compilation flags quiet unless V=1 is used.
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
-PKG_PROG_PKG_CONFIG
-
-AC_PATH_TOOL(AR, ar)
-AC_PATH_TOOL(RANLIB, ranlib)
-AC_PATH_TOOL(STRIP, strip)
-
-AM_PROG_CC_C_O
-AC_PROG_CC_C89
+AC_PROG_CC
if test x"$ac_cv_prog_cc_c89" = x"no"; then
AC_MSG_ERROR([c89 compiler support required])
fi
AM_PROG_AS
+AM_PROG_AR
+
+LT_INIT([win32-dll])
+
+build_windows=no
case $host_os in
*darwin*)
@@ -49,6 +66,9 @@ case $host_os in
fi
fi
;;
+ cygwin*|mingw*)
+ build_windows=yes
+ ;;
esac
# Try if some desirable compiler flags are supported and append them to SECP_CFLAGS.
@@ -91,55 +111,54 @@ SECP_TRY_APPEND_DEFAULT_CFLAGS(SECP_CFLAGS)
### Define config arguments
###
+# In dev mode, we enable all binaries and modules by default but individual options can still be overridden explicitly.
+# Check for dev mode first because SECP_SET_DEFAULT needs enable_dev_mode set.
+AC_ARG_ENABLE(dev_mode, [], [],
+ [enable_dev_mode=no])
+
AC_ARG_ENABLE(benchmark,
- AS_HELP_STRING([--enable-benchmark],[compile benchmark [default=yes]]),
- [use_benchmark=$enableval],
- [use_benchmark=yes])
+ AS_HELP_STRING([--enable-benchmark],[compile benchmark [default=yes]]), [],
+ [SECP_SET_DEFAULT([enable_benchmark], [yes], [yes])])
AC_ARG_ENABLE(coverage,
- AS_HELP_STRING([--enable-coverage],[enable compiler flags to support kcov coverage analysis [default=no]]),
- [enable_coverage=$enableval],
- [enable_coverage=no])
+ AS_HELP_STRING([--enable-coverage],[enable compiler flags to support kcov coverage analysis [default=no]]), [],
+ [SECP_SET_DEFAULT([enable_coverage], [no], [no])])
AC_ARG_ENABLE(tests,
- AS_HELP_STRING([--enable-tests],[compile tests [default=yes]]),
- [use_tests=$enableval],
- [use_tests=yes])
+ AS_HELP_STRING([--enable-tests],[compile tests [default=yes]]), [],
+ [SECP_SET_DEFAULT([enable_tests], [yes], [yes])])
AC_ARG_ENABLE(experimental,
- AS_HELP_STRING([--enable-experimental],[allow experimental configure options [default=no]]),
- [use_experimental=$enableval],
- [use_experimental=no])
+ AS_HELP_STRING([--enable-experimental],[allow experimental configure options [default=no]]), [],
+ [SECP_SET_DEFAULT([enable_experimental], [no], [yes])])
AC_ARG_ENABLE(exhaustive_tests,
- AS_HELP_STRING([--enable-exhaustive-tests],[compile exhaustive tests [default=yes]]),
- [use_exhaustive_tests=$enableval],
- [use_exhaustive_tests=yes])
+ AS_HELP_STRING([--enable-exhaustive-tests],[compile exhaustive tests [default=yes]]), [],
+ [SECP_SET_DEFAULT([enable_exhaustive_tests], [yes], [yes])])
+
+AC_ARG_ENABLE(examples,
+ AS_HELP_STRING([--enable-examples],[compile the examples [default=no]]), [],
+ [SECP_SET_DEFAULT([enable_examples], [no], [yes])])
AC_ARG_ENABLE(module_ecdh,
- AS_HELP_STRING([--enable-module-ecdh],[enable ECDH shared secret computation]),
- [enable_module_ecdh=$enableval],
- [enable_module_ecdh=no])
+ AS_HELP_STRING([--enable-module-ecdh],[enable ECDH module [default=no]]), [],
+ [SECP_SET_DEFAULT([enable_module_ecdh], [no], [yes])])
AC_ARG_ENABLE(module_recovery,
- AS_HELP_STRING([--enable-module-recovery],[enable ECDSA pubkey recovery module [default=no]]),
- [enable_module_recovery=$enableval],
- [enable_module_recovery=no])
+ AS_HELP_STRING([--enable-module-recovery],[enable ECDSA pubkey recovery module [default=no]]), [],
+ [SECP_SET_DEFAULT([enable_module_recovery], [no], [yes])])
AC_ARG_ENABLE(module_extrakeys,
- AS_HELP_STRING([--enable-module-extrakeys],[enable extrakeys module (experimental)]),
- [enable_module_extrakeys=$enableval],
- [enable_module_extrakeys=no])
+ AS_HELP_STRING([--enable-module-extrakeys],[enable extrakeys module [default=no]]), [],
+ [SECP_SET_DEFAULT([enable_module_extrakeys], [no], [yes])])
AC_ARG_ENABLE(module_schnorrsig,
- AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module (experimental)]),
- [enable_module_schnorrsig=$enableval],
- [enable_module_schnorrsig=no])
+ AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module [default=no]]), [],
+ [SECP_SET_DEFAULT([enable_module_schnorrsig], [no], [yes])])
AC_ARG_ENABLE(external_default_callbacks,
- AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]),
- [use_external_default_callbacks=$enableval],
- [use_external_default_callbacks=no])
+ AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [],
+ [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])])
# Test-only override of the (autodetected by the C code) "widemul" setting.
# Legal values are int64 (for [u]int64_t), int128 (for [unsigned] __int128), and auto (the default).
@@ -152,7 +171,7 @@ AC_ARG_WITH([ecmult-window], [AS_HELP_STRING([--with-ecmult-window=SIZE|auto],
[window size for ecmult precomputation for verification, specified as integer in range [2..24].]
[Larger values result in possibly better performance at the cost of an exponentially larger precomputed table.]
[The table will store 2^(SIZE-1) * 64 bytes of data but can be larger in memory due to platform-specific padding and alignment.]
-[A window size larger than 15 will require you delete the prebuilt ecmult_static_pre_g.h file so that it can be rebuilt.]
+[A window size larger than 15 will require you delete the prebuilt precomputed_ecmult.c file so that it can be rebuilt.]
[For very large window sizes, use "make -j 1" to reduce memory use during compilation.]
["auto" is a reasonable setting for desktop machines (currently 15). [default=auto]]
)],
@@ -229,14 +248,14 @@ else
fi
# Select assembly optimization
-use_external_asm=no
+enable_external_asm=no
case $set_asm in
x86_64)
AC_DEFINE(USE_ASM_X86_64, 1, [Define this symbol to enable x86_64 assembly optimizations])
;;
arm)
- use_external_asm=yes
+ enable_external_asm=yes
;;
no)
;;
@@ -245,7 +264,7 @@ no)
;;
esac
-if test x"$use_external_asm" = x"yes"; then
+if test x"$enable_external_asm" = x"yes"; then
AC_DEFINE(USE_EXTERNAL_ASM, 1, [Define this symbol if an external (non-inline) assembly implementation is used])
fi
@@ -333,7 +352,7 @@ if test x"$enable_module_extrakeys" = x"yes"; then
AC_DEFINE(ENABLE_MODULE_EXTRAKEYS, 1, [Define this symbol to enable the extrakeys module])
fi
-if test x"$use_external_default_callbacks" = x"yes"; then
+if test x"$enable_external_default_callbacks" = x"yes"; then
AC_DEFINE(USE_EXTERNAL_DEFAULT_CALLBACKS, 1, [Define this symbol if an external implementation of the default callbacks is used])
fi
@@ -345,16 +364,8 @@ if test x"$enable_experimental" = x"yes"; then
AC_MSG_NOTICE([******])
AC_MSG_NOTICE([WARNING: experimental build])
AC_MSG_NOTICE([Experimental features do not have stable APIs or properties, and may not be safe for production use.])
- AC_MSG_NOTICE([Building extrakeys module: $enable_module_extrakeys])
- AC_MSG_NOTICE([Building schnorrsig module: $enable_module_schnorrsig])
AC_MSG_NOTICE([******])
else
- if test x"$enable_module_extrakeys" = x"yes"; then
- AC_MSG_ERROR([extrakeys module is experimental. Use --enable-experimental to allow.])
- fi
- if test x"$enable_module_schnorrsig" = x"yes"; then
- AC_MSG_ERROR([schnorrsig module is experimental. Use --enable-experimental to allow.])
- fi
if test x"$set_asm" = x"arm"; then
AC_MSG_ERROR([ARM assembly optimization is experimental. Use --enable-experimental to allow.])
fi
@@ -372,29 +383,30 @@ AC_SUBST(SECP_TEST_LIBS)
AC_SUBST(SECP_TEST_INCLUDES)
AC_SUBST(SECP_CFLAGS)
AM_CONDITIONAL([ENABLE_COVERAGE], [test x"$enable_coverage" = x"yes"])
-AM_CONDITIONAL([USE_TESTS], [test x"$use_tests" != x"no"])
-AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$use_exhaustive_tests" != x"no"])
-AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"])
+AM_CONDITIONAL([USE_TESTS], [test x"$enable_tests" != x"no"])
+AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$enable_exhaustive_tests" != x"no"])
+AM_CONDITIONAL([USE_EXAMPLES], [test x"$enable_examples" != x"no"])
+AM_CONDITIONAL([USE_BENCHMARK], [test x"$enable_benchmark" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"])
-AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$use_external_asm" = x"yes"])
+AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm"])
-
-# Make sure nothing new is exported so that we don't break the cache.
-PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH"
-unset PKG_CONFIG_PATH
-PKG_CONFIG_PATH="$PKGCONFIG_PATH_TEMP"
+AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"])
+AC_SUBST(LIB_VERSION_CURRENT, _LIB_VERSION_CURRENT)
+AC_SUBST(LIB_VERSION_REVISION, _LIB_VERSION_REVISION)
+AC_SUBST(LIB_VERSION_AGE, _LIB_VERSION_AGE)
AC_OUTPUT
echo
echo "Build Options:"
-echo " with external callbacks = $use_external_default_callbacks"
-echo " with benchmarks = $use_benchmark"
-echo " with tests = $use_tests"
+echo " with external callbacks = $enable_external_default_callbacks"
+echo " with benchmarks = $enable_benchmark"
+echo " with tests = $enable_tests"
echo " with coverage = $enable_coverage"
+echo " with examples = $enable_examples"
echo " module ecdh = $enable_module_ecdh"
echo " module recovery = $enable_module_recovery"
echo " module extrakeys = $enable_module_extrakeys"
diff --git a/src/secp256k1/doc/CHANGELOG.md b/src/secp256k1/doc/CHANGELOG.md
new file mode 100644
index 0000000000..3c4c2e4583
--- /dev/null
+++ b/src/secp256k1/doc/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Changelog
+
+This file is currently only a template for future use.
+
+Each change falls into one of the following categories: Added, Changed, Deprecated, Removed, Fixed or Security.
+
+## [Unreleased]
+
+## [MAJOR.MINOR.PATCH] - YYYY-MM-DD
+
+### Added/Changed/Deprecated/Removed/Fixed/Security
+- [Title with link to Pull Request](https://link-to-pr)
diff --git a/src/secp256k1/doc/release-process.md b/src/secp256k1/doc/release-process.md
new file mode 100644
index 0000000000..a35b8a9db3
--- /dev/null
+++ b/src/secp256k1/doc/release-process.md
@@ -0,0 +1,14 @@
+# Release Process
+
+1. Open PR to master that
+ 1. adds release notes to `doc/CHANGELOG.md` and
+ 2. if this is **not** a patch release, updates `_PKG_VERSION_{MAJOR,MINOR}` and `_LIB_VERSIONS_*` in `configure.ac`
+2. After the PR is merged,
+ * if this is **not** a patch release, create a release branch with name `MAJOR.MINOR`.
+ Make sure that the branch contains the right commits.
+ Create commit on the release branch that sets `_PKG_VERSION_IS_RELEASE` in `configure.ac` to `true`.
+ * if this **is** a patch release, open a pull request with the bugfixes to the `MAJOR.MINOR` branch.
+ Also include the release note commit bump `_PKG_VERSION_BUILD` and `_LIB_VERSIONS_*` in `configure.ac`.
+4. Tag the commit with `git tag -s vMAJOR.MINOR.PATCH`.
+5. Push branch and tag with `git push origin --tags`.
+6. Create a new GitHub release with a link to the corresponding entry in `doc/CHANGELOG.md`.
diff --git a/src/secp256k1/examples/EXAMPLES_COPYING b/src/secp256k1/examples/EXAMPLES_COPYING
new file mode 100644
index 0000000000..0e259d42c9
--- /dev/null
+++ b/src/secp256k1/examples/EXAMPLES_COPYING
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/src/secp256k1/examples/ecdh.c b/src/secp256k1/examples/ecdh.c
new file mode 100644
index 0000000000..d7e8add361
--- /dev/null
+++ b/src/secp256k1/examples/ecdh.c
@@ -0,0 +1,127 @@
+/*************************************************************************
+ * Written in 2020-2022 by Elichai Turkel *
+ * To the extent possible under law, the author(s) have dedicated all *
+ * copyright and related and neighboring rights to the software in this *
+ * file to the public domain worldwide. This software is distributed *
+ * without any warranty. For the CC0 Public Domain Dedication, see *
+ * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
+ *************************************************************************/
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include <secp256k1.h>
+#include <secp256k1_ecdh.h>
+
+#include "random.h"
+
+
+int main(void) {
+ unsigned char seckey1[32];
+ unsigned char seckey2[32];
+ unsigned char compressed_pubkey1[33];
+ unsigned char compressed_pubkey2[33];
+ unsigned char shared_secret1[32];
+ unsigned char shared_secret2[32];
+ unsigned char randomize[32];
+ int return_val;
+ size_t len;
+ secp256k1_pubkey pubkey1;
+ secp256k1_pubkey pubkey2;
+
+ /* The specification in secp256k1.h states that `secp256k1_ec_pubkey_create`
+ * needs a context object initialized for signing, which is why we create
+ * a context with the SECP256K1_CONTEXT_SIGN flag.
+ * (The docs for `secp256k1_ecdh` don't require any special context, just
+ * some initialized context) */
+ secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
+ if (!fill_random(randomize, sizeof(randomize))) {
+ printf("Failed to generate randomness\n");
+ return 1;
+ }
+ /* Randomizing the context is recommended to protect against side-channel
+ * leakage See `secp256k1_context_randomize` in secp256k1.h for more
+ * information about it. This should never fail. */
+ return_val = secp256k1_context_randomize(ctx, randomize);
+ assert(return_val);
+
+ /*** Key Generation ***/
+
+ /* If the secret key is zero or out of range (bigger than secp256k1's
+ * order), we try to sample a new key. Note that the probability of this
+ * happening is negligible. */
+ while (1) {
+ if (!fill_random(seckey1, sizeof(seckey1)) || !fill_random(seckey2, sizeof(seckey2))) {
+ printf("Failed to generate randomness\n");
+ return 1;
+ }
+ if (secp256k1_ec_seckey_verify(ctx, seckey1) && secp256k1_ec_seckey_verify(ctx, seckey2)) {
+ break;
+ }
+ }
+
+ /* Public key creation using a valid context with a verified secret key should never fail */
+ return_val = secp256k1_ec_pubkey_create(ctx, &pubkey1, seckey1);
+ assert(return_val);
+ return_val = secp256k1_ec_pubkey_create(ctx, &pubkey2, seckey2);
+ assert(return_val);
+
+ /* Serialize pubkey1 in a compressed form (33 bytes), should always return 1 */
+ len = sizeof(compressed_pubkey1);
+ return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey1, &len, &pubkey1, SECP256K1_EC_COMPRESSED);
+ assert(return_val);
+ /* Should be the same size as the size of the output, because we passed a 33 byte array. */
+ assert(len == sizeof(compressed_pubkey1));
+
+ /* Serialize pubkey2 in a compressed form (33 bytes) */
+ len = sizeof(compressed_pubkey2);
+ return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey2, &len, &pubkey2, SECP256K1_EC_COMPRESSED);
+ assert(return_val);
+ /* Should be the same size as the size of the output, because we passed a 33 byte array. */
+ assert(len == sizeof(compressed_pubkey2));
+
+ /*** Creating the shared secret ***/
+
+ /* Perform ECDH with seckey1 and pubkey2. Should never fail with a verified
+ * seckey and valid pubkey */
+ return_val = secp256k1_ecdh(ctx, shared_secret1, &pubkey2, seckey1, NULL, NULL);
+ assert(return_val);
+
+ /* Perform ECDH with seckey2 and pubkey1. Should never fail with a verified
+ * seckey and valid pubkey */
+ return_val = secp256k1_ecdh(ctx, shared_secret2, &pubkey1, seckey2, NULL, NULL);
+ assert(return_val);
+
+ /* Both parties should end up with the same shared secret */
+ return_val = memcmp(shared_secret1, shared_secret2, sizeof(shared_secret1));
+ assert(return_val == 0);
+
+ printf("Secret Key1: ");
+ print_hex(seckey1, sizeof(seckey1));
+ printf("Compressed Pubkey1: ");
+ print_hex(compressed_pubkey1, sizeof(compressed_pubkey1));
+ printf("\nSecret Key2: ");
+ print_hex(seckey2, sizeof(seckey2));
+ printf("Compressed Pubkey2: ");
+ print_hex(compressed_pubkey2, sizeof(compressed_pubkey2));
+ printf("\nShared Secret: ");
+ print_hex(shared_secret1, sizeof(shared_secret1));
+
+ /* This will clear everything from the context and free the memory */
+ secp256k1_context_destroy(ctx);
+
+ /* It's best practice to try to clear secrets from memory after using them.
+ * This is done because some bugs can allow an attacker to leak memory, for
+ * example through "out of bounds" array access (see Heartbleed), Or the OS
+ * swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
+ *
+ * TODO: Prevent these writes from being optimized out, as any good compiler
+ * will remove any writes that aren't used. */
+ memset(seckey1, 0, sizeof(seckey1));
+ memset(seckey2, 0, sizeof(seckey2));
+ memset(shared_secret1, 0, sizeof(shared_secret1));
+ memset(shared_secret2, 0, sizeof(shared_secret2));
+
+ return 0;
+}
diff --git a/src/secp256k1/examples/ecdsa.c b/src/secp256k1/examples/ecdsa.c
new file mode 100644
index 0000000000..434c856ba0
--- /dev/null
+++ b/src/secp256k1/examples/ecdsa.c
@@ -0,0 +1,137 @@
+/*************************************************************************
+ * Written in 2020-2022 by Elichai Turkel *
+ * To the extent possible under law, the author(s) have dedicated all *
+ * copyright and related and neighboring rights to the software in this *
+ * file to the public domain worldwide. This software is distributed *
+ * without any warranty. For the CC0 Public Domain Dedication, see *
+ * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
+ *************************************************************************/
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include <secp256k1.h>
+
+#include "random.h"
+
+
+
+int main(void) {
+ /* Instead of signing the message directly, we must sign a 32-byte hash.
+ * Here the message is "Hello, world!" and the hash function was SHA-256.
+ * An actual implementation should just call SHA-256, but this example
+ * hardcodes the output to avoid depending on an additional library.
+ * See https://bitcoin.stackexchange.com/questions/81115/if-someone-wanted-to-pretend-to-be-satoshi-by-posting-a-fake-signature-to-defrau/81116#81116 */
+ unsigned char msg_hash[32] = {
+ 0x31, 0x5F, 0x5B, 0xDB, 0x76, 0xD0, 0x78, 0xC4,
+ 0x3B, 0x8A, 0xC0, 0x06, 0x4E, 0x4A, 0x01, 0x64,
+ 0x61, 0x2B, 0x1F, 0xCE, 0x77, 0xC8, 0x69, 0x34,
+ 0x5B, 0xFC, 0x94, 0xC7, 0x58, 0x94, 0xED, 0xD3,
+ };
+ unsigned char seckey[32];
+ unsigned char randomize[32];
+ unsigned char compressed_pubkey[33];
+ unsigned char serialized_signature[64];
+ size_t len;
+ int is_signature_valid;
+ int return_val;
+ secp256k1_pubkey pubkey;
+ secp256k1_ecdsa_signature sig;
+ /* The specification in secp256k1.h states that `secp256k1_ec_pubkey_create` needs
+ * a context object initialized for signing and `secp256k1_ecdsa_verify` needs
+ * a context initialized for verification, which is why we create a context
+ * for both signing and verification with the SECP256K1_CONTEXT_SIGN and
+ * SECP256K1_CONTEXT_VERIFY flags. */
+ secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
+ if (!fill_random(randomize, sizeof(randomize))) {
+ printf("Failed to generate randomness\n");
+ return 1;
+ }
+ /* Randomizing the context is recommended to protect against side-channel
+ * leakage See `secp256k1_context_randomize` in secp256k1.h for more
+ * information about it. This should never fail. */
+ return_val = secp256k1_context_randomize(ctx, randomize);
+ assert(return_val);
+
+ /*** Key Generation ***/
+
+ /* If the secret key is zero or out of range (bigger than secp256k1's
+ * order), we try to sample a new key. Note that the probability of this
+ * happening is negligible. */
+ while (1) {
+ if (!fill_random(seckey, sizeof(seckey))) {
+ printf("Failed to generate randomness\n");
+ return 1;
+ }
+ if (secp256k1_ec_seckey_verify(ctx, seckey)) {
+ break;
+ }
+ }
+
+ /* Public key creation using a valid context with a verified secret key should never fail */
+ return_val = secp256k1_ec_pubkey_create(ctx, &pubkey, seckey);
+ assert(return_val);
+
+ /* Serialize the pubkey in a compressed form(33 bytes). Should always return 1. */
+ len = sizeof(compressed_pubkey);
+ return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey, &len, &pubkey, SECP256K1_EC_COMPRESSED);
+ assert(return_val);
+ /* Should be the same size as the size of the output, because we passed a 33 byte array. */
+ assert(len == sizeof(compressed_pubkey));
+
+ /*** Signing ***/
+
+ /* Generate an ECDSA signature `noncefp` and `ndata` allows you to pass a
+ * custom nonce function, passing `NULL` will use the RFC-6979 safe default.
+ * Signing with a valid context, verified secret key
+ * and the default nonce function should never fail. */
+ return_val = secp256k1_ecdsa_sign(ctx, &sig, msg_hash, seckey, NULL, NULL);
+ assert(return_val);
+
+ /* Serialize the signature in a compact form. Should always return 1
+ * according to the documentation in secp256k1.h. */
+ return_val = secp256k1_ecdsa_signature_serialize_compact(ctx, serialized_signature, &sig);
+ assert(return_val);
+
+
+ /*** Verification ***/
+
+ /* Deserialize the signature. This will return 0 if the signature can't be parsed correctly. */
+ if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, serialized_signature)) {
+ printf("Failed parsing the signature\n");
+ return 1;
+ }
+
+ /* Deserialize the public key. This will return 0 if the public key can't be parsed correctly. */
+ if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey, sizeof(compressed_pubkey))) {
+ printf("Failed parsing the public key\n");
+ return 1;
+ }
+
+ /* Verify a signature. This will return 1 if it's valid and 0 if it's not. */
+ is_signature_valid = secp256k1_ecdsa_verify(ctx, &sig, msg_hash, &pubkey);
+
+ printf("Is the signature valid? %s\n", is_signature_valid ? "true" : "false");
+ printf("Secret Key: ");
+ print_hex(seckey, sizeof(seckey));
+ printf("Public Key: ");
+ print_hex(compressed_pubkey, sizeof(compressed_pubkey));
+ printf("Signature: ");
+ print_hex(serialized_signature, sizeof(serialized_signature));
+
+
+ /* This will clear everything from the context and free the memory */
+ secp256k1_context_destroy(ctx);
+
+ /* It's best practice to try to clear secrets from memory after using them.
+ * This is done because some bugs can allow an attacker to leak memory, for
+ * example through "out of bounds" array access (see Heartbleed), Or the OS
+ * swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
+ *
+ * TODO: Prevent these writes from being optimized out, as any good compiler
+ * will remove any writes that aren't used. */
+ memset(seckey, 0, sizeof(seckey));
+
+ return 0;
+}
diff --git a/src/secp256k1/examples/random.h b/src/secp256k1/examples/random.h
new file mode 100644
index 0000000000..439226f09f
--- /dev/null
+++ b/src/secp256k1/examples/random.h
@@ -0,0 +1,73 @@
+/*************************************************************************
+ * Copyright (c) 2020-2021 Elichai Turkel *
+ * Distributed under the CC0 software license, see the accompanying file *
+ * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
+ *************************************************************************/
+
+/*
+ * This file is an attempt at collecting best practice methods for obtaining randomness with different operating systems.
+ * It may be out-of-date. Consult the documentation of the operating system before considering to use the methods below.
+ *
+ * Platform randomness sources:
+ * Linux -> `getrandom(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. http://man7.org/linux/man-pages/man2/getrandom.2.html, https://linux.die.net/man/4/urandom
+ * macOS -> `getentropy(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. https://www.unix.com/man-page/mojave/2/getentropy, https://opensource.apple.com/source/xnu/xnu-517.12.7/bsd/man/man4/random.4.auto.html
+ * FreeBSD -> `getrandom(2)`(`sys/random.h`), if not available `kern.arandom` should be used. https://www.freebsd.org/cgi/man.cgi?query=getrandom, https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4
+ * OpenBSD -> `getentropy(2)`(`unistd.h`), if not available `/dev/urandom` should be used. https://man.openbsd.org/getentropy, https://man.openbsd.org/urandom
+ * Windows -> `BCryptGenRandom`(`bcrypt.h`). https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
+ */
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <ntstatus.h>
+#include <bcrypt.h>
+#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
+#include <sys/random.h>
+#elif defined(__OpenBSD__)
+#include <unistd.h>
+#else
+#error "Couldn't identify the OS"
+#endif
+
+#include <stddef.h>
+#include <limits.h>
+#include <stdio.h>
+
+
+/* Returns 1 on success, and 0 on failure. */
+static int fill_random(unsigned char* data, size_t size) {
+#if defined(_WIN32)
+ NTSTATUS res = BCryptGenRandom(NULL, data, size, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ if (res != STATUS_SUCCESS || size > ULONG_MAX) {
+ return 0;
+ } else {
+ return 1;
+ }
+#elif defined(__linux__) || defined(__FreeBSD__)
+ /* If `getrandom(2)` is not available you should fallback to /dev/urandom */
+ ssize_t res = getrandom(data, size, 0);
+ if (res < 0 || (size_t)res != size ) {
+ return 0;
+ } else {
+ return 1;
+ }
+#elif defined(__APPLE__) || defined(__OpenBSD__)
+ /* If `getentropy(2)` is not available you should fallback to either
+ * `SecRandomCopyBytes` or /dev/urandom */
+ int res = getentropy(data, size);
+ if (res == 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+#endif
+ return 0;
+}
+
+static void print_hex(unsigned char* data, size_t size) {
+ size_t i;
+ printf("0x");
+ for (i = 0; i < size; i++) {
+ printf("%02x", data[i]);
+ }
+ printf("\n");
+}
diff --git a/src/secp256k1/examples/schnorr.c b/src/secp256k1/examples/schnorr.c
new file mode 100644
index 0000000000..82eb07d5d7
--- /dev/null
+++ b/src/secp256k1/examples/schnorr.c
@@ -0,0 +1,152 @@
+/*************************************************************************
+ * Written in 2020-2022 by Elichai Turkel *
+ * To the extent possible under law, the author(s) have dedicated all *
+ * copyright and related and neighboring rights to the software in this *
+ * file to the public domain worldwide. This software is distributed *
+ * without any warranty. For the CC0 Public Domain Dedication, see *
+ * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
+ *************************************************************************/
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include <secp256k1.h>
+#include <secp256k1_extrakeys.h>
+#include <secp256k1_schnorrsig.h>
+
+#include "random.h"
+
+int main(void) {
+ unsigned char msg[12] = "Hello World!";
+ unsigned char msg_hash[32];
+ unsigned char tag[17] = "my_fancy_protocol";
+ unsigned char seckey[32];
+ unsigned char randomize[32];
+ unsigned char auxiliary_rand[32];
+ unsigned char serialized_pubkey[32];
+ unsigned char signature[64];
+ int is_signature_valid;
+ int return_val;
+ secp256k1_xonly_pubkey pubkey;
+ secp256k1_keypair keypair;
+ /* The specification in secp256k1_extrakeys.h states that `secp256k1_keypair_create`
+ * needs a context object initialized for signing. And in secp256k1_schnorrsig.h
+ * they state that `secp256k1_schnorrsig_verify` needs a context initialized for
+ * verification, which is why we create a context for both signing and verification
+ * with the SECP256K1_CONTEXT_SIGN and SECP256K1_CONTEXT_VERIFY flags. */
+ secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
+ if (!fill_random(randomize, sizeof(randomize))) {
+ printf("Failed to generate randomness\n");
+ return 1;
+ }
+ /* Randomizing the context is recommended to protect against side-channel
+ * leakage See `secp256k1_context_randomize` in secp256k1.h for more
+ * information about it. This should never fail. */
+ return_val = secp256k1_context_randomize(ctx, randomize);
+ assert(return_val);
+
+ /*** Key Generation ***/
+
+ /* If the secret key is zero or out of range (bigger than secp256k1's
+ * order), we try to sample a new key. Note that the probability of this
+ * happening is negligible. */
+ while (1) {
+ if (!fill_random(seckey, sizeof(seckey))) {
+ printf("Failed to generate randomness\n");
+ return 1;
+ }
+ /* Try to create a keypair with a valid context, it should only fail if
+ * the secret key is zero or out of range. */
+ if (secp256k1_keypair_create(ctx, &keypair, seckey)) {
+ break;
+ }
+ }
+
+ /* Extract the X-only public key from the keypair. We pass NULL for
+ * `pk_parity` as the parity isn't needed for signing or verification.
+ * `secp256k1_keypair_xonly_pub` supports returning the parity for
+ * other use cases such as tests or verifying Taproot tweaks.
+ * This should never fail with a valid context and public key. */
+ return_val = secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, &keypair);
+ assert(return_val);
+
+ /* Serialize the public key. Should always return 1 for a valid public key. */
+ return_val = secp256k1_xonly_pubkey_serialize(ctx, serialized_pubkey, &pubkey);
+ assert(return_val);
+
+ /*** Signing ***/
+
+ /* Instead of signing (possibly very long) messages directly, we sign a
+ * 32-byte hash of the message in this example.
+ *
+ * We use secp256k1_tagged_sha256 to create this hash. This function expects
+ * a context-specific "tag", which restricts the context in which the signed
+ * messages should be considered valid. For example, if protocol A mandates
+ * to use the tag "my_fancy_protocol" and protocol B mandates to use the tag
+ * "my_boring_protocol", then signed messages from protocol A will never be
+ * valid in protocol B (and vice versa), even if keys are reused across
+ * protocols. This implements "domain separation", which is considered good
+ * practice. It avoids attacks in which users are tricked into signing a
+ * message that has intended consequences in the intended context (e.g.,
+ * protocol A) but would have unintended consequences if it were valid in
+ * some other context (e.g., protocol B). */
+ return_val = secp256k1_tagged_sha256(ctx, msg_hash, tag, sizeof(tag), msg, sizeof(msg));
+ assert(return_val);
+
+ /* Generate 32 bytes of randomness to use with BIP-340 schnorr signing. */
+ if (!fill_random(auxiliary_rand, sizeof(auxiliary_rand))) {
+ printf("Failed to generate randomness\n");
+ return 1;
+ }
+
+ /* Generate a Schnorr signature.
+ *
+ * We use the secp256k1_schnorrsig_sign32 function that provides a simple
+ * interface for signing 32-byte messages (which in our case is a hash of
+ * the actual message). BIP-340 recommends passing 32 bytes of randomness
+ * to the signing function to improve security against side-channel attacks.
+ * Signing with a valid context, a 32-byte message, a verified keypair, and
+ * any 32 bytes of auxiliary random data should never fail. */
+ return_val = secp256k1_schnorrsig_sign32(ctx, signature, msg_hash, &keypair, auxiliary_rand);
+ assert(return_val);
+
+ /*** Verification ***/
+
+ /* Deserialize the public key. This will return 0 if the public key can't
+ * be parsed correctly */
+ if (!secp256k1_xonly_pubkey_parse(ctx, &pubkey, serialized_pubkey)) {
+ printf("Failed parsing the public key\n");
+ return 1;
+ }
+
+ /* Compute the tagged hash on the received messages using the same tag as the signer. */
+ return_val = secp256k1_tagged_sha256(ctx, msg_hash, tag, sizeof(tag), msg, sizeof(msg));
+ assert(return_val);
+
+ /* Verify a signature. This will return 1 if it's valid and 0 if it's not. */
+ is_signature_valid = secp256k1_schnorrsig_verify(ctx, signature, msg_hash, 32, &pubkey);
+
+
+ printf("Is the signature valid? %s\n", is_signature_valid ? "true" : "false");
+ printf("Secret Key: ");
+ print_hex(seckey, sizeof(seckey));
+ printf("Public Key: ");
+ print_hex(serialized_pubkey, sizeof(serialized_pubkey));
+ printf("Signature: ");
+ print_hex(signature, sizeof(signature));
+
+ /* This will clear everything from the context and free the memory */
+ secp256k1_context_destroy(ctx);
+
+ /* It's best practice to try to clear secrets from memory after using them.
+ * This is done because some bugs can allow an attacker to leak memory, for
+ * example through "out of bounds" array access (see Heartbleed), Or the OS
+ * swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
+ *
+ * TODO: Prevent these writes from being optimized out, as any good compiler
+ * will remove any writes that aren't used. */
+ memset(seckey, 0, sizeof(seckey));
+
+ return 0;
+}
diff --git a/src/secp256k1/include/secp256k1.h b/src/secp256k1/include/secp256k1.h
index 57114b8f26..86ab7e556d 100644
--- a/src/secp256k1/include/secp256k1.h
+++ b/src/secp256k1/include/secp256k1.h
@@ -169,6 +169,17 @@ typedef int (*secp256k1_nonce_function)(
# define SECP256K1_ARG_NONNULL(_x)
# endif
+/** Attribute for marking functions, types, and variables as deprecated */
+#if !defined(SECP256K1_BUILD) && defined(__has_attribute)
+# if __has_attribute(__deprecated__)
+# define SECP256K1_DEPRECATED(_msg) __attribute__ ((__deprecated__(_msg)))
+# else
+# define SECP256K1_DEPRECATED(_msg)
+# endif
+#else
+# define SECP256K1_DEPRECATED(_msg)
+#endif
+
/** All flags' lower 8 bits indicate what they're for. Do not use directly. */
#define SECP256K1_FLAGS_TYPE_MASK ((1 << 8) - 1)
#define SECP256K1_FLAGS_TYPE_CONTEXT (1 << 0)
@@ -641,7 +652,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_negate(
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_negate(
const secp256k1_context* ctx,
unsigned char *seckey
-) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2)
+ SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_negate instead");
/** Negates a public key in place.
*
@@ -681,7 +693,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_add(
const secp256k1_context* ctx,
unsigned char *seckey,
const unsigned char *tweak32
-) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
+ SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_tweak_add instead");
/** Tweak a public key by adding tweak times the generator to it.
*
@@ -727,7 +740,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_mul(
const secp256k1_context* ctx,
unsigned char *seckey,
const unsigned char *tweak32
-) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
+ SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_tweak_mul instead");
/** Tweak a public key by multiplying it by a tweak value.
*
@@ -800,7 +814,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine(
* implementations optimized for a specific tag can precompute the SHA256 state
* after hashing the tag hashes.
*
- * Returns 0 if the arguments are invalid and 1 otherwise.
+ * Returns: 1 always.
* Args: ctx: pointer to a context object
* Out: hash32: pointer to a 32-byte array to store the resulting hash
* In: tag: pointer to an array containing the tag
diff --git a/src/secp256k1/include/secp256k1_extrakeys.h b/src/secp256k1/include/secp256k1_extrakeys.h
index a64d561b60..09cbeaaa80 100644
--- a/src/secp256k1/include/secp256k1_extrakeys.h
+++ b/src/secp256k1/include/secp256k1_extrakeys.h
@@ -81,8 +81,7 @@ SECP256K1_API int secp256k1_xonly_pubkey_cmp(
/** Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey.
*
- * Returns: 1 if the public key was successfully converted
- * 0 otherwise
+ * Returns: 1 always.
*
* Args: ctx: pointer to a context object.
* Out: xonly_pubkey: pointer to an x-only public key object for placing the converted public key.
@@ -172,7 +171,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_create(
/** Get the secret key from a keypair.
*
- * Returns: 0 if the arguments are invalid. 1 otherwise.
+ * Returns: 1 always.
* Args: ctx: pointer to a context object.
* Out: seckey: pointer to a 32-byte buffer for the secret key.
* In: keypair: pointer to a keypair.
@@ -185,7 +184,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_sec(
/** Get the public key from a keypair.
*
- * Returns: 0 if the arguments are invalid. 1 otherwise.
+ * Returns: 1 always.
* Args: ctx: pointer to a context object.
* Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to
* the keypair public key. If not, it's set to an invalid value.
@@ -202,7 +201,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_pub(
* This is the same as calling secp256k1_keypair_pub and then
* secp256k1_xonly_pubkey_from_pubkey.
*
- * Returns: 0 if the arguments are invalid. 1 otherwise.
+ * Returns: 1 always.
* Args: ctx: pointer to a context object.
* Out: pubkey: pointer to an xonly_pubkey object. If 1 is returned, it is set
* to the keypair public key after converting it to an
diff --git a/src/secp256k1/include/secp256k1_schnorrsig.h b/src/secp256k1/include/secp256k1_schnorrsig.h
index e971ddc2aa..5fedcb07b0 100644
--- a/src/secp256k1/include/secp256k1_schnorrsig.h
+++ b/src/secp256k1/include/secp256k1_schnorrsig.h
@@ -116,7 +116,7 @@ typedef struct {
* BIP-340 "Default Signing" for a full explanation of this
* argument and for guidance if randomness is expensive.
*/
-SECP256K1_API int secp256k1_schnorrsig_sign(
+SECP256K1_API int secp256k1_schnorrsig_sign32(
const secp256k1_context* ctx,
unsigned char *sig64,
const unsigned char *msg32,
@@ -124,6 +124,17 @@ SECP256K1_API int secp256k1_schnorrsig_sign(
const unsigned char *aux_rand32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
+/** Same as secp256k1_schnorrsig_sign32, but DEPRECATED. Will be removed in
+ * future versions. */
+SECP256K1_API int secp256k1_schnorrsig_sign(
+ const secp256k1_context* ctx,
+ unsigned char *sig64,
+ const unsigned char *msg32,
+ const secp256k1_keypair *keypair,
+ const unsigned char *aux_rand32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4)
+ SECP256K1_DEPRECATED("Use secp256k1_schnorrsig_sign32 instead");
+
/** Create a Schnorr signature with a more flexible API.
*
* Same arguments as secp256k1_schnorrsig_sign except that it allows signing
diff --git a/src/secp256k1/sage/group_prover.sage b/src/secp256k1/sage/group_prover.sage
index b200bfeae3..9305c215d5 100644
--- a/src/secp256k1/sage/group_prover.sage
+++ b/src/secp256k1/sage/group_prover.sage
@@ -164,6 +164,9 @@ class constraints:
def negate(self):
return constraints(zero=self.nonzero, nonzero=self.zero)
+ def map(self, fun):
+ return constraints(zero={fun(k): v for k, v in self.zero.items()}, nonzero={fun(k): v for k, v in self.nonzero.items()})
+
def __add__(self, other):
zero = self.zero.copy()
zero.update(other.zero)
@@ -177,6 +180,30 @@ class constraints:
def __repr__(self):
return "%s" % self
+def normalize_factor(p):
+ """Normalizes the sign of primitive polynomials (as returned by factor())
+
+ This function ensures that the polynomial has a positive leading coefficient.
+
+ This is necessary because recent sage versions (starting with v9.3 or v9.4,
+ we don't know) are inconsistent about the placement of the minus sign in
+ polynomial factorizations:
+ ```
+ sage: R.<ax,bx,ay,by,Az,Bz,Ai,Bi> = PolynomialRing(QQ,8,order='invlex')
+ sage: R((-2 * (bx - ax)) ^ 1).factor()
+ (-2) * (bx - ax)
+ sage: R((-2 * (bx - ax)) ^ 2).factor()
+ (4) * (-bx + ax)^2
+ sage: R((-2 * (bx - ax)) ^ 3).factor()
+ (8) * (-bx + ax)^3
+ ```
+ """
+ # Assert p is not 0 and that its non-zero coeffients are coprime.
+ # (We could just work with the primitive part p/p.content() but we want to be
+ # aware if factor() does not return a primitive part in future sage versions.)
+ assert p.content() == 1
+ # Ensure that the first non-zero coefficient is positive.
+ return p if p.lc() > 0 else -p
def conflicts(R, con):
"""Check whether any of the passed non-zero assumptions is implied by the zero assumptions"""
@@ -204,10 +231,10 @@ def get_nonzero_set(R, assume):
nonzero = set()
for nz in map(numerator, assume.nonzero):
for (f,n) in nz.factor():
- nonzero.add(f)
+ nonzero.add(normalize_factor(f))
rnz = zero.reduce(nz)
for (f,n) in rnz.factor():
- nonzero.add(f)
+ nonzero.add(normalize_factor(f))
return nonzero
@@ -222,27 +249,27 @@ def prove_nonzero(R, exprs, assume):
return (False, [exprs[expr]])
allexprs = reduce(lambda a,b: numerator(a)*numerator(b), exprs, 1)
for (f, n) in allexprs.factor():
- if f not in nonzero:
+ if normalize_factor(f) not in nonzero:
ok = False
if ok:
return (True, None)
ok = True
- for (f, n) in zero.reduce(numerator(allexprs)).factor():
- if f not in nonzero:
+ for (f, n) in zero.reduce(allexprs).factor():
+ if normalize_factor(f) not in nonzero:
ok = False
if ok:
return (True, None)
ok = True
for expr in exprs:
for (f,n) in numerator(expr).factor():
- if f not in nonzero:
+ if normalize_factor(f) not in nonzero:
ok = False
if ok:
return (True, None)
ok = True
for expr in exprs:
for (f,n) in zero.reduce(numerator(expr)).factor():
- if f not in nonzero:
+ if normalize_factor(f) not in nonzero:
expl.add(exprs[expr])
if expl:
return (False, list(expl))
@@ -254,7 +281,7 @@ def prove_zero(R, exprs, assume):
"""Check whether all of the passed expressions are provably zero, given assumptions"""
r, e = prove_nonzero(R, dict(map(lambda x: (fastfrac(R, x.bot, 1), exprs[x]), exprs)), assume)
if not r:
- return (False, map(lambda x: "Possibly zero denominator: %s" % x, e))
+ return (False, list(map(lambda x: "Possibly zero denominator: %s" % x, e)))
zero = R.ideal(list(map(numerator, assume.zero)))
nonzero = prod(x for x in assume.nonzero)
expl = []
@@ -279,8 +306,8 @@ def describe_extra(R, assume, assumeExtra):
if base not in zero:
add = []
for (f, n) in numerator(base).factor():
- if f not in nonzero:
- add += ["%s" % f]
+ if normalize_factor(f) not in nonzero:
+ add += ["%s" % normalize_factor(f)]
if add:
ret.add((" * ".join(add)) + " = 0 [%s]" % assumeExtra.zero[base])
# Iterate over the extra nonzero expressions
@@ -288,8 +315,8 @@ def describe_extra(R, assume, assumeExtra):
nzr = zeroextra.reduce(numerator(nz))
if nzr not in zeroextra:
for (f,n) in nzr.factor():
- if zeroextra.reduce(f) not in nonzero:
- ret.add("%s != 0" % zeroextra.reduce(f))
+ if normalize_factor(zeroextra.reduce(f)) not in nonzero:
+ ret.add("%s != 0" % normalize_factor(zeroextra.reduce(f)))
return ", ".join(x for x in ret)
@@ -299,22 +326,21 @@ def check_symbolic(R, assumeLaw, assumeAssert, assumeBranch, require):
if conflicts(R, assume):
# This formula does not apply
- return None
+ return (True, None)
describe = describe_extra(R, assumeLaw + assumeBranch, assumeAssert)
+ if describe != "":
+ describe = " (assuming " + describe + ")"
ok, msg = prove_zero(R, require.zero, assume)
if not ok:
- return "FAIL, %s fails (assuming %s)" % (str(msg), describe)
+ return (False, "FAIL, %s fails%s" % (str(msg), describe))
res, expl = prove_nonzero(R, require.nonzero, assume)
if not res:
- return "FAIL, %s fails (assuming %s)" % (str(expl), describe)
+ return (False, "FAIL, %s fails%s" % (str(expl), describe))
- if describe != "":
- return "OK (assuming %s)" % describe
- else:
- return "OK"
+ return (True, "OK%s" % describe)
def concrete_verify(c):
diff --git a/src/secp256k1/sage/prove_group_implementations.sage b/src/secp256k1/sage/prove_group_implementations.sage
index a97e732f7f..96ce33506a 100644
--- a/src/secp256k1/sage/prove_group_implementations.sage
+++ b/src/secp256k1/sage/prove_group_implementations.sage
@@ -8,25 +8,20 @@ load("weierstrass_prover.sage")
def formula_secp256k1_gej_double_var(a):
"""libsecp256k1's secp256k1_gej_double_var, used by various addition functions"""
rz = a.Z * a.Y
- rz = rz * 2
- t1 = a.X^2
- t1 = t1 * 3
- t2 = t1^2
- t3 = a.Y^2
- t3 = t3 * 2
- t4 = t3^2
- t4 = t4 * 2
- t3 = t3 * a.X
- rx = t3
- rx = rx * 4
- rx = -rx
- rx = rx + t2
- t2 = -t2
- t3 = t3 * 6
- t3 = t3 + t2
- ry = t1 * t3
- t2 = -t4
- ry = ry + t2
+ s = a.Y^2
+ l = a.X^2
+ l = l * 3
+ l = l / 2
+ t = -s
+ t = t * a.X
+ rx = l^2
+ rx = rx + t
+ rx = rx + t
+ s = s^2
+ t = t + rx
+ ry = t * l
+ ry = ry + s
+ ry = -ry
return jacobianpoint(rx, ry, rz)
def formula_secp256k1_gej_add_var(branch, a, b):
@@ -197,7 +192,8 @@ def formula_secp256k1_gej_add_ge(branch, a, b):
rr_alt = rr
m_alt = m
n = m_alt^2
- q = n * t
+ q = -t
+ q = q * n
n = n^2
if degenerate:
n = m
@@ -210,8 +206,6 @@ def formula_secp256k1_gej_add_ge(branch, a, b):
zeroes.update({rz : 'r.z=0'})
else:
nonzeroes.update({rz : 'r.z!=0'})
- rz = rz * 2
- q = -q
t = t + q
rx = t
t = t * 2
@@ -219,8 +213,7 @@ def formula_secp256k1_gej_add_ge(branch, a, b):
t = t * rr_alt
t = t + n
ry = -t
- rx = rx * 4
- ry = ry * 4
+ ry = ry / 2
if a_infinity:
rx = b.X
ry = b.Y
@@ -292,15 +285,18 @@ def formula_secp256k1_gej_add_ge_old(branch, a, b):
return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zero, nonzero=nonzero), jacobianpoint(rx, ry, rz))
if __name__ == "__main__":
- check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var)
- check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var)
- check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var)
- check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge)
- check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old)
+ success = True
+ success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var)
+ success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var)
+ success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var)
+ success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge)
+ success = success & (not check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old))
if len(sys.argv) >= 2 and sys.argv[1] == "--exhaustive":
- check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43)
- check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43)
- check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43)
- check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge, 43)
- check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43)
+ success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43)
+ success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43)
+ success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43)
+ success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge, 43)
+ success = success & (not check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43))
+
+ sys.exit(int(not success))
diff --git a/src/secp256k1/sage/weierstrass_prover.sage b/src/secp256k1/sage/weierstrass_prover.sage
index b770c6dafe..be9cfd4c76 100644
--- a/src/secp256k1/sage/weierstrass_prover.sage
+++ b/src/secp256k1/sage/weierstrass_prover.sage
@@ -184,6 +184,7 @@ def check_exhaustive_jacobian_weierstrass(name, A, B, branches, formula, p):
if r:
points.append(point)
+ ret = True
for za in range(1, p):
for zb in range(1, p):
for pa in points:
@@ -211,8 +212,11 @@ def check_exhaustive_jacobian_weierstrass(name, A, B, branches, formula, p):
match = True
r, e = concrete_verify(require)
if not r:
+ ret = False
print(" failure in branch %i for (%s,%s,%s,%s) + (%s,%s,%s,%s) = (%s,%s,%s,%s): %s" % (branch, pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity, pC.X, pC.Y, pC.Z, pC.Infinity, e))
+
print()
+ return ret
def check_symbolic_function(R, assumeAssert, assumeBranch, f, A, B, pa, pb, pA, pB, pC):
@@ -244,15 +248,21 @@ def check_symbolic_jacobian_weierstrass(name, A, B, branches, formula):
print("Formula " + name + ":")
count = 0
+ ret = True
for branch in range(branches):
assumeFormula, assumeBranch, pC = formula(branch, pA, pB)
+ assumeBranch = assumeBranch.map(lift)
+ assumeFormula = assumeFormula.map(lift)
pC.X = lift(pC.X)
pC.Y = lift(pC.Y)
pC.Z = lift(pC.Z)
pC.Infinity = lift(pC.Infinity)
for key in laws_jacobian_weierstrass:
- res[key].append((check_symbolic_function(R, assumeFormula, assumeBranch, laws_jacobian_weierstrass[key], A, B, pa, pb, pA, pB, pC), branch))
+ success, msg = check_symbolic_function(R, assumeFormula, assumeBranch, laws_jacobian_weierstrass[key], A, B, pa, pb, pA, pB, pC)
+ if not success:
+ ret = False
+ res[key].append((msg, branch))
for key in res:
print(" %s:" % key)
@@ -262,3 +272,4 @@ def check_symbolic_jacobian_weierstrass(name, A, B, branches, formula):
print(" branch %i: %s" % (x[1], x[0]))
print()
+ return ret
diff --git a/src/secp256k1/src/bench_internal.c b/src/secp256k1/src/bench_internal.c
index aed8216127..3c145f306c 100644
--- a/src/secp256k1/src/bench_internal.c
+++ b/src/secp256k1/src/bench_internal.c
@@ -140,6 +140,15 @@ void bench_scalar_inverse_var(void* arg, int iters) {
CHECK(j <= iters);
}
+void bench_field_half(void* arg, int iters) {
+ int i;
+ bench_inv *data = (bench_inv*)arg;
+
+ for (i = 0; i < iters; i++) {
+ secp256k1_fe_half(&data->fe[0]);
+ }
+}
+
void bench_field_normalize(void* arg, int iters) {
int i;
bench_inv *data = (bench_inv*)arg;
@@ -354,6 +363,7 @@ int main(int argc, char **argv) {
if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "inverse")) run_benchmark("scalar_inverse", bench_scalar_inverse, bench_setup, NULL, &data, 10, iters);
if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "inverse")) run_benchmark("scalar_inverse_var", bench_scalar_inverse_var, bench_setup, NULL, &data, 10, iters);
+ if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "half")) run_benchmark("field_half", bench_field_half, bench_setup, NULL, &data, 10, iters*100);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize", bench_field_normalize, bench_setup, NULL, &data, 10, iters*100);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize_weak", bench_field_normalize_weak, bench_setup, NULL, &data, 10, iters*100);
if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "sqr")) run_benchmark("field_sqr", bench_field_sqr, bench_setup, NULL, &data, 10, iters*10);
diff --git a/src/secp256k1/src/ecmult_compute_table.h b/src/secp256k1/src/ecmult_compute_table.h
new file mode 100644
index 0000000000..665f87ff3d
--- /dev/null
+++ b/src/secp256k1/src/ecmult_compute_table.h
@@ -0,0 +1,16 @@
+/*****************************************************************************************************
+ * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor *
+ * Distributed under the MIT software license, see the accompanying *
+ * file COPYING or https://www.opensource.org/licenses/mit-license.php. *
+ *****************************************************************************************************/
+
+#ifndef SECP256K1_ECMULT_COMPUTE_TABLE_H
+#define SECP256K1_ECMULT_COMPUTE_TABLE_H
+
+/* Construct table of all odd multiples of gen in range 1..(2**(window_g-1)-1). */
+static void secp256k1_ecmult_compute_table(secp256k1_ge_storage* table, int window_g, const secp256k1_gej* gen);
+
+/* Like secp256k1_ecmult_compute_table, but one for both gen and gen*2^128. */
+static void secp256k1_ecmult_compute_two_tables(secp256k1_ge_storage* table, secp256k1_ge_storage* table_128, int window_g, const secp256k1_ge* gen);
+
+#endif /* SECP256K1_ECMULT_COMPUTE_TABLE_H */
diff --git a/src/secp256k1/src/ecmult_compute_table_impl.h b/src/secp256k1/src/ecmult_compute_table_impl.h
new file mode 100644
index 0000000000..69d59ce595
--- /dev/null
+++ b/src/secp256k1/src/ecmult_compute_table_impl.h
@@ -0,0 +1,49 @@
+/*****************************************************************************************************
+ * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor *
+ * Distributed under the MIT software license, see the accompanying *
+ * file COPYING or https://www.opensource.org/licenses/mit-license.php. *
+ *****************************************************************************************************/
+
+#ifndef SECP256K1_ECMULT_COMPUTE_TABLE_IMPL_H
+#define SECP256K1_ECMULT_COMPUTE_TABLE_IMPL_H
+
+#include "ecmult_compute_table.h"
+#include "group_impl.h"
+#include "field_impl.h"
+#include "ecmult.h"
+#include "util.h"
+
+static void secp256k1_ecmult_compute_table(secp256k1_ge_storage* table, int window_g, const secp256k1_gej* gen) {
+ secp256k1_gej gj;
+ secp256k1_ge ge, dgen;
+ int j;
+
+ gj = *gen;
+ secp256k1_ge_set_gej_var(&ge, &gj);
+ secp256k1_ge_to_storage(&table[0], &ge);
+
+ secp256k1_gej_double_var(&gj, gen, NULL);
+ secp256k1_ge_set_gej_var(&dgen, &gj);
+
+ for (j = 1; j < ECMULT_TABLE_SIZE(window_g); ++j) {
+ secp256k1_gej_set_ge(&gj, &ge);
+ secp256k1_gej_add_ge_var(&gj, &gj, &dgen, NULL);
+ secp256k1_ge_set_gej_var(&ge, &gj);
+ secp256k1_ge_to_storage(&table[j], &ge);
+ }
+}
+
+/* Like secp256k1_ecmult_compute_table, but one for both gen and gen*2^128. */
+static void secp256k1_ecmult_compute_two_tables(secp256k1_ge_storage* table, secp256k1_ge_storage* table_128, int window_g, const secp256k1_ge* gen) {
+ secp256k1_gej gj;
+ int i;
+
+ secp256k1_gej_set_ge(&gj, gen);
+ secp256k1_ecmult_compute_table(table, window_g, &gj);
+ for (i = 0; i < 128; ++i) {
+ secp256k1_gej_double_var(&gj, &gj, NULL);
+ }
+ secp256k1_ecmult_compute_table(table_128, window_g, &gj);
+}
+
+#endif /* SECP256K1_ECMULT_COMPUTE_TABLE_IMPL_H */
diff --git a/src/secp256k1/src/ecmult_const_impl.h b/src/secp256k1/src/ecmult_const_impl.h
index 30b151ff9a..12dbcc6c5b 100644
--- a/src/secp256k1/src/ecmult_const_impl.h
+++ b/src/secp256k1/src/ecmult_const_impl.h
@@ -12,6 +12,19 @@
#include "ecmult_const.h"
#include "ecmult_impl.h"
+/** Fill a table 'pre' with precomputed odd multiples of a.
+ *
+ * The resulting point set is brought to a single constant Z denominator, stores the X and Y
+ * coordinates as ge_storage points in pre, and stores the global Z in globalz.
+ * It only operates on tables sized for WINDOW_A wnaf multiples.
+ */
+static void secp256k1_ecmult_odd_multiples_table_globalz_windowa(secp256k1_ge *pre, secp256k1_fe *globalz, const secp256k1_gej *a) {
+ secp256k1_fe zr[ECMULT_TABLE_SIZE(WINDOW_A)];
+
+ secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), pre, zr, globalz, a);
+ secp256k1_ge_table_set_globalz(ECMULT_TABLE_SIZE(WINDOW_A), pre, zr);
+}
+
/* This is like `ECMULT_TABLE_GET_GE` but is constant time */
#define ECMULT_CONST_TABLE_GET_GE(r,pre,n,w) do { \
int m = 0; \
@@ -40,7 +53,6 @@
secp256k1_fe_cmov(&(r)->y, &neg_y, (n) != abs_n); \
} while(0)
-
/** Convert a number to WNAF notation.
* The number becomes represented by sum(2^{wi} * wnaf[i], i=0..WNAF_SIZE(w)+1) - return_val.
* It has the following guarantees:
@@ -56,7 +68,7 @@
*/
static int secp256k1_wnaf_const(int *wnaf, const secp256k1_scalar *scalar, int w, int size) {
int global_sign;
- int skew = 0;
+ int skew;
int word = 0;
/* 1 2 3 */
@@ -64,9 +76,7 @@ static int secp256k1_wnaf_const(int *wnaf, const secp256k1_scalar *scalar, int w
int u;
int flip;
- int bit;
- secp256k1_scalar s;
- int not_neg_one;
+ secp256k1_scalar s = *scalar;
VERIFY_CHECK(w > 0);
VERIFY_CHECK(size > 0);
@@ -74,33 +84,19 @@ static int secp256k1_wnaf_const(int *wnaf, const secp256k1_scalar *scalar, int w
/* Note that we cannot handle even numbers by negating them to be odd, as is
* done in other implementations, since if our scalars were specified to have
* width < 256 for performance reasons, their negations would have width 256
- * and we'd lose any performance benefit. Instead, we use a technique from
- * Section 4.2 of the Okeya/Tagaki paper, which is to add either 1 (for even)
- * or 2 (for odd) to the number we are encoding, returning a skew value indicating
+ * and we'd lose any performance benefit. Instead, we use a variation of a
+ * technique from Section 4.2 of the Okeya/Tagaki paper, which is to add 1 to the
+ * number we are encoding when it is even, returning a skew value indicating
* this, and having the caller compensate after doing the multiplication.
*
* In fact, we _do_ want to negate numbers to minimize their bit-lengths (and in
* particular, to ensure that the outputs from the endomorphism-split fit into
- * 128 bits). If we negate, the parity of our number flips, inverting which of
- * {1, 2} we want to add to the scalar when ensuring that it's odd. Further
- * complicating things, -1 interacts badly with `secp256k1_scalar_cadd_bit` and
- * we need to special-case it in this logic. */
- flip = secp256k1_scalar_is_high(scalar);
- /* We add 1 to even numbers, 2 to odd ones, noting that negation flips parity */
- bit = flip ^ !secp256k1_scalar_is_even(scalar);
- /* We check for negative one, since adding 2 to it will cause an overflow */
- secp256k1_scalar_negate(&s, scalar);
- not_neg_one = !secp256k1_scalar_is_one(&s);
- s = *scalar;
- secp256k1_scalar_cadd_bit(&s, bit, not_neg_one);
- /* If we had negative one, flip == 1, s.d[0] == 0, bit == 1, so caller expects
- * that we added two to it and flipped it. In fact for -1 these operations are
- * identical. We only flipped, but since skewing is required (in the sense that
- * the skew must be 1 or 2, never zero) and flipping is not, we need to change
- * our flags to claim that we only skewed. */
+ * 128 bits). If we negate, the parity of our number flips, affecting whether
+ * we want to add to the scalar to ensure that it's odd. */
+ flip = secp256k1_scalar_is_high(&s);
+ skew = flip ^ secp256k1_scalar_is_even(&s);
+ secp256k1_scalar_cadd_bit(&s, 0, skew);
global_sign = secp256k1_scalar_cond_negate(&s, flip);
- global_sign *= not_neg_one * 2 - 1;
- skew = 1 << bit;
/* 4 */
u_last = secp256k1_scalar_shr_int(&s, w);
@@ -214,42 +210,22 @@ static void secp256k1_ecmult_const(secp256k1_gej *r, const secp256k1_ge *a, cons
}
}
- secp256k1_fe_mul(&r->z, &r->z, &Z);
-
{
/* Correct for wNAF skew */
- secp256k1_ge correction = *a;
- secp256k1_ge_storage correction_1_stor;
- secp256k1_ge_storage correction_lam_stor;
- secp256k1_ge_storage a2_stor;
secp256k1_gej tmpj;
- secp256k1_gej_set_ge(&tmpj, &correction);
- secp256k1_gej_double_var(&tmpj, &tmpj, NULL);
- secp256k1_ge_set_gej(&correction, &tmpj);
- secp256k1_ge_to_storage(&correction_1_stor, a);
- if (size > 128) {
- secp256k1_ge_to_storage(&correction_lam_stor, a);
- }
- secp256k1_ge_to_storage(&a2_stor, &correction);
- /* For odd numbers this is 2a (so replace it), for even ones a (so no-op) */
- secp256k1_ge_storage_cmov(&correction_1_stor, &a2_stor, skew_1 == 2);
- if (size > 128) {
- secp256k1_ge_storage_cmov(&correction_lam_stor, &a2_stor, skew_lam == 2);
- }
-
- /* Apply the correction */
- secp256k1_ge_from_storage(&correction, &correction_1_stor);
- secp256k1_ge_neg(&correction, &correction);
- secp256k1_gej_add_ge(r, r, &correction);
+ secp256k1_ge_neg(&tmpa, &pre_a[0]);
+ secp256k1_gej_add_ge(&tmpj, r, &tmpa);
+ secp256k1_gej_cmov(r, &tmpj, skew_1);
if (size > 128) {
- secp256k1_ge_from_storage(&correction, &correction_lam_stor);
- secp256k1_ge_neg(&correction, &correction);
- secp256k1_ge_mul_lambda(&correction, &correction);
- secp256k1_gej_add_ge(r, r, &correction);
+ secp256k1_ge_neg(&tmpa, &pre_a_lam[0]);
+ secp256k1_gej_add_ge(&tmpj, r, &tmpa);
+ secp256k1_gej_cmov(r, &tmpj, skew_lam);
}
}
+
+ secp256k1_fe_mul(&r->z, &r->z, &Z);
}
#endif /* SECP256K1_ECMULT_CONST_IMPL_H */
diff --git a/src/secp256k1/src/ecmult_gen_prec.h b/src/secp256k1/src/ecmult_gen_compute_table.h
index 0cfcde9b79..e577158d92 100644
--- a/src/secp256k1/src/ecmult_gen_prec.h
+++ b/src/secp256k1/src/ecmult_gen_compute_table.h
@@ -4,11 +4,11 @@
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
-#ifndef SECP256K1_ECMULT_GEN_PREC_H
-#define SECP256K1_ECMULT_GEN_PREC_H
+#ifndef SECP256K1_ECMULT_GEN_COMPUTE_TABLE_H
+#define SECP256K1_ECMULT_GEN_COMPUTE_TABLE_H
#include "ecmult_gen.h"
-static void secp256k1_ecmult_gen_create_prec_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int bits);
+static void secp256k1_ecmult_gen_compute_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int bits);
-#endif /* SECP256K1_ECMULT_GEN_PREC_H */
+#endif /* SECP256K1_ECMULT_GEN_COMPUTE_TABLE_H */
diff --git a/src/secp256k1/src/ecmult_gen_prec_impl.h b/src/secp256k1/src/ecmult_gen_compute_table_impl.h
index bac76c8b13..ff6a2992dc 100644
--- a/src/secp256k1/src/ecmult_gen_prec_impl.h
+++ b/src/secp256k1/src/ecmult_gen_compute_table_impl.h
@@ -4,16 +4,16 @@
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
-#ifndef SECP256K1_ECMULT_GEN_PREC_IMPL_H
-#define SECP256K1_ECMULT_GEN_PREC_IMPL_H
+#ifndef SECP256K1_ECMULT_GEN_COMPUTE_TABLE_IMPL_H
+#define SECP256K1_ECMULT_GEN_COMPUTE_TABLE_IMPL_H
-#include "ecmult_gen_prec.h"
+#include "ecmult_gen_compute_table.h"
#include "group_impl.h"
#include "field_impl.h"
#include "ecmult_gen.h"
#include "util.h"
-static void secp256k1_ecmult_gen_create_prec_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int bits) {
+static void secp256k1_ecmult_gen_compute_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int bits) {
int g = ECMULT_GEN_PREC_G(bits);
int n = ECMULT_GEN_PREC_N(bits);
@@ -78,4 +78,4 @@ static void secp256k1_ecmult_gen_create_prec_table(secp256k1_ge_storage* table,
free(prec);
}
-#endif /* SECP256K1_ECMULT_GEN_PREC_IMPL_H */
+#endif /* SECP256K1_ECMULT_GEN_COMPUTE_TABLE_IMPL_H */
diff --git a/src/secp256k1/src/ecmult_gen_impl.h b/src/secp256k1/src/ecmult_gen_impl.h
index 6a6ab9a4b5..2c8a503acc 100644
--- a/src/secp256k1/src/ecmult_gen_impl.h
+++ b/src/secp256k1/src/ecmult_gen_impl.h
@@ -12,7 +12,7 @@
#include "group.h"
#include "ecmult_gen.h"
#include "hash_impl.h"
-#include "ecmult_gen_static_prec_table.h"
+#include "precomputed_ecmult_gen.h"
static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context *ctx) {
secp256k1_ecmult_gen_blind(ctx, NULL);
diff --git a/src/secp256k1/src/ecmult_impl.h b/src/secp256k1/src/ecmult_impl.h
index 5bd4d4d23d..bbc820c77c 100644
--- a/src/secp256k1/src/ecmult_impl.h
+++ b/src/secp256k1/src/ecmult_impl.h
@@ -14,7 +14,7 @@
#include "group.h"
#include "scalar.h"
#include "ecmult.h"
-#include "ecmult_static_pre_g.h"
+#include "precomputed_ecmult.h"
#if defined(EXHAUSTIVE_TEST_ORDER)
/* We need to lower these values for exhaustive tests because
@@ -47,7 +47,7 @@
/* The number of objects allocated on the scratch space for ecmult_multi algorithms */
#define PIPPENGER_SCRATCH_OBJECTS 6
-#define STRAUSS_SCRATCH_OBJECTS 7
+#define STRAUSS_SCRATCH_OBJECTS 5
#define PIPPENGER_MAX_BUCKET_WINDOW 12
@@ -56,14 +56,23 @@
#define ECMULT_MAX_POINTS_PER_BATCH 5000000
-/** Fill a table 'prej' with precomputed odd multiples of a. Prej will contain
- * the values [1*a,3*a,...,(2*n-1)*a], so it space for n values. zr[0] will
- * contain prej[0].z / a.z. The other zr[i] values = prej[i].z / prej[i-1].z.
- * Prej's Z values are undefined, except for the last value.
+/** Fill a table 'pre_a' with precomputed odd multiples of a.
+ * pre_a will contain [1*a,3*a,...,(2*n-1)*a], so it needs space for n group elements.
+ * zr needs space for n field elements.
+ *
+ * Although pre_a is an array of _ge rather than _gej, it actually represents elements
+ * in Jacobian coordinates with their z coordinates omitted. The omitted z-coordinates
+ * can be recovered using z and zr. Using the notation z(b) to represent the omitted
+ * z coordinate of b:
+ * - z(pre_a[n-1]) = 'z'
+ * - z(pre_a[i-1]) = z(pre_a[i]) / zr[i] for n > i > 0
+ *
+ * Lastly the zr[0] value, which isn't used above, is set so that:
+ * - a.z = z(pre_a[0]) / zr[0]
*/
-static void secp256k1_ecmult_odd_multiples_table(int n, secp256k1_gej *prej, secp256k1_fe *zr, const secp256k1_gej *a) {
- secp256k1_gej d;
- secp256k1_ge a_ge, d_ge;
+static void secp256k1_ecmult_odd_multiples_table(int n, secp256k1_ge *pre_a, secp256k1_fe *zr, secp256k1_fe *z, const secp256k1_gej *a) {
+ secp256k1_gej d, ai;
+ secp256k1_ge d_ge;
int i;
VERIFY_CHECK(!a->infinity);
@@ -71,75 +80,74 @@ static void secp256k1_ecmult_odd_multiples_table(int n, secp256k1_gej *prej, sec
secp256k1_gej_double_var(&d, a, NULL);
/*
- * Perform the additions on an isomorphism where 'd' is affine: drop the z coordinate
- * of 'd', and scale the 1P starting value's x/y coordinates without changing its z.
+ * Perform the additions using an isomorphic curve Y^2 = X^3 + 7*C^6 where C := d.z.
+ * The isomorphism, phi, maps a secp256k1 point (x, y) to the point (x*C^2, y*C^3) on the other curve.
+ * In Jacobian coordinates phi maps (x, y, z) to (x*C^2, y*C^3, z) or, equivalently to (x, y, z/C).
+ *
+ * phi(x, y, z) = (x*C^2, y*C^3, z) = (x, y, z/C)
+ * d_ge := phi(d) = (d.x, d.y, 1)
+ * ai := phi(a) = (a.x*C^2, a.y*C^3, a.z)
+ *
+ * The group addition functions work correctly on these isomorphic curves.
+ * In particular phi(d) is easy to represent in affine coordinates under this isomorphism.
+ * This lets us use the faster secp256k1_gej_add_ge_var group addition function that we wouldn't be able to use otherwise.
*/
- d_ge.x = d.x;
- d_ge.y = d.y;
- d_ge.infinity = 0;
-
- secp256k1_ge_set_gej_zinv(&a_ge, a, &d.z);
- prej[0].x = a_ge.x;
- prej[0].y = a_ge.y;
- prej[0].z = a->z;
- prej[0].infinity = 0;
+ secp256k1_ge_set_xy(&d_ge, &d.x, &d.y);
+ secp256k1_ge_set_gej_zinv(&pre_a[0], a, &d.z);
+ secp256k1_gej_set_ge(&ai, &pre_a[0]);
+ ai.z = a->z;
+ /* pre_a[0] is the point (a.x*C^2, a.y*C^3, a.z*C) which is equvalent to a.
+ * Set zr[0] to C, which is the ratio between the omitted z(pre_a[0]) value and a.z.
+ */
zr[0] = d.z;
+
for (i = 1; i < n; i++) {
- secp256k1_gej_add_ge_var(&prej[i], &prej[i-1], &d_ge, &zr[i]);
+ secp256k1_gej_add_ge_var(&ai, &ai, &d_ge, &zr[i]);
+ secp256k1_ge_set_xy(&pre_a[i], &ai.x, &ai.y);
}
- /*
- * Each point in 'prej' has a z coordinate too small by a factor of 'd.z'. Only
- * the final point's z coordinate is actually used though, so just update that.
+ /* Multiply the last z-coordinate by C to undo the isomorphism.
+ * Since the z-coordinates of the pre_a values are implied by the zr array of z-coordinate ratios,
+ * undoing the isomorphism here undoes the isomorphism for all pre_a values.
*/
- secp256k1_fe_mul(&prej[n-1].z, &prej[n-1].z, &d.z);
+ secp256k1_fe_mul(z, &ai.z, &d.z);
}
-/** Fill a table 'pre' with precomputed odd multiples of a.
- *
- * The resulting point set is brought to a single constant Z denominator, stores the X and Y
- * coordinates as ge_storage points in pre, and stores the global Z in rz.
- * It only operates on tables sized for WINDOW_A wnaf multiples.
- *
- * To compute a*P + b*G, we compute a table for P using this function,
- * and use the precomputed table in <ecmult_static_pre_g.h> for G.
- */
-static void secp256k1_ecmult_odd_multiples_table_globalz_windowa(secp256k1_ge *pre, secp256k1_fe *globalz, const secp256k1_gej *a) {
- secp256k1_gej prej[ECMULT_TABLE_SIZE(WINDOW_A)];
- secp256k1_fe zr[ECMULT_TABLE_SIZE(WINDOW_A)];
+#define SECP256K1_ECMULT_TABLE_VERIFY(n,w) \
+ VERIFY_CHECK(((n) & 1) == 1); \
+ VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \
+ VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1));
- /* Compute the odd multiples in Jacobian form. */
- secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), prej, zr, a);
- /* Bring them to the same Z denominator. */
- secp256k1_ge_globalz_set_table_gej(ECMULT_TABLE_SIZE(WINDOW_A), pre, globalz, prej, zr);
+SECP256K1_INLINE static void secp256k1_ecmult_table_get_ge(secp256k1_ge *r, const secp256k1_ge *pre, int n, int w) {
+ SECP256K1_ECMULT_TABLE_VERIFY(n,w)
+ if (n > 0) {
+ *r = pre[(n-1)/2];
+ } else {
+ *r = pre[(-n-1)/2];
+ secp256k1_fe_negate(&(r->y), &(r->y), 1);
+ }
}
-/** The following two macro retrieves a particular odd multiple from a table
- * of precomputed multiples. */
-#define ECMULT_TABLE_GET_GE(r,pre,n,w) do { \
- VERIFY_CHECK(((n) & 1) == 1); \
- VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \
- VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); \
- if ((n) > 0) { \
- *(r) = (pre)[((n)-1)/2]; \
- } else { \
- *(r) = (pre)[(-(n)-1)/2]; \
- secp256k1_fe_negate(&((r)->y), &((r)->y), 1); \
- } \
-} while(0)
-
-#define ECMULT_TABLE_GET_GE_STORAGE(r,pre,n,w) do { \
- VERIFY_CHECK(((n) & 1) == 1); \
- VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \
- VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); \
- if ((n) > 0) { \
- secp256k1_ge_from_storage((r), &(pre)[((n)-1)/2]); \
- } else { \
- secp256k1_ge_from_storage((r), &(pre)[(-(n)-1)/2]); \
- secp256k1_fe_negate(&((r)->y), &((r)->y), 1); \
- } \
-} while(0)
+SECP256K1_INLINE static void secp256k1_ecmult_table_get_ge_lambda(secp256k1_ge *r, const secp256k1_ge *pre, const secp256k1_fe *x, int n, int w) {
+ SECP256K1_ECMULT_TABLE_VERIFY(n,w)
+ if (n > 0) {
+ secp256k1_ge_set_xy(r, &x[(n-1)/2], &pre[(n-1)/2].y);
+ } else {
+ secp256k1_ge_set_xy(r, &x[(-n-1)/2], &pre[(-n-1)/2].y);
+ secp256k1_fe_negate(&(r->y), &(r->y), 1);
+ }
+}
+
+SECP256K1_INLINE static void secp256k1_ecmult_table_get_ge_storage(secp256k1_ge *r, const secp256k1_ge_storage *pre, int n, int w) {
+ SECP256K1_ECMULT_TABLE_VERIFY(n,w)
+ if (n > 0) {
+ secp256k1_ge_from_storage(r, &pre[(n-1)/2]);
+ } else {
+ secp256k1_ge_from_storage(r, &pre[(-n-1)/2]);
+ secp256k1_fe_negate(&(r->y), &(r->y), 1);
+ }
+}
/** Convert a number to WNAF notation. The number becomes represented by sum(2^i * wnaf[i], i=0..bits),
* with the following guarantees:
@@ -201,19 +209,16 @@ static int secp256k1_ecmult_wnaf(int *wnaf, int len, const secp256k1_scalar *a,
}
struct secp256k1_strauss_point_state {
- secp256k1_scalar na_1, na_lam;
int wnaf_na_1[129];
int wnaf_na_lam[129];
int bits_na_1;
int bits_na_lam;
- size_t input_pos;
};
struct secp256k1_strauss_state {
- secp256k1_gej* prej;
- secp256k1_fe* zr;
+ /* aux is used to hold z-ratios, and then used to hold pre_a[i].x * BETA values. */
+ secp256k1_fe* aux;
secp256k1_ge* pre_a;
- secp256k1_ge* pre_a_lam;
struct secp256k1_strauss_point_state* ps;
};
@@ -231,17 +236,19 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state *
size_t np;
size_t no = 0;
+ secp256k1_fe_set_int(&Z, 1);
for (np = 0; np < num; ++np) {
+ secp256k1_gej tmp;
+ secp256k1_scalar na_1, na_lam;
if (secp256k1_scalar_is_zero(&na[np]) || secp256k1_gej_is_infinity(&a[np])) {
continue;
}
- state->ps[no].input_pos = np;
/* split na into na_1 and na_lam (where na = na_1 + na_lam*lambda, and na_1 and na_lam are ~128 bit) */
- secp256k1_scalar_split_lambda(&state->ps[no].na_1, &state->ps[no].na_lam, &na[np]);
+ secp256k1_scalar_split_lambda(&na_1, &na_lam, &na[np]);
/* build wnaf representation for na_1 and na_lam. */
- state->ps[no].bits_na_1 = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_1, 129, &state->ps[no].na_1, WINDOW_A);
- state->ps[no].bits_na_lam = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_lam, 129, &state->ps[no].na_lam, WINDOW_A);
+ state->ps[no].bits_na_1 = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_1, 129, &na_1, WINDOW_A);
+ state->ps[no].bits_na_lam = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_lam, 129, &na_lam, WINDOW_A);
VERIFY_CHECK(state->ps[no].bits_na_1 <= 129);
VERIFY_CHECK(state->ps[no].bits_na_lam <= 129);
if (state->ps[no].bits_na_1 > bits) {
@@ -250,40 +257,36 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state *
if (state->ps[no].bits_na_lam > bits) {
bits = state->ps[no].bits_na_lam;
}
- ++no;
- }
- /* Calculate odd multiples of a.
- * All multiples are brought to the same Z 'denominator', which is stored
- * in Z. Due to secp256k1' isomorphism we can do all operations pretending
- * that the Z coordinate was 1, use affine addition formulae, and correct
- * the Z coordinate of the result once at the end.
- * The exception is the precomputed G table points, which are actually
- * affine. Compared to the base used for other points, they have a Z ratio
- * of 1/Z, so we can use secp256k1_gej_add_zinv_var, which uses the same
- * isomorphism to efficiently add with a known Z inverse.
- */
- if (no > 0) {
- /* Compute the odd multiples in Jacobian form. */
- secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), state->prej, state->zr, &a[state->ps[0].input_pos]);
- for (np = 1; np < no; ++np) {
- secp256k1_gej tmp = a[state->ps[np].input_pos];
+ /* Calculate odd multiples of a.
+ * All multiples are brought to the same Z 'denominator', which is stored
+ * in Z. Due to secp256k1' isomorphism we can do all operations pretending
+ * that the Z coordinate was 1, use affine addition formulae, and correct
+ * the Z coordinate of the result once at the end.
+ * The exception is the precomputed G table points, which are actually
+ * affine. Compared to the base used for other points, they have a Z ratio
+ * of 1/Z, so we can use secp256k1_gej_add_zinv_var, which uses the same
+ * isomorphism to efficiently add with a known Z inverse.
+ */
+ tmp = a[np];
+ if (no) {
#ifdef VERIFY
- secp256k1_fe_normalize_var(&(state->prej[(np - 1) * ECMULT_TABLE_SIZE(WINDOW_A) + ECMULT_TABLE_SIZE(WINDOW_A) - 1].z));
+ secp256k1_fe_normalize_var(&Z);
#endif
- secp256k1_gej_rescale(&tmp, &(state->prej[(np - 1) * ECMULT_TABLE_SIZE(WINDOW_A) + ECMULT_TABLE_SIZE(WINDOW_A) - 1].z));
- secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), state->prej + np * ECMULT_TABLE_SIZE(WINDOW_A), state->zr + np * ECMULT_TABLE_SIZE(WINDOW_A), &tmp);
- secp256k1_fe_mul(state->zr + np * ECMULT_TABLE_SIZE(WINDOW_A), state->zr + np * ECMULT_TABLE_SIZE(WINDOW_A), &(a[state->ps[np].input_pos].z));
+ secp256k1_gej_rescale(&tmp, &Z);
}
- /* Bring them to the same Z denominator. */
- secp256k1_ge_globalz_set_table_gej(ECMULT_TABLE_SIZE(WINDOW_A) * no, state->pre_a, &Z, state->prej, state->zr);
- } else {
- secp256k1_fe_set_int(&Z, 1);
+ secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), state->pre_a + no * ECMULT_TABLE_SIZE(WINDOW_A), state->aux + no * ECMULT_TABLE_SIZE(WINDOW_A), &Z, &tmp);
+ if (no) secp256k1_fe_mul(state->aux + no * ECMULT_TABLE_SIZE(WINDOW_A), state->aux + no * ECMULT_TABLE_SIZE(WINDOW_A), &(a[np].z));
+
+ ++no;
}
+ /* Bring them to the same Z denominator. */
+ secp256k1_ge_table_set_globalz(ECMULT_TABLE_SIZE(WINDOW_A) * no, state->pre_a, state->aux);
+
for (np = 0; np < no; ++np) {
for (i = 0; i < ECMULT_TABLE_SIZE(WINDOW_A); i++) {
- secp256k1_ge_mul_lambda(&state->pre_a_lam[np * ECMULT_TABLE_SIZE(WINDOW_A) + i], &state->pre_a[np * ECMULT_TABLE_SIZE(WINDOW_A) + i]);
+ secp256k1_fe_mul(&state->aux[np * ECMULT_TABLE_SIZE(WINDOW_A) + i], &state->pre_a[np * ECMULT_TABLE_SIZE(WINDOW_A) + i].x, &secp256k1_const_beta);
}
}
@@ -309,20 +312,20 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state *
secp256k1_gej_double_var(r, r, NULL);
for (np = 0; np < no; ++np) {
if (i < state->ps[np].bits_na_1 && (n = state->ps[np].wnaf_na_1[i])) {
- ECMULT_TABLE_GET_GE(&tmpa, state->pre_a + np * ECMULT_TABLE_SIZE(WINDOW_A), n, WINDOW_A);
+ secp256k1_ecmult_table_get_ge(&tmpa, state->pre_a + np * ECMULT_TABLE_SIZE(WINDOW_A), n, WINDOW_A);
secp256k1_gej_add_ge_var(r, r, &tmpa, NULL);
}
if (i < state->ps[np].bits_na_lam && (n = state->ps[np].wnaf_na_lam[i])) {
- ECMULT_TABLE_GET_GE(&tmpa, state->pre_a_lam + np * ECMULT_TABLE_SIZE(WINDOW_A), n, WINDOW_A);
+ secp256k1_ecmult_table_get_ge_lambda(&tmpa, state->pre_a + np * ECMULT_TABLE_SIZE(WINDOW_A), state->aux + np * ECMULT_TABLE_SIZE(WINDOW_A), n, WINDOW_A);
secp256k1_gej_add_ge_var(r, r, &tmpa, NULL);
}
}
if (i < bits_ng_1 && (n = wnaf_ng_1[i])) {
- ECMULT_TABLE_GET_GE_STORAGE(&tmpa, secp256k1_pre_g, n, WINDOW_G);
+ secp256k1_ecmult_table_get_ge_storage(&tmpa, secp256k1_pre_g, n, WINDOW_G);
secp256k1_gej_add_zinv_var(r, r, &tmpa, &Z);
}
if (i < bits_ng_128 && (n = wnaf_ng_128[i])) {
- ECMULT_TABLE_GET_GE_STORAGE(&tmpa, secp256k1_pre_g_128, n, WINDOW_G);
+ secp256k1_ecmult_table_get_ge_storage(&tmpa, secp256k1_pre_g_128, n, WINDOW_G);
secp256k1_gej_add_zinv_var(r, r, &tmpa, &Z);
}
}
@@ -333,23 +336,19 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state *
}
static void secp256k1_ecmult(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_scalar *na, const secp256k1_scalar *ng) {
- secp256k1_gej prej[ECMULT_TABLE_SIZE(WINDOW_A)];
- secp256k1_fe zr[ECMULT_TABLE_SIZE(WINDOW_A)];
+ secp256k1_fe aux[ECMULT_TABLE_SIZE(WINDOW_A)];
secp256k1_ge pre_a[ECMULT_TABLE_SIZE(WINDOW_A)];
struct secp256k1_strauss_point_state ps[1];
- secp256k1_ge pre_a_lam[ECMULT_TABLE_SIZE(WINDOW_A)];
struct secp256k1_strauss_state state;
- state.prej = prej;
- state.zr = zr;
+ state.aux = aux;
state.pre_a = pre_a;
- state.pre_a_lam = pre_a_lam;
state.ps = ps;
secp256k1_ecmult_strauss_wnaf(&state, r, 1, a, na, ng);
}
static size_t secp256k1_strauss_scratch_size(size_t n_points) {
- static const size_t point_size = (2 * sizeof(secp256k1_ge) + sizeof(secp256k1_gej) + sizeof(secp256k1_fe)) * ECMULT_TABLE_SIZE(WINDOW_A) + sizeof(struct secp256k1_strauss_point_state) + sizeof(secp256k1_gej) + sizeof(secp256k1_scalar);
+ static const size_t point_size = (sizeof(secp256k1_ge) + sizeof(secp256k1_fe)) * ECMULT_TABLE_SIZE(WINDOW_A) + sizeof(struct secp256k1_strauss_point_state) + sizeof(secp256k1_gej) + sizeof(secp256k1_scalar);
return n_points*point_size;
}
@@ -370,13 +369,11 @@ static int secp256k1_ecmult_strauss_batch(const secp256k1_callback* error_callba
* constant and strauss_scratch_size accordingly. */
points = (secp256k1_gej*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_gej));
scalars = (secp256k1_scalar*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_scalar));
- state.prej = (secp256k1_gej*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_gej));
- state.zr = (secp256k1_fe*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe));
+ state.aux = (secp256k1_fe*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe));
state.pre_a = (secp256k1_ge*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge));
- state.pre_a_lam = (secp256k1_ge*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge));
state.ps = (struct secp256k1_strauss_point_state*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(struct secp256k1_strauss_point_state));
- if (points == NULL || scalars == NULL || state.prej == NULL || state.zr == NULL || state.pre_a == NULL || state.pre_a_lam == NULL || state.ps == NULL) {
+ if (points == NULL || scalars == NULL || state.aux == NULL || state.pre_a == NULL || state.ps == NULL) {
secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint);
return 0;
}
diff --git a/src/secp256k1/src/field.h b/src/secp256k1/src/field.h
index 55679a2fc1..2584a494ee 100644
--- a/src/secp256k1/src/field.h
+++ b/src/secp256k1/src/field.h
@@ -32,6 +32,12 @@
#error "Please select wide multiplication implementation"
#endif
+static const secp256k1_fe secp256k1_fe_one = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1);
+static const secp256k1_fe secp256k1_const_beta = SECP256K1_FE_CONST(
+ 0x7ae96a2bul, 0x657c0710ul, 0x6e64479eul, 0xac3434e9ul,
+ 0x9cf04975ul, 0x12f58995ul, 0xc1396c28ul, 0x719501eeul
+);
+
/** Normalize a field element. This brings the field element to a canonical representation, reduces
* its magnitude to 1, and reduces it modulo field size `p`.
*/
@@ -124,4 +130,13 @@ static void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_f
/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/
static void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag);
+/** Halves the value of a field element modulo the field prime. Constant-time.
+ * For an input magnitude 'm', the output magnitude is set to 'floor(m/2) + 1'.
+ * The output is not guaranteed to be normalized, regardless of the input. */
+static void secp256k1_fe_half(secp256k1_fe *r);
+
+/** Sets each limb of 'r' to its upper bound at magnitude 'm'. The output will also have its
+ * magnitude set to 'm' and is normalized if (and only if) 'm' is zero. */
+static void secp256k1_fe_get_bounds(secp256k1_fe *r, int m);
+
#endif /* SECP256K1_FIELD_H */
diff --git a/src/secp256k1/src/field_10x26_impl.h b/src/secp256k1/src/field_10x26_impl.h
index 4363e727e7..21742bf6eb 100644
--- a/src/secp256k1/src/field_10x26_impl.h
+++ b/src/secp256k1/src/field_10x26_impl.h
@@ -11,6 +11,15 @@
#include "field.h"
#include "modinv32_impl.h"
+/** See the comment at the top of field_5x52_impl.h for more details.
+ *
+ * Here, we represent field elements as 10 uint32_t's in base 2^26, least significant first,
+ * where limbs can contain >26 bits.
+ * A magnitude M means:
+ * - 2*M*(2^22-1) is the max (inclusive) of the most significant limb
+ * - 2*M*(2^26-1) is the max (inclusive) of the remaining limbs
+ */
+
#ifdef VERIFY
static void secp256k1_fe_verify(const secp256k1_fe *a) {
const uint32_t *d = a->n;
@@ -40,6 +49,26 @@ static void secp256k1_fe_verify(const secp256k1_fe *a) {
}
#endif
+static void secp256k1_fe_get_bounds(secp256k1_fe *r, int m) {
+ VERIFY_CHECK(m >= 0);
+ VERIFY_CHECK(m <= 2048);
+ r->n[0] = 0x3FFFFFFUL * 2 * m;
+ r->n[1] = 0x3FFFFFFUL * 2 * m;
+ r->n[2] = 0x3FFFFFFUL * 2 * m;
+ r->n[3] = 0x3FFFFFFUL * 2 * m;
+ r->n[4] = 0x3FFFFFFUL * 2 * m;
+ r->n[5] = 0x3FFFFFFUL * 2 * m;
+ r->n[6] = 0x3FFFFFFUL * 2 * m;
+ r->n[7] = 0x3FFFFFFUL * 2 * m;
+ r->n[8] = 0x3FFFFFFUL * 2 * m;
+ r->n[9] = 0x03FFFFFUL * 2 * m;
+#ifdef VERIFY
+ r->magnitude = m;
+ r->normalized = (m == 0);
+ secp256k1_fe_verify(r);
+#endif
+}
+
static void secp256k1_fe_normalize(secp256k1_fe *r) {
uint32_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4],
t5 = r->n[5], t6 = r->n[6], t7 = r->n[7], t8 = r->n[8], t9 = r->n[9];
@@ -391,6 +420,10 @@ SECP256K1_INLINE static void secp256k1_fe_negate(secp256k1_fe *r, const secp256k
#ifdef VERIFY
VERIFY_CHECK(a->magnitude <= m);
secp256k1_fe_verify(a);
+ VERIFY_CHECK(0x3FFFC2FUL * 2 * (m + 1) >= 0x3FFFFFFUL * 2 * m);
+ VERIFY_CHECK(0x3FFFFBFUL * 2 * (m + 1) >= 0x3FFFFFFUL * 2 * m);
+ VERIFY_CHECK(0x3FFFFFFUL * 2 * (m + 1) >= 0x3FFFFFFUL * 2 * m);
+ VERIFY_CHECK(0x03FFFFFUL * 2 * (m + 1) >= 0x03FFFFFUL * 2 * m);
#endif
r->n[0] = 0x3FFFC2FUL * 2 * (m + 1) - a->n[0];
r->n[1] = 0x3FFFFBFUL * 2 * (m + 1) - a->n[1];
@@ -1120,6 +1153,82 @@ static SECP256K1_INLINE void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_
#endif
}
+static SECP256K1_INLINE void secp256k1_fe_half(secp256k1_fe *r) {
+ uint32_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4],
+ t5 = r->n[5], t6 = r->n[6], t7 = r->n[7], t8 = r->n[8], t9 = r->n[9];
+ uint32_t one = (uint32_t)1;
+ uint32_t mask = -(t0 & one) >> 6;
+
+#ifdef VERIFY
+ secp256k1_fe_verify(r);
+ VERIFY_CHECK(r->magnitude < 32);
+#endif
+
+ /* Bounds analysis (over the rationals).
+ *
+ * Let m = r->magnitude
+ * C = 0x3FFFFFFUL * 2
+ * D = 0x03FFFFFUL * 2
+ *
+ * Initial bounds: t0..t8 <= C * m
+ * t9 <= D * m
+ */
+
+ t0 += 0x3FFFC2FUL & mask;
+ t1 += 0x3FFFFBFUL & mask;
+ t2 += mask;
+ t3 += mask;
+ t4 += mask;
+ t5 += mask;
+ t6 += mask;
+ t7 += mask;
+ t8 += mask;
+ t9 += mask >> 4;
+
+ VERIFY_CHECK((t0 & one) == 0);
+
+ /* t0..t8: added <= C/2
+ * t9: added <= D/2
+ *
+ * Current bounds: t0..t8 <= C * (m + 1/2)
+ * t9 <= D * (m + 1/2)
+ */
+
+ r->n[0] = (t0 >> 1) + ((t1 & one) << 25);
+ r->n[1] = (t1 >> 1) + ((t2 & one) << 25);
+ r->n[2] = (t2 >> 1) + ((t3 & one) << 25);
+ r->n[3] = (t3 >> 1) + ((t4 & one) << 25);
+ r->n[4] = (t4 >> 1) + ((t5 & one) << 25);
+ r->n[5] = (t5 >> 1) + ((t6 & one) << 25);
+ r->n[6] = (t6 >> 1) + ((t7 & one) << 25);
+ r->n[7] = (t7 >> 1) + ((t8 & one) << 25);
+ r->n[8] = (t8 >> 1) + ((t9 & one) << 25);
+ r->n[9] = (t9 >> 1);
+
+ /* t0..t8: shifted right and added <= C/4 + 1/2
+ * t9: shifted right
+ *
+ * Current bounds: t0..t8 <= C * (m/2 + 1/2)
+ * t9 <= D * (m/2 + 1/4)
+ */
+
+#ifdef VERIFY
+ /* Therefore the output magnitude (M) has to be set such that:
+ * t0..t8: C * M >= C * (m/2 + 1/2)
+ * t9: D * M >= D * (m/2 + 1/4)
+ *
+ * It suffices for all limbs that, for any input magnitude m:
+ * M >= m/2 + 1/2
+ *
+ * and since we want the smallest such integer value for M:
+ * M == floor(m/2) + 1
+ */
+ r->magnitude = (r->magnitude >> 1) + 1;
+ r->normalized = 0;
+ secp256k1_fe_verify(r);
+#endif
+}
+
static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) {
uint32_t mask0, mask1;
VG_CHECK_VERIFY(r->n, sizeof(r->n));
diff --git a/src/secp256k1/src/field_5x52_impl.h b/src/secp256k1/src/field_5x52_impl.h
index b56bdd1353..6bd202f587 100644
--- a/src/secp256k1/src/field_5x52_impl.h
+++ b/src/secp256k1/src/field_5x52_impl.h
@@ -22,11 +22,18 @@
#endif
/** Implements arithmetic modulo FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F,
- * represented as 5 uint64_t's in base 2^52. The values are allowed to contain >52 each. In particular,
- * each FieldElem has a 'magnitude' associated with it. Internally, a magnitude M means each element
- * is at most M*(2^53-1), except the most significant one, which is limited to M*(2^49-1). All operations
- * accept any input with magnitude at most M, and have different rules for propagating magnitude to their
- * output.
+ * represented as 5 uint64_t's in base 2^52, least significant first. Note that the limbs are allowed to
+ * contain >52 bits each.
+ *
+ * Each field element has a 'magnitude' associated with it. Internally, a magnitude M means:
+ * - 2*M*(2^48-1) is the max (inclusive) of the most significant limb
+ * - 2*M*(2^52-1) is the max (inclusive) of the remaining limbs
+ *
+ * Operations have different rules for propagating magnitude to their outputs. If an operation takes a
+ * magnitude M as a parameter, that means the magnitude of input field elements can be at most M (inclusive).
+ *
+ * Each field element also has a 'normalized' flag. A field element is normalized if its magnitude is either
+ * 0 or 1, and its value is already reduced modulo the order of the field.
*/
#ifdef VERIFY
@@ -51,6 +58,21 @@ static void secp256k1_fe_verify(const secp256k1_fe *a) {
}
#endif
+static void secp256k1_fe_get_bounds(secp256k1_fe *r, int m) {
+ VERIFY_CHECK(m >= 0);
+ VERIFY_CHECK(m <= 2048);
+ r->n[0] = 0xFFFFFFFFFFFFFULL * 2 * m;
+ r->n[1] = 0xFFFFFFFFFFFFFULL * 2 * m;
+ r->n[2] = 0xFFFFFFFFFFFFFULL * 2 * m;
+ r->n[3] = 0xFFFFFFFFFFFFFULL * 2 * m;
+ r->n[4] = 0x0FFFFFFFFFFFFULL * 2 * m;
+#ifdef VERIFY
+ r->magnitude = m;
+ r->normalized = (m == 0);
+ secp256k1_fe_verify(r);
+#endif
+}
+
static void secp256k1_fe_normalize(secp256k1_fe *r) {
uint64_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4];
@@ -377,6 +399,9 @@ SECP256K1_INLINE static void secp256k1_fe_negate(secp256k1_fe *r, const secp256k
#ifdef VERIFY
VERIFY_CHECK(a->magnitude <= m);
secp256k1_fe_verify(a);
+ VERIFY_CHECK(0xFFFFEFFFFFC2FULL * 2 * (m + 1) >= 0xFFFFFFFFFFFFFULL * 2 * m);
+ VERIFY_CHECK(0xFFFFFFFFFFFFFULL * 2 * (m + 1) >= 0xFFFFFFFFFFFFFULL * 2 * m);
+ VERIFY_CHECK(0x0FFFFFFFFFFFFULL * 2 * (m + 1) >= 0x0FFFFFFFFFFFFULL * 2 * m);
#endif
r->n[0] = 0xFFFFEFFFFFC2FULL * 2 * (m + 1) - a->n[0];
r->n[1] = 0xFFFFFFFFFFFFFULL * 2 * (m + 1) - a->n[1];
@@ -467,6 +492,71 @@ static SECP256K1_INLINE void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_
#endif
}
+static SECP256K1_INLINE void secp256k1_fe_half(secp256k1_fe *r) {
+ uint64_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4];
+ uint64_t one = (uint64_t)1;
+ uint64_t mask = -(t0 & one) >> 12;
+
+#ifdef VERIFY
+ secp256k1_fe_verify(r);
+ VERIFY_CHECK(r->magnitude < 32);
+#endif
+
+ /* Bounds analysis (over the rationals).
+ *
+ * Let m = r->magnitude
+ * C = 0xFFFFFFFFFFFFFULL * 2
+ * D = 0x0FFFFFFFFFFFFULL * 2
+ *
+ * Initial bounds: t0..t3 <= C * m
+ * t4 <= D * m
+ */
+
+ t0 += 0xFFFFEFFFFFC2FULL & mask;
+ t1 += mask;
+ t2 += mask;
+ t3 += mask;
+ t4 += mask >> 4;
+
+ VERIFY_CHECK((t0 & one) == 0);
+
+ /* t0..t3: added <= C/2
+ * t4: added <= D/2
+ *
+ * Current bounds: t0..t3 <= C * (m + 1/2)
+ * t4 <= D * (m + 1/2)
+ */
+
+ r->n[0] = (t0 >> 1) + ((t1 & one) << 51);
+ r->n[1] = (t1 >> 1) + ((t2 & one) << 51);
+ r->n[2] = (t2 >> 1) + ((t3 & one) << 51);
+ r->n[3] = (t3 >> 1) + ((t4 & one) << 51);
+ r->n[4] = (t4 >> 1);
+
+ /* t0..t3: shifted right and added <= C/4 + 1/2
+ * t4: shifted right
+ *
+ * Current bounds: t0..t3 <= C * (m/2 + 1/2)
+ * t4 <= D * (m/2 + 1/4)
+ */
+
+#ifdef VERIFY
+ /* Therefore the output magnitude (M) has to be set such that:
+ * t0..t3: C * M >= C * (m/2 + 1/2)
+ * t4: D * M >= D * (m/2 + 1/4)
+ *
+ * It suffices for all limbs that, for any input magnitude m:
+ * M >= m/2 + 1/2
+ *
+ * and since we want the smallest such integer value for M:
+ * M == floor(m/2) + 1
+ */
+ r->magnitude = (r->magnitude >> 1) + 1;
+ r->normalized = 0;
+ secp256k1_fe_verify(r);
+#endif
+}
+
static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) {
uint64_t mask0, mask1;
VG_CHECK_VERIFY(r->n, sizeof(r->n));
diff --git a/src/secp256k1/src/field_impl.h b/src/secp256k1/src/field_impl.h
index 374284a1f4..0a4a04d9ac 100644
--- a/src/secp256k1/src/field_impl.h
+++ b/src/secp256k1/src/field_impl.h
@@ -135,6 +135,4 @@ static int secp256k1_fe_sqrt(secp256k1_fe *r, const secp256k1_fe *a) {
return secp256k1_fe_equal(&t1, a);
}
-static const secp256k1_fe secp256k1_fe_one = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1);
-
#endif /* SECP256K1_FIELD_IMPL_H */
diff --git a/src/secp256k1/src/gen_ecmult_static_pre_g.c b/src/secp256k1/src/gen_ecmult_static_pre_g.c
deleted file mode 100644
index ba1d1f17d7..0000000000
--- a/src/secp256k1/src/gen_ecmult_static_pre_g.c
+++ /dev/null
@@ -1,131 +0,0 @@
-/*****************************************************************************************************
- * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor *
- * Distributed under the MIT software license, see the accompanying *
- * file COPYING or https://www.opensource.org/licenses/mit-license.php. *
- *****************************************************************************************************/
-
-#include <inttypes.h>
-#include <stdio.h>
-
-/* Autotools creates libsecp256k1-config.h, of which ECMULT_WINDOW_SIZE is needed.
- ifndef guard so downstream users can define their own if they do not use autotools. */
-#if !defined(ECMULT_WINDOW_SIZE)
-#include "libsecp256k1-config.h"
-#endif
-
-#include "../include/secp256k1.h"
-#include "assumptions.h"
-#include "util.h"
-#include "field_impl.h"
-#include "group_impl.h"
-#include "ecmult.h"
-
-void print_table(FILE *fp, const char *name, int window_g, const secp256k1_gej *gen, int with_conditionals) {
- static secp256k1_gej gj;
- static secp256k1_ge ge, dgen;
- static secp256k1_ge_storage ges;
- int j;
- int i;
-
- gj = *gen;
- secp256k1_ge_set_gej_var(&ge, &gj);
- secp256k1_ge_to_storage(&ges, &ge);
-
- fprintf(fp, "static const secp256k1_ge_storage %s[ECMULT_TABLE_SIZE(WINDOW_G)] = {\n", name);
- fprintf(fp, " S(%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32
- ",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32")\n",
- SECP256K1_GE_STORAGE_CONST_GET(ges));
-
- secp256k1_gej_double_var(&gj, gen, NULL);
- secp256k1_ge_set_gej_var(&dgen, &gj);
-
- j = 1;
- for(i = 3; i <= window_g; ++i) {
- if (with_conditionals) {
- fprintf(fp, "#if ECMULT_TABLE_SIZE(WINDOW_G) > %ld\n", ECMULT_TABLE_SIZE(i-1));
- }
- for(;j < ECMULT_TABLE_SIZE(i); ++j) {
- secp256k1_gej_set_ge(&gj, &ge);
- secp256k1_gej_add_ge_var(&gj, &gj, &dgen, NULL);
- secp256k1_ge_set_gej_var(&ge, &gj);
- secp256k1_ge_to_storage(&ges, &ge);
-
- fprintf(fp, ",S(%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32
- ",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32")\n",
- SECP256K1_GE_STORAGE_CONST_GET(ges));
- }
- if (with_conditionals) {
- fprintf(fp, "#endif\n");
- }
- }
- fprintf(fp, "};\n");
-}
-
-void print_two_tables(FILE *fp, int window_g, const secp256k1_ge *g, int with_conditionals) {
- secp256k1_gej gj;
- int i;
-
- secp256k1_gej_set_ge(&gj, g);
- print_table(fp, "secp256k1_pre_g", window_g, &gj, with_conditionals);
- for (i = 0; i < 128; ++i) {
- secp256k1_gej_double_var(&gj, &gj, NULL);
- }
- print_table(fp, "secp256k1_pre_g_128", window_g, &gj, with_conditionals);
-}
-
-int main(void) {
- const secp256k1_ge g = SECP256K1_G;
- const secp256k1_ge g_13 = SECP256K1_G_ORDER_13;
- const secp256k1_ge g_199 = SECP256K1_G_ORDER_199;
- const int window_g_13 = 4;
- const int window_g_199 = 8;
- FILE* fp;
-
- fp = fopen("src/ecmult_static_pre_g.h","w");
- if (fp == NULL) {
- fprintf(stderr, "Could not open src/ecmult_static_pre_g.h for writing!\n");
- return -1;
- }
-
- fprintf(fp, "/* This file was automatically generated by gen_ecmult_static_pre_g. */\n");
- fprintf(fp, "/* This file contains an array secp256k1_pre_g with odd multiples of the base point G and\n");
- fprintf(fp, " * an array secp256k1_pre_g_128 with odd multiples of 2^128*G for accelerating the computation of a*P + b*G.\n");
- fprintf(fp, " */\n");
- fprintf(fp, "#ifndef SECP256K1_ECMULT_STATIC_PRE_G_H\n");
- fprintf(fp, "#define SECP256K1_ECMULT_STATIC_PRE_G_H\n");
- fprintf(fp, "#include \"group.h\"\n");
- fprintf(fp, "#ifdef S\n");
- fprintf(fp, " #error macro identifier S already in use.\n");
- fprintf(fp, "#endif\n");
- fprintf(fp, "#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) "
- "SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,"
- "0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)\n");
- fprintf(fp, "#if ECMULT_TABLE_SIZE(ECMULT_WINDOW_SIZE) > %ld\n", ECMULT_TABLE_SIZE(ECMULT_WINDOW_SIZE));
- fprintf(fp, " #error configuration mismatch, invalid ECMULT_WINDOW_SIZE. Try deleting ecmult_static_pre_g.h before the build.\n");
- fprintf(fp, "#endif\n");
- fprintf(fp, "#if defined(EXHAUSTIVE_TEST_ORDER)\n");
- fprintf(fp, "#if EXHAUSTIVE_TEST_ORDER == 13\n");
- fprintf(fp, "#define WINDOW_G %d\n", window_g_13);
-
- print_two_tables(fp, window_g_13, &g_13, 0);
-
- fprintf(fp, "#elif EXHAUSTIVE_TEST_ORDER == 199\n");
- fprintf(fp, "#define WINDOW_G %d\n", window_g_199);
-
- print_two_tables(fp, window_g_199, &g_199, 0);
-
- fprintf(fp, "#else\n");
- fprintf(fp, " #error No known generator for the specified exhaustive test group order.\n");
- fprintf(fp, "#endif\n");
- fprintf(fp, "#else /* !defined(EXHAUSTIVE_TEST_ORDER) */\n");
- fprintf(fp, "#define WINDOW_G ECMULT_WINDOW_SIZE\n");
-
- print_two_tables(fp, ECMULT_WINDOW_SIZE, &g, 1);
-
- fprintf(fp, "#endif\n");
- fprintf(fp, "#undef S\n");
- fprintf(fp, "#endif\n");
- fclose(fp);
-
- return 0;
-}
diff --git a/src/secp256k1/src/group.h b/src/secp256k1/src/group.h
index b9cd334dae..bb7dae1cf7 100644
--- a/src/secp256k1/src/group.h
+++ b/src/secp256k1/src/group.h
@@ -9,7 +9,10 @@
#include "field.h"
-/** A group element of the secp256k1 curve, in affine coordinates. */
+/** A group element in affine coordinates on the secp256k1 curve,
+ * or occasionally on an isomorphic curve of the form y^2 = x^3 + 7*t^6.
+ * Note: For exhaustive test mode, secp256k1 is replaced by a small subgroup of a different curve.
+ */
typedef struct {
secp256k1_fe x;
secp256k1_fe y;
@@ -19,7 +22,9 @@ typedef struct {
#define SECP256K1_GE_CONST(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {SECP256K1_FE_CONST((a),(b),(c),(d),(e),(f),(g),(h)), SECP256K1_FE_CONST((i),(j),(k),(l),(m),(n),(o),(p)), 0}
#define SECP256K1_GE_CONST_INFINITY {SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), 1}
-/** A group element of the secp256k1 curve, in jacobian coordinates. */
+/** A group element of the secp256k1 curve, in jacobian coordinates.
+ * Note: For exhastive test mode, sepc256k1 is replaced by a small subgroup of a different curve.
+ */
typedef struct {
secp256k1_fe x; /* actual X: x/z^2 */
secp256k1_fe y; /* actual Y: y/z^3 */
@@ -64,12 +69,24 @@ static void secp256k1_ge_set_gej_var(secp256k1_ge *r, secp256k1_gej *a);
/** Set a batch of group elements equal to the inputs given in jacobian coordinates */
static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len);
-/** Bring a batch inputs given in jacobian coordinates (with known z-ratios) to
- * the same global z "denominator". zr must contain the known z-ratios such
- * that mul(a[i].z, zr[i+1]) == a[i+1].z. zr[0] is ignored. The x and y
- * coordinates of the result are stored in r, the common z coordinate is
- * stored in globalz. */
-static void secp256k1_ge_globalz_set_table_gej(size_t len, secp256k1_ge *r, secp256k1_fe *globalz, const secp256k1_gej *a, const secp256k1_fe *zr);
+/** Bring a batch of inputs to the same global z "denominator", based on ratios between
+ * (omitted) z coordinates of adjacent elements.
+ *
+ * Although the elements a[i] are _ge rather than _gej, they actually represent elements
+ * in Jacobian coordinates with their z coordinates omitted.
+ *
+ * Using the notation z(b) to represent the omitted z coordinate of b, the array zr of
+ * z coordinate ratios must satisfy zr[i] == z(a[i]) / z(a[i-1]) for 0 < 'i' < len.
+ * The zr[0] value is unused.
+ *
+ * This function adjusts the coordinates of 'a' in place so that for all 'i', z(a[i]) == z(a[len-1]).
+ * In other words, the initial value of z(a[len-1]) becomes the global z "denominator". Only the
+ * a[i].x and a[i].y coordinates are explicitly modified; the adjustment of the omitted z coordinate is
+ * implicit.
+ *
+ * The coordinates of the final element a[len-1] are not changed.
+ */
+static void secp256k1_ge_table_set_globalz(size_t len, secp256k1_ge *a, const secp256k1_fe *zr);
/** Set a group element (affine) equal to the point at infinity. */
static void secp256k1_ge_set_infinity(secp256k1_ge *r);
@@ -125,6 +142,9 @@ static void secp256k1_ge_to_storage(secp256k1_ge_storage *r, const secp256k1_ge
static void secp256k1_ge_from_storage(secp256k1_ge *r, const secp256k1_ge_storage *a);
/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/
+static void secp256k1_gej_cmov(secp256k1_gej *r, const secp256k1_gej *a, int flag);
+
+/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/
static void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_ge_storage *a, int flag);
/** Rescale a jacobian point by b which must be non-zero. Constant-time. */
diff --git a/src/secp256k1/src/group_impl.h b/src/secp256k1/src/group_impl.h
index bce9fbdad5..b19b02a01f 100644
--- a/src/secp256k1/src/group_impl.h
+++ b/src/secp256k1/src/group_impl.h
@@ -161,27 +161,26 @@ static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a
}
}
-static void secp256k1_ge_globalz_set_table_gej(size_t len, secp256k1_ge *r, secp256k1_fe *globalz, const secp256k1_gej *a, const secp256k1_fe *zr) {
+static void secp256k1_ge_table_set_globalz(size_t len, secp256k1_ge *a, const secp256k1_fe *zr) {
size_t i = len - 1;
secp256k1_fe zs;
if (len > 0) {
- /* The z of the final point gives us the "global Z" for the table. */
- r[i].x = a[i].x;
- r[i].y = a[i].y;
/* Ensure all y values are in weak normal form for fast negation of points */
- secp256k1_fe_normalize_weak(&r[i].y);
- *globalz = a[i].z;
- r[i].infinity = 0;
+ secp256k1_fe_normalize_weak(&a[i].y);
zs = zr[i];
/* Work our way backwards, using the z-ratios to scale the x/y values. */
while (i > 0) {
+ secp256k1_gej tmpa;
if (i != len - 1) {
secp256k1_fe_mul(&zs, &zs, &zr[i]);
}
i--;
- secp256k1_ge_set_gej_zinv(&r[i], &a[i], &zs);
+ tmpa.x = a[i].x;
+ tmpa.y = a[i].y;
+ tmpa.infinity = 0;
+ secp256k1_ge_set_gej_zinv(&a[i], &tmpa, &zs);
}
}
}
@@ -272,37 +271,35 @@ static int secp256k1_ge_is_valid_var(const secp256k1_ge *a) {
}
static SECP256K1_INLINE void secp256k1_gej_double(secp256k1_gej *r, const secp256k1_gej *a) {
- /* Operations: 3 mul, 4 sqr, 0 normalize, 12 mul_int/add/negate.
- *
- * Note that there is an implementation described at
- * https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
- * which trades a multiply for a square, but in practice this is actually slower,
- * mainly because it requires more normalizations.
- */
- secp256k1_fe t1,t2,t3,t4;
+ /* Operations: 3 mul, 4 sqr, 8 add/half/mul_int/negate */
+ secp256k1_fe l, s, t;
r->infinity = a->infinity;
- secp256k1_fe_mul(&r->z, &a->z, &a->y);
- secp256k1_fe_mul_int(&r->z, 2); /* Z' = 2*Y*Z (2) */
- secp256k1_fe_sqr(&t1, &a->x);
- secp256k1_fe_mul_int(&t1, 3); /* T1 = 3*X^2 (3) */
- secp256k1_fe_sqr(&t2, &t1); /* T2 = 9*X^4 (1) */
- secp256k1_fe_sqr(&t3, &a->y);
- secp256k1_fe_mul_int(&t3, 2); /* T3 = 2*Y^2 (2) */
- secp256k1_fe_sqr(&t4, &t3);
- secp256k1_fe_mul_int(&t4, 2); /* T4 = 8*Y^4 (2) */
- secp256k1_fe_mul(&t3, &t3, &a->x); /* T3 = 2*X*Y^2 (1) */
- r->x = t3;
- secp256k1_fe_mul_int(&r->x, 4); /* X' = 8*X*Y^2 (4) */
- secp256k1_fe_negate(&r->x, &r->x, 4); /* X' = -8*X*Y^2 (5) */
- secp256k1_fe_add(&r->x, &t2); /* X' = 9*X^4 - 8*X*Y^2 (6) */
- secp256k1_fe_negate(&t2, &t2, 1); /* T2 = -9*X^4 (2) */
- secp256k1_fe_mul_int(&t3, 6); /* T3 = 12*X*Y^2 (6) */
- secp256k1_fe_add(&t3, &t2); /* T3 = 12*X*Y^2 - 9*X^4 (8) */
- secp256k1_fe_mul(&r->y, &t1, &t3); /* Y' = 36*X^3*Y^2 - 27*X^6 (1) */
- secp256k1_fe_negate(&t2, &t4, 2); /* T2 = -8*Y^4 (3) */
- secp256k1_fe_add(&r->y, &t2); /* Y' = 36*X^3*Y^2 - 27*X^6 - 8*Y^4 (4) */
+ /* Formula used:
+ * L = (3/2) * X1^2
+ * S = Y1^2
+ * T = -X1*S
+ * X3 = L^2 + 2*T
+ * Y3 = -(L*(X3 + T) + S^2)
+ * Z3 = Y1*Z1
+ */
+
+ secp256k1_fe_mul(&r->z, &a->z, &a->y); /* Z3 = Y1*Z1 (1) */
+ secp256k1_fe_sqr(&s, &a->y); /* S = Y1^2 (1) */
+ secp256k1_fe_sqr(&l, &a->x); /* L = X1^2 (1) */
+ secp256k1_fe_mul_int(&l, 3); /* L = 3*X1^2 (3) */
+ secp256k1_fe_half(&l); /* L = 3/2*X1^2 (2) */
+ secp256k1_fe_negate(&t, &s, 1); /* T = -S (2) */
+ secp256k1_fe_mul(&t, &t, &a->x); /* T = -X1*S (1) */
+ secp256k1_fe_sqr(&r->x, &l); /* X3 = L^2 (1) */
+ secp256k1_fe_add(&r->x, &t); /* X3 = L^2 + T (2) */
+ secp256k1_fe_add(&r->x, &t); /* X3 = L^2 + 2*T (3) */
+ secp256k1_fe_sqr(&s, &s); /* S' = S^2 (1) */
+ secp256k1_fe_add(&t, &r->x); /* T' = X3 + T (4) */
+ secp256k1_fe_mul(&r->y, &t, &l); /* Y3 = L*(X3 + T) (1) */
+ secp256k1_fe_add(&r->y, &s); /* Y3 = L*(X3 + T) + S^2 (2) */
+ secp256k1_fe_negate(&r->y, &r->y, 2); /* Y3 = -(L*(X3 + T) + S^2) (3) */
}
static void secp256k1_gej_double_var(secp256k1_gej *r, const secp256k1_gej *a, secp256k1_fe *rzr) {
@@ -327,7 +324,6 @@ static void secp256k1_gej_double_var(secp256k1_gej *r, const secp256k1_gej *a, s
if (rzr != NULL) {
*rzr = a->y;
secp256k1_fe_normalize_weak(rzr);
- secp256k1_fe_mul_int(rzr, 2);
}
secp256k1_gej_double(r, a);
@@ -493,8 +489,7 @@ static void secp256k1_gej_add_zinv_var(secp256k1_gej *r, const secp256k1_gej *a,
static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b) {
- /* Operations: 7 mul, 5 sqr, 4 normalize, 21 mul_int/add/negate/cmov */
- static const secp256k1_fe fe_1 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1);
+ /* Operations: 7 mul, 5 sqr, 24 add/cmov/half/mul_int/negate/normalize_weak/normalizes_to_zero */
secp256k1_fe zz, u1, u2, s1, s2, t, tt, m, n, q, rr;
secp256k1_fe m_alt, rr_alt;
int infinity, degenerate;
@@ -515,11 +510,11 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const
* Z = Z1*Z2
* T = U1+U2
* M = S1+S2
- * Q = T*M^2
+ * Q = -T*M^2
* R = T^2-U1*U2
- * X3 = 4*(R^2-Q)
- * Y3 = 4*(R*(3*Q-2*R^2)-M^4)
- * Z3 = 2*M*Z
+ * X3 = R^2+Q
+ * Y3 = -(R*(2*X3+Q)+M^4)/2
+ * Z3 = M*Z
* (Note that the paper uses xi = Xi / Zi and yi = Yi / Zi instead.)
*
* This formula has the benefit of being the same for both addition
@@ -583,7 +578,8 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const
* and denominator of lambda; R and M represent the explicit
* expressions x1^2 + x2^2 + x1x2 and y1 + y2. */
secp256k1_fe_sqr(&n, &m_alt); /* n = Malt^2 (1) */
- secp256k1_fe_mul(&q, &n, &t); /* q = Q = T*Malt^2 (1) */
+ secp256k1_fe_negate(&q, &t, 2); /* q = -T (3) */
+ secp256k1_fe_mul(&q, &q, &n); /* q = Q = -T*Malt^2 (1) */
/* These two lines use the observation that either M == Malt or M == 0,
* so M^3 * Malt is either Malt^4 (which is computed by squaring), or
* zero (which is "computed" by cmov). So the cost is one squaring
@@ -591,26 +587,21 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const
secp256k1_fe_sqr(&n, &n);
secp256k1_fe_cmov(&n, &m, degenerate); /* n = M^3 * Malt (2) */
secp256k1_fe_sqr(&t, &rr_alt); /* t = Ralt^2 (1) */
- secp256k1_fe_mul(&r->z, &a->z, &m_alt); /* r->z = Malt*Z (1) */
+ secp256k1_fe_mul(&r->z, &a->z, &m_alt); /* r->z = Z3 = Malt*Z (1) */
infinity = secp256k1_fe_normalizes_to_zero(&r->z) & ~a->infinity;
- secp256k1_fe_mul_int(&r->z, 2); /* r->z = Z3 = 2*Malt*Z (2) */
- secp256k1_fe_negate(&q, &q, 1); /* q = -Q (2) */
- secp256k1_fe_add(&t, &q); /* t = Ralt^2-Q (3) */
- secp256k1_fe_normalize_weak(&t);
- r->x = t; /* r->x = Ralt^2-Q (1) */
- secp256k1_fe_mul_int(&t, 2); /* t = 2*x3 (2) */
- secp256k1_fe_add(&t, &q); /* t = 2*x3 - Q: (4) */
- secp256k1_fe_mul(&t, &t, &rr_alt); /* t = Ralt*(2*x3 - Q) (1) */
- secp256k1_fe_add(&t, &n); /* t = Ralt*(2*x3 - Q) + M^3*Malt (3) */
- secp256k1_fe_negate(&r->y, &t, 3); /* r->y = Ralt*(Q - 2x3) - M^3*Malt (4) */
- secp256k1_fe_normalize_weak(&r->y);
- secp256k1_fe_mul_int(&r->x, 4); /* r->x = X3 = 4*(Ralt^2-Q) */
- secp256k1_fe_mul_int(&r->y, 4); /* r->y = Y3 = 4*Ralt*(Q - 2x3) - 4*M^3*Malt (4) */
+ secp256k1_fe_add(&t, &q); /* t = Ralt^2 + Q (2) */
+ r->x = t; /* r->x = X3 = Ralt^2 + Q (2) */
+ secp256k1_fe_mul_int(&t, 2); /* t = 2*X3 (4) */
+ secp256k1_fe_add(&t, &q); /* t = 2*X3 + Q (5) */
+ secp256k1_fe_mul(&t, &t, &rr_alt); /* t = Ralt*(2*X3 + Q) (1) */
+ secp256k1_fe_add(&t, &n); /* t = Ralt*(2*X3 + Q) + M^3*Malt (3) */
+ secp256k1_fe_negate(&r->y, &t, 3); /* r->y = -(Ralt*(2*X3 + Q) + M^3*Malt) (4) */
+ secp256k1_fe_half(&r->y); /* r->y = Y3 = -(Ralt*(2*X3 + Q) + M^3*Malt)/2 (3) */
/** In case a->infinity == 1, replace r with (b->x, b->y, 1). */
secp256k1_fe_cmov(&r->x, &b->x, a->infinity);
secp256k1_fe_cmov(&r->y, &b->y, a->infinity);
- secp256k1_fe_cmov(&r->z, &fe_1, a->infinity);
+ secp256k1_fe_cmov(&r->z, &secp256k1_fe_one, a->infinity);
r->infinity = infinity;
}
@@ -642,18 +633,22 @@ static void secp256k1_ge_from_storage(secp256k1_ge *r, const secp256k1_ge_storag
r->infinity = 0;
}
+static SECP256K1_INLINE void secp256k1_gej_cmov(secp256k1_gej *r, const secp256k1_gej *a, int flag) {
+ secp256k1_fe_cmov(&r->x, &a->x, flag);
+ secp256k1_fe_cmov(&r->y, &a->y, flag);
+ secp256k1_fe_cmov(&r->z, &a->z, flag);
+
+ r->infinity ^= (r->infinity ^ a->infinity) & flag;
+}
+
static SECP256K1_INLINE void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_ge_storage *a, int flag) {
secp256k1_fe_storage_cmov(&r->x, &a->x, flag);
secp256k1_fe_storage_cmov(&r->y, &a->y, flag);
}
static void secp256k1_ge_mul_lambda(secp256k1_ge *r, const secp256k1_ge *a) {
- static const secp256k1_fe beta = SECP256K1_FE_CONST(
- 0x7ae96a2bul, 0x657c0710ul, 0x6e64479eul, 0xac3434e9ul,
- 0x9cf04975ul, 0x12f58995ul, 0xc1396c28ul, 0x719501eeul
- );
*r = *a;
- secp256k1_fe_mul(&r->x, &r->x, &beta);
+ secp256k1_fe_mul(&r->x, &r->x, &secp256k1_const_beta);
}
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) {
diff --git a/src/secp256k1/src/hash.h b/src/secp256k1/src/hash.h
index 0947a09694..4e0384cfbf 100644
--- a/src/secp256k1/src/hash.h
+++ b/src/secp256k1/src/hash.h
@@ -12,8 +12,8 @@
typedef struct {
uint32_t s[8];
- uint32_t buf[16]; /* In big endian */
- size_t bytes;
+ unsigned char buf[64];
+ uint64_t bytes;
} secp256k1_sha256;
static void secp256k1_sha256_initialize(secp256k1_sha256 *hash);
diff --git a/src/secp256k1/src/hash_impl.h b/src/secp256k1/src/hash_impl.h
index f8cd3a1634..0991fe7838 100644
--- a/src/secp256k1/src/hash_impl.h
+++ b/src/secp256k1/src/hash_impl.h
@@ -28,12 +28,6 @@
(h) = t1 + t2; \
} while(0)
-#if defined(SECP256K1_BIG_ENDIAN)
-#define BE32(x) (x)
-#elif defined(SECP256K1_LITTLE_ENDIAN)
-#define BE32(p) ((((p) & 0xFF) << 24) | (((p) & 0xFF00) << 8) | (((p) & 0xFF0000) >> 8) | (((p) & 0xFF000000) >> 24))
-#endif
-
static void secp256k1_sha256_initialize(secp256k1_sha256 *hash) {
hash->s[0] = 0x6a09e667ul;
hash->s[1] = 0xbb67ae85ul;
@@ -47,26 +41,26 @@ static void secp256k1_sha256_initialize(secp256k1_sha256 *hash) {
}
/** Perform one SHA-256 transformation, processing 16 big endian 32-bit words. */
-static void secp256k1_sha256_transform(uint32_t* s, const uint32_t* chunk) {
+static void secp256k1_sha256_transform(uint32_t* s, const unsigned char* buf) {
uint32_t a = s[0], b = s[1], c = s[2], d = s[3], e = s[4], f = s[5], g = s[6], h = s[7];
uint32_t w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15;
- Round(a, b, c, d, e, f, g, h, 0x428a2f98, w0 = BE32(chunk[0]));
- Round(h, a, b, c, d, e, f, g, 0x71374491, w1 = BE32(chunk[1]));
- Round(g, h, a, b, c, d, e, f, 0xb5c0fbcf, w2 = BE32(chunk[2]));
- Round(f, g, h, a, b, c, d, e, 0xe9b5dba5, w3 = BE32(chunk[3]));
- Round(e, f, g, h, a, b, c, d, 0x3956c25b, w4 = BE32(chunk[4]));
- Round(d, e, f, g, h, a, b, c, 0x59f111f1, w5 = BE32(chunk[5]));
- Round(c, d, e, f, g, h, a, b, 0x923f82a4, w6 = BE32(chunk[6]));
- Round(b, c, d, e, f, g, h, a, 0xab1c5ed5, w7 = BE32(chunk[7]));
- Round(a, b, c, d, e, f, g, h, 0xd807aa98, w8 = BE32(chunk[8]));
- Round(h, a, b, c, d, e, f, g, 0x12835b01, w9 = BE32(chunk[9]));
- Round(g, h, a, b, c, d, e, f, 0x243185be, w10 = BE32(chunk[10]));
- Round(f, g, h, a, b, c, d, e, 0x550c7dc3, w11 = BE32(chunk[11]));
- Round(e, f, g, h, a, b, c, d, 0x72be5d74, w12 = BE32(chunk[12]));
- Round(d, e, f, g, h, a, b, c, 0x80deb1fe, w13 = BE32(chunk[13]));
- Round(c, d, e, f, g, h, a, b, 0x9bdc06a7, w14 = BE32(chunk[14]));
- Round(b, c, d, e, f, g, h, a, 0xc19bf174, w15 = BE32(chunk[15]));
+ Round(a, b, c, d, e, f, g, h, 0x428a2f98, w0 = secp256k1_read_be32(&buf[0]));
+ Round(h, a, b, c, d, e, f, g, 0x71374491, w1 = secp256k1_read_be32(&buf[4]));
+ Round(g, h, a, b, c, d, e, f, 0xb5c0fbcf, w2 = secp256k1_read_be32(&buf[8]));
+ Round(f, g, h, a, b, c, d, e, 0xe9b5dba5, w3 = secp256k1_read_be32(&buf[12]));
+ Round(e, f, g, h, a, b, c, d, 0x3956c25b, w4 = secp256k1_read_be32(&buf[16]));
+ Round(d, e, f, g, h, a, b, c, 0x59f111f1, w5 = secp256k1_read_be32(&buf[20]));
+ Round(c, d, e, f, g, h, a, b, 0x923f82a4, w6 = secp256k1_read_be32(&buf[24]));
+ Round(b, c, d, e, f, g, h, a, 0xab1c5ed5, w7 = secp256k1_read_be32(&buf[28]));
+ Round(a, b, c, d, e, f, g, h, 0xd807aa98, w8 = secp256k1_read_be32(&buf[32]));
+ Round(h, a, b, c, d, e, f, g, 0x12835b01, w9 = secp256k1_read_be32(&buf[36]));
+ Round(g, h, a, b, c, d, e, f, 0x243185be, w10 = secp256k1_read_be32(&buf[40]));
+ Round(f, g, h, a, b, c, d, e, 0x550c7dc3, w11 = secp256k1_read_be32(&buf[44]));
+ Round(e, f, g, h, a, b, c, d, 0x72be5d74, w12 = secp256k1_read_be32(&buf[48]));
+ Round(d, e, f, g, h, a, b, c, 0x80deb1fe, w13 = secp256k1_read_be32(&buf[52]));
+ Round(c, d, e, f, g, h, a, b, 0x9bdc06a7, w14 = secp256k1_read_be32(&buf[56]));
+ Round(b, c, d, e, f, g, h, a, 0xc19bf174, w15 = secp256k1_read_be32(&buf[60]));
Round(a, b, c, d, e, f, g, h, 0xe49b69c1, w0 += sigma1(w14) + w9 + sigma0(w1));
Round(h, a, b, c, d, e, f, g, 0xefbe4786, w1 += sigma1(w15) + w10 + sigma0(w2));
@@ -136,7 +130,7 @@ static void secp256k1_sha256_write(secp256k1_sha256 *hash, const unsigned char *
while (len >= 64 - bufsize) {
/* Fill the buffer, and process it. */
size_t chunk_len = 64 - bufsize;
- memcpy(((unsigned char*)hash->buf) + bufsize, data, chunk_len);
+ memcpy(hash->buf + bufsize, data, chunk_len);
data += chunk_len;
len -= chunk_len;
secp256k1_sha256_transform(hash->s, hash->buf);
@@ -149,19 +143,19 @@ static void secp256k1_sha256_write(secp256k1_sha256 *hash, const unsigned char *
}
static void secp256k1_sha256_finalize(secp256k1_sha256 *hash, unsigned char *out32) {
- static const unsigned char pad[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- uint32_t sizedesc[2];
- uint32_t out[8];
- int i = 0;
- sizedesc[0] = BE32(hash->bytes >> 29);
- sizedesc[1] = BE32(hash->bytes << 3);
+ static const unsigned char pad[64] = {0x80};
+ unsigned char sizedesc[8];
+ int i;
+ /* The maximum message size of SHA256 is 2^64-1 bits. */
+ VERIFY_CHECK(hash->bytes < ((uint64_t)1 << 61));
+ secp256k1_write_be32(&sizedesc[0], hash->bytes >> 29);
+ secp256k1_write_be32(&sizedesc[4], hash->bytes << 3);
secp256k1_sha256_write(hash, pad, 1 + ((119 - (hash->bytes % 64)) % 64));
- secp256k1_sha256_write(hash, (const unsigned char*)sizedesc, 8);
+ secp256k1_sha256_write(hash, sizedesc, 8);
for (i = 0; i < 8; i++) {
- out[i] = BE32(hash->s[i]);
+ secp256k1_write_be32(&out32[4*i], hash->s[i]);
hash->s[i] = 0;
}
- memcpy(out32, (const unsigned char*)out, 32);
}
/* Initializes a sha256 struct and writes the 64 byte string
@@ -285,7 +279,6 @@ static void secp256k1_rfc6979_hmac_sha256_finalize(secp256k1_rfc6979_hmac_sha256
rng->retry = 0;
}
-#undef BE32
#undef Round
#undef sigma1
#undef sigma0
diff --git a/src/secp256k1/src/modules/ecdh/tests_impl.h b/src/secp256k1/src/modules/ecdh/tests_impl.h
index be07447a4b..10b7075c38 100644
--- a/src/secp256k1/src/modules/ecdh/tests_impl.h
+++ b/src/secp256k1/src/modules/ecdh/tests_impl.h
@@ -60,7 +60,7 @@ void test_ecdh_generator_basepoint(void) {
s_one[31] = 1;
/* Check against pubkey creation when the basepoint is the generator */
- for (i = 0; i < 100; ++i) {
+ for (i = 0; i < 2 * count; ++i) {
secp256k1_sha256 sha;
unsigned char s_b32[32];
unsigned char output_ecdh[65];
@@ -123,10 +123,43 @@ void test_bad_scalar(void) {
CHECK(secp256k1_ecdh(ctx, output, &point, s_overflow, ecdh_hash_function_test_fail, NULL) == 0);
}
+/** Test that ECDH(sG, 1/s) == ECDH((1/s)G, s) == ECDH(G, 1) for a few random s. */
+void test_result_basepoint(void) {
+ secp256k1_pubkey point;
+ secp256k1_scalar rand;
+ unsigned char s[32];
+ unsigned char s_inv[32];
+ unsigned char out[32];
+ unsigned char out_inv[32];
+ unsigned char out_base[32];
+ int i;
+
+ unsigned char s_one[32] = { 0 };
+ s_one[31] = 1;
+ CHECK(secp256k1_ec_pubkey_create(ctx, &point, s_one) == 1);
+ CHECK(secp256k1_ecdh(ctx, out_base, &point, s_one, NULL, NULL) == 1);
+
+ for (i = 0; i < 2 * count; i++) {
+ random_scalar_order(&rand);
+ secp256k1_scalar_get_b32(s, &rand);
+ secp256k1_scalar_inverse(&rand, &rand);
+ secp256k1_scalar_get_b32(s_inv, &rand);
+
+ CHECK(secp256k1_ec_pubkey_create(ctx, &point, s) == 1);
+ CHECK(secp256k1_ecdh(ctx, out, &point, s_inv, NULL, NULL) == 1);
+ CHECK(secp256k1_memcmp_var(out, out_base, 32) == 0);
+
+ CHECK(secp256k1_ec_pubkey_create(ctx, &point, s_inv) == 1);
+ CHECK(secp256k1_ecdh(ctx, out_inv, &point, s, NULL, NULL) == 1);
+ CHECK(secp256k1_memcmp_var(out_inv, out_base, 32) == 0);
+ }
+}
+
void run_ecdh_tests(void) {
test_ecdh_api();
test_ecdh_generator_basepoint();
test_bad_scalar();
+ test_result_basepoint();
}
#endif /* SECP256K1_MODULE_ECDH_TESTS_H */
diff --git a/src/secp256k1/src/modules/schnorrsig/main_impl.h b/src/secp256k1/src/modules/schnorrsig/main_impl.h
index 94e3ee414d..cd651591c4 100644
--- a/src/secp256k1/src/modules/schnorrsig/main_impl.h
+++ b/src/secp256k1/src/modules/schnorrsig/main_impl.h
@@ -192,11 +192,15 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi
return ret;
}
-int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) {
+int secp256k1_schnorrsig_sign32(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) {
/* We cast away const from the passed aux_rand32 argument since we know the default nonce function does not modify it. */
return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg32, 32, keypair, secp256k1_nonce_function_bip340, (unsigned char*)aux_rand32);
}
+int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) {
+ return secp256k1_schnorrsig_sign32(ctx, sig64, msg32, keypair, aux_rand32);
+}
+
int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_keypair *keypair, secp256k1_schnorrsig_extraparams *extraparams) {
secp256k1_nonce_function_hardened noncefp = NULL;
void *ndata = NULL;
diff --git a/src/secp256k1/src/modules/schnorrsig/tests_impl.h b/src/secp256k1/src/modules/schnorrsig/tests_impl.h
index 2efec8a2b9..25840b8fa7 100644
--- a/src/secp256k1/src/modules/schnorrsig/tests_impl.h
+++ b/src/secp256k1/src/modules/schnorrsig/tests_impl.h
@@ -87,7 +87,7 @@ void run_nonce_function_bip340_tests(void) {
CHECK(nonce_function_bip340(nonce, msg, msglen, key, pk, NULL, 0, NULL) == 0);
CHECK(nonce_function_bip340(nonce, msg, msglen, key, pk, algo, algolen, NULL) == 1);
/* Other algo is fine */
- secp256k1_rfc6979_hmac_sha256_generate(&secp256k1_test_rng, algo, algolen);
+ secp256k1_testrand_bytes_test(algo, algolen);
CHECK(nonce_function_bip340(nonce, msg, msglen, key, pk, algo, algolen, NULL) == 1);
for (i = 0; i < count; i++) {
@@ -160,21 +160,21 @@ void test_schnorrsig_api(void) {
/** main test body **/
ecount = 0;
- CHECK(secp256k1_schnorrsig_sign(none, sig, msg, &keypairs[0], NULL) == 1);
+ CHECK(secp256k1_schnorrsig_sign32(none, sig, msg, &keypairs[0], NULL) == 1);
CHECK(ecount == 0);
- CHECK(secp256k1_schnorrsig_sign(vrfy, sig, msg, &keypairs[0], NULL) == 1);
+ CHECK(secp256k1_schnorrsig_sign32(vrfy, sig, msg, &keypairs[0], NULL) == 1);
CHECK(ecount == 0);
- CHECK(secp256k1_schnorrsig_sign(sign, sig, msg, &keypairs[0], NULL) == 1);
+ CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &keypairs[0], NULL) == 1);
CHECK(ecount == 0);
- CHECK(secp256k1_schnorrsig_sign(sign, NULL, msg, &keypairs[0], NULL) == 0);
+ CHECK(secp256k1_schnorrsig_sign32(sign, NULL, msg, &keypairs[0], NULL) == 0);
CHECK(ecount == 1);
- CHECK(secp256k1_schnorrsig_sign(sign, sig, NULL, &keypairs[0], NULL) == 0);
+ CHECK(secp256k1_schnorrsig_sign32(sign, sig, NULL, &keypairs[0], NULL) == 0);
CHECK(ecount == 2);
- CHECK(secp256k1_schnorrsig_sign(sign, sig, msg, NULL, NULL) == 0);
+ CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, NULL, NULL) == 0);
CHECK(ecount == 3);
- CHECK(secp256k1_schnorrsig_sign(sign, sig, msg, &invalid_keypair, NULL) == 0);
+ CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &invalid_keypair, NULL) == 0);
CHECK(ecount == 4);
- CHECK(secp256k1_schnorrsig_sign(sttc, sig, msg, &keypairs[0], NULL) == 0);
+ CHECK(secp256k1_schnorrsig_sign32(sttc, sig, msg, &keypairs[0], NULL) == 0);
CHECK(ecount == 5);
ecount = 0;
@@ -202,7 +202,7 @@ void test_schnorrsig_api(void) {
CHECK(ecount == 6);
ecount = 0;
- CHECK(secp256k1_schnorrsig_sign(sign, sig, msg, &keypairs[0], NULL) == 1);
+ CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &keypairs[0], NULL) == 1);
CHECK(secp256k1_schnorrsig_verify(none, sig, msg, sizeof(msg), &pk[0]) == 1);
CHECK(ecount == 0);
CHECK(secp256k1_schnorrsig_verify(sign, sig, msg, sizeof(msg), &pk[0]) == 1);
@@ -247,7 +247,7 @@ void test_schnorrsig_bip_vectors_check_signing(const unsigned char *sk, const un
secp256k1_xonly_pubkey pk, pk_expected;
CHECK(secp256k1_keypair_create(ctx, &keypair, sk));
- CHECK(secp256k1_schnorrsig_sign(ctx, sig, msg32, &keypair, aux_rand));
+ CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg32, &keypair, aux_rand));
CHECK(secp256k1_memcmp_var(sig, expected_sig, 64) == 0);
CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk_expected, pk_serialized));
@@ -740,8 +740,11 @@ void test_schnorrsig_sign(void) {
secp256k1_testrand256(aux_rand);
CHECK(secp256k1_keypair_create(ctx, &keypair, sk));
CHECK(secp256k1_keypair_xonly_pub(ctx, &pk, NULL, &keypair));
- CHECK(secp256k1_schnorrsig_sign(ctx, sig, msg, &keypair, NULL) == 1);
+ CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL) == 1);
CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &pk));
+ /* Check that deprecated alias gives the same result */
+ CHECK(secp256k1_schnorrsig_sign(ctx, sig2, msg, &keypair, NULL) == 1);
+ CHECK(secp256k1_memcmp_var(sig, sig2, sizeof(sig)) == 0);
/* Test different nonce functions */
CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 1);
@@ -764,7 +767,7 @@ void test_schnorrsig_sign(void) {
extraparams.noncefp = NULL;
extraparams.ndata = aux_rand;
CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 1);
- CHECK(secp256k1_schnorrsig_sign(ctx, sig2, msg, &keypair, extraparams.ndata) == 1);
+ CHECK(secp256k1_schnorrsig_sign32(ctx, sig2, msg, &keypair, extraparams.ndata) == 1);
CHECK(secp256k1_memcmp_var(sig, sig2, sizeof(sig)) == 0);
}
@@ -787,7 +790,7 @@ void test_schnorrsig_sign_verify(void) {
for (i = 0; i < N_SIGS; i++) {
secp256k1_testrand256(msg[i]);
- CHECK(secp256k1_schnorrsig_sign(ctx, sig[i], msg[i], &keypair, NULL));
+ CHECK(secp256k1_schnorrsig_sign32(ctx, sig[i], msg[i], &keypair, NULL));
CHECK(secp256k1_schnorrsig_verify(ctx, sig[i], msg[i], sizeof(msg[i]), &pk));
}
@@ -795,18 +798,18 @@ void test_schnorrsig_sign_verify(void) {
/* Flip a few bits in the signature and in the message and check that
* verify and verify_batch (TODO) fail */
size_t sig_idx = secp256k1_testrand_int(N_SIGS);
- size_t byte_idx = secp256k1_testrand_int(32);
+ size_t byte_idx = secp256k1_testrand_bits(5);
unsigned char xorbyte = secp256k1_testrand_int(254)+1;
sig[sig_idx][byte_idx] ^= xorbyte;
CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk));
sig[sig_idx][byte_idx] ^= xorbyte;
- byte_idx = secp256k1_testrand_int(32);
+ byte_idx = secp256k1_testrand_bits(5);
sig[sig_idx][32+byte_idx] ^= xorbyte;
CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk));
sig[sig_idx][32+byte_idx] ^= xorbyte;
- byte_idx = secp256k1_testrand_int(32);
+ byte_idx = secp256k1_testrand_bits(5);
msg[sig_idx][byte_idx] ^= xorbyte;
CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk));
msg[sig_idx][byte_idx] ^= xorbyte;
@@ -816,13 +819,13 @@ void test_schnorrsig_sign_verify(void) {
}
/* Test overflowing s */
- CHECK(secp256k1_schnorrsig_sign(ctx, sig[0], msg[0], &keypair, NULL));
+ CHECK(secp256k1_schnorrsig_sign32(ctx, sig[0], msg[0], &keypair, NULL));
CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk));
memset(&sig[0][32], 0xFF, 32);
CHECK(!secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk));
/* Test negative s */
- CHECK(secp256k1_schnorrsig_sign(ctx, sig[0], msg[0], &keypair, NULL));
+ CHECK(secp256k1_schnorrsig_sign32(ctx, sig[0], msg[0], &keypair, NULL));
CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk));
secp256k1_scalar_set_b32(&s, &sig[0][32], NULL);
secp256k1_scalar_negate(&s, &s);
@@ -873,7 +876,7 @@ void test_schnorrsig_taproot(void) {
/* Key spend */
secp256k1_testrand256(msg);
- CHECK(secp256k1_schnorrsig_sign(ctx, sig, msg, &keypair, NULL) == 1);
+ CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL) == 1);
/* Verify key spend */
CHECK(secp256k1_xonly_pubkey_parse(ctx, &output_pk, output_pk_bytes) == 1);
CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &output_pk) == 1);
diff --git a/src/secp256k1/src/precompute_ecmult.c b/src/secp256k1/src/precompute_ecmult.c
new file mode 100644
index 0000000000..5ccbcb3c57
--- /dev/null
+++ b/src/secp256k1/src/precompute_ecmult.c
@@ -0,0 +1,96 @@
+/*****************************************************************************************************
+ * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor *
+ * Distributed under the MIT software license, see the accompanying *
+ * file COPYING or https://www.opensource.org/licenses/mit-license.php. *
+ *****************************************************************************************************/
+
+#include <inttypes.h>
+#include <stdio.h>
+
+/* Autotools creates libsecp256k1-config.h, of which ECMULT_WINDOW_SIZE is needed.
+ ifndef guard so downstream users can define their own if they do not use autotools. */
+#if !defined(ECMULT_WINDOW_SIZE)
+#include "libsecp256k1-config.h"
+#endif
+
+#include "../include/secp256k1.h"
+#include "assumptions.h"
+#include "util.h"
+#include "field_impl.h"
+#include "group_impl.h"
+#include "ecmult.h"
+#include "ecmult_compute_table_impl.h"
+
+static void print_table(FILE *fp, const char *name, int window_g, const secp256k1_ge_storage* table) {
+ int j;
+ int i;
+
+ fprintf(fp, "const secp256k1_ge_storage %s[ECMULT_TABLE_SIZE(WINDOW_G)] = {\n", name);
+ fprintf(fp, " S(%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32
+ ",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32")\n",
+ SECP256K1_GE_STORAGE_CONST_GET(table[0]));
+
+ j = 1;
+ for(i = 3; i <= window_g; ++i) {
+ fprintf(fp, "#if WINDOW_G > %d\n", i-1);
+ for(;j < ECMULT_TABLE_SIZE(i); ++j) {
+ fprintf(fp, ",S(%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32
+ ",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32")\n",
+ SECP256K1_GE_STORAGE_CONST_GET(table[j]));
+ }
+ fprintf(fp, "#endif\n");
+ }
+ fprintf(fp, "};\n");
+}
+
+static void print_two_tables(FILE *fp, int window_g) {
+ secp256k1_ge_storage* table = malloc(ECMULT_TABLE_SIZE(window_g) * sizeof(secp256k1_ge_storage));
+ secp256k1_ge_storage* table_128 = malloc(ECMULT_TABLE_SIZE(window_g) * sizeof(secp256k1_ge_storage));
+
+ secp256k1_ecmult_compute_two_tables(table, table_128, window_g, &secp256k1_ge_const_g);
+
+ print_table(fp, "secp256k1_pre_g", window_g, table);
+ print_table(fp, "secp256k1_pre_g_128", window_g, table_128);
+
+ free(table);
+ free(table_128);
+}
+
+int main(void) {
+ /* Always compute all tables for window sizes up to 15. */
+ int window_g = (ECMULT_WINDOW_SIZE < 15) ? 15 : ECMULT_WINDOW_SIZE;
+ FILE* fp;
+
+ fp = fopen("src/precomputed_ecmult.c","w");
+ if (fp == NULL) {
+ fprintf(stderr, "Could not open src/precomputed_ecmult.h for writing!\n");
+ return -1;
+ }
+
+ fprintf(fp, "/* This file was automatically generated by precompute_ecmult. */\n");
+ fprintf(fp, "/* This file contains an array secp256k1_pre_g with odd multiples of the base point G and\n");
+ fprintf(fp, " * an array secp256k1_pre_g_128 with odd multiples of 2^128*G for accelerating the computation of a*P + b*G.\n");
+ fprintf(fp, " */\n");
+ fprintf(fp, "#if defined HAVE_CONFIG_H\n");
+ fprintf(fp, "# include \"libsecp256k1-config.h\"\n");
+ fprintf(fp, "#endif\n");
+ fprintf(fp, "#include \"../include/secp256k1.h\"\n");
+ fprintf(fp, "#include \"group.h\"\n");
+ fprintf(fp, "#include \"ecmult.h\"\n");
+ fprintf(fp, "#include \"precomputed_ecmult.h\"\n");
+ fprintf(fp, "#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)\n");
+ fprintf(fp, "#if ECMULT_WINDOW_SIZE > %d\n", window_g);
+ fprintf(fp, " #error configuration mismatch, invalid ECMULT_WINDOW_SIZE. Try deleting precomputed_ecmult.c before the build.\n");
+ fprintf(fp, "#endif\n");
+ fprintf(fp, "#ifdef EXHAUSTIVE_TEST_ORDER\n");
+ fprintf(fp, "# error Cannot compile precomputed_ecmult.c in exhaustive test mode\n");
+ fprintf(fp, "#endif /* EXHAUSTIVE_TEST_ORDER */\n");
+ fprintf(fp, "#define WINDOW_G ECMULT_WINDOW_SIZE\n");
+
+ print_two_tables(fp, window_g);
+
+ fprintf(fp, "#undef S\n");
+ fclose(fp);
+
+ return 0;
+}
diff --git a/src/secp256k1/src/gen_ecmult_gen_static_prec_table.c b/src/secp256k1/src/precompute_ecmult_gen.c
index 22923df313..7c6359c402 100644
--- a/src/secp256k1/src/gen_ecmult_gen_static_prec_table.c
+++ b/src/secp256k1/src/precompute_ecmult_gen.c
@@ -1,8 +1,8 @@
-/***********************************************************************
- * Copyright (c) 2013, 2014, 2015 Thomas Daede, Cory Fields *
- * Distributed under the MIT software license, see the accompanying *
- * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
- ***********************************************************************/
+/*********************************************************************************
+ * Copyright (c) 2013, 2014, 2015, 2021 Thomas Daede, Cory Fields, Pieter Wuille *
+ * Distributed under the MIT software license, see the accompanying *
+ * file COPYING or https://www.opensource.org/licenses/mit-license.php. *
+ *********************************************************************************/
#include <inttypes.h>
#include <stdio.h>
@@ -12,10 +12,10 @@
#include "util.h"
#include "group.h"
#include "ecmult_gen.h"
-#include "ecmult_gen_prec_impl.h"
+#include "ecmult_gen_compute_table_impl.h"
int main(int argc, char **argv) {
- const char outfile[] = "src/ecmult_gen_static_prec_table.h";
+ const char outfile[] = "src/precomputed_ecmult_gen.c";
FILE* fp;
int bits;
@@ -28,21 +28,20 @@ int main(int argc, char **argv) {
return -1;
}
- fprintf(fp, "/* This file was automatically generated by gen_ecmult_gen_static_prec_table. */\n");
+ fprintf(fp, "/* This file was automatically generated by precompute_ecmult_gen. */\n");
fprintf(fp, "/* See ecmult_gen_impl.h for details about the contents of this file. */\n");
- fprintf(fp, "#ifndef SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H\n");
- fprintf(fp, "#define SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H\n");
-
+ fprintf(fp, "#if defined HAVE_CONFIG_H\n");
+ fprintf(fp, "# include \"libsecp256k1-config.h\"\n");
+ fprintf(fp, "#endif\n");
+ fprintf(fp, "#include \"../include/secp256k1.h\"\n");
fprintf(fp, "#include \"group.h\"\n");
-
- fprintf(fp, "#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) "
- "SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,"
- "0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)\n");
-
+ fprintf(fp, "#include \"ecmult_gen.h\"\n");
+ fprintf(fp, "#include \"precomputed_ecmult_gen.h\"\n");
fprintf(fp, "#ifdef EXHAUSTIVE_TEST_ORDER\n");
- fprintf(fp, "static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)];\n");
- fprintf(fp, "#else\n");
- fprintf(fp, "static const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)] = {\n");
+ fprintf(fp, "# error Cannot compile precomputed_ecmult_gen.c in exhaustive test mode\n");
+ fprintf(fp, "#endif /* EXHAUSTIVE_TEST_ORDER */\n");
+ fprintf(fp, "#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)\n");
+ fprintf(fp, "const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)] = {\n");
for (bits = 2; bits <= 8; bits *= 2) {
int g = ECMULT_GEN_PREC_G(bits);
@@ -50,7 +49,7 @@ int main(int argc, char **argv) {
int inner, outer;
secp256k1_ge_storage* table = checked_malloc(&default_error_callback, n * g * sizeof(secp256k1_ge_storage));
- secp256k1_ecmult_gen_create_prec_table(table, &secp256k1_ge_const_g, bits);
+ secp256k1_ecmult_gen_compute_table(table, &secp256k1_ge_const_g, bits);
fprintf(fp, "#if ECMULT_GEN_PREC_BITS == %d\n", bits);
for(outer = 0; outer != n; outer++) {
@@ -74,9 +73,7 @@ int main(int argc, char **argv) {
}
fprintf(fp, "};\n");
- fprintf(fp, "#endif /* EXHAUSTIVE_TEST_ORDER */\n");
- fprintf(fp, "#undef SC\n");
- fprintf(fp, "#endif /* SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H */\n");
+ fprintf(fp, "#undef S\n");
fclose(fp);
return 0;
diff --git a/src/secp256k1/src/ecmult_static_pre_g.h b/src/secp256k1/src/precomputed_ecmult.c
index 9072fb2688..3e67f37b74 100644
--- a/src/secp256k1/src/ecmult_static_pre_g.h
+++ b/src/secp256k1/src/precomputed_ecmult.c
@@ -1,187 +1,38 @@
-/* This file was automatically generated by gen_ecmult_static_pre_g. */
+/* This file was automatically generated by precompute_ecmult. */
/* This file contains an array secp256k1_pre_g with odd multiples of the base point G and
* an array secp256k1_pre_g_128 with odd multiples of 2^128*G for accelerating the computation of a*P + b*G.
*/
-#ifndef SECP256K1_ECMULT_STATIC_PRE_G_H
-#define SECP256K1_ECMULT_STATIC_PRE_G_H
-#include "group.h"
-#ifdef S
- #error macro identifier S already in use.
+#if defined HAVE_CONFIG_H
+# include "libsecp256k1-config.h"
#endif
+#include "../include/secp256k1.h"
+#include "group.h"
+#include "ecmult.h"
+#include "precomputed_ecmult.h"
#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)
-#if ECMULT_TABLE_SIZE(ECMULT_WINDOW_SIZE) > 8192
- #error configuration mismatch, invalid ECMULT_WINDOW_SIZE. Try deleting ecmult_static_pre_g.h before the build.
+#if ECMULT_WINDOW_SIZE > 15
+ #error configuration mismatch, invalid ECMULT_WINDOW_SIZE. Try deleting precomputed_ecmult.c before the build.
#endif
-#if defined(EXHAUSTIVE_TEST_ORDER)
-#if EXHAUSTIVE_TEST_ORDER == 13
-#define WINDOW_G 4
-static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = {
- S(c3459c3d,35326167,cd86cce8,7a2417f,5b8bd567,de8538ee,d507b0c,d128f5bb,8e467fec,cd30000a,6cc1184e,25d382c2,a2f4494e,2fbe9abc,8b64abac,d005fb24)
-,S(ae64a1bd,38872f22,f637b457,125cc859,e4c7a31b,cf553cf5,b96e7096,cc61cc10,8e467fec,cd30000a,6cc1184e,25d382c2,a2f4494e,2fbe9abc,8b64abac,d005fb24)
-,S(851695d4,9a83f8ef,919bb861,53cbcb16,630fb68a,ed0a766a,3ec693d6,8e6afa40,3c915051,a5fb60b4,fec49de6,e4385101,59f30035,b6502bc0,f5edba86,9753a96c)
-,S(0,0,0,0,0,0,0,1,c36eafae,5a049f4b,13b6219,1bc7aefe,a60cffca,49afd43f,a124578,68ac52c3)
-};
-static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)] = {
- S(8e55c205,92466f75,3c417ec0,e600f626,bfac877c,52258a1c,3941145a,62753693,8e467fec,cd30000a,6cc1184e,25d382c2,a2f4494e,2fbe9abc,8b64abac,d005fb24)
-,S(c3459c3d,35326167,cd86cce8,7a2417f,5b8bd567,de8538ee,d507b0c,d128f5bb,8e467fec,cd30000a,6cc1184e,25d382c2,a2f4494e,2fbe9abc,8b64abac,d005fb24)
-,S(0,0,0,0,0,0,0,1,3c915051,a5fb60b4,fec49de6,e4385101,59f30035,b6502bc0,f5edba86,9753a96c)
-,S(7ae96a2b,657c0710,6e64479e,ac3434e9,9cf04975,12f58995,c1396c28,719501ee,c36eafae,5a049f4b,13b6219,1bc7aefe,a60cffca,49afd43f,a124578,68ac52c3)
-};
-#elif EXHAUSTIVE_TEST_ORDER == 199
-#define WINDOW_G 8
-static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = {
- S(226e653f,c8df7744,9bacbf12,7d1dcbf9,87f05b2a,e7edbd28,1f564575,c48dcf18,a13872c2,e933bb17,5d9ffd5b,b5b6e10c,57fe3c00,baaaa15a,e003ec3e,9c269bae)
-,S(ff1755e,623c8369,f55edda4,2a5deef0,b32c57f4,80c5884f,d2a2dde1,b1c078c4,640db9f3,9dda2f51,ee3ef3db,775315aa,c06346f1,e31ff76f,83a24bb0,8fc93242)
-,S(64dd1439,5d19a544,a7a1e81b,b9d079b3,593e7022,6bcd444e,6dc8197a,1a6dc3e6,2c7f2dce,e421d852,d3bff68e,993c8bb4,c189d3be,bd4fa667,6a599f9f,8c639c50)
-,S(199d1eb1,5a28f0aa,7258651b,ff07ff13,1c988fa,dc41dc67,390c3172,54b98016,d670e3f1,6fc2382,46bf323f,127e7c14,576e3a64,5d5c41da,40f22b29,4fca876c)
-,S(3d303d12,f8eb67d0,ecaee514,bdd90e57,b58b6d6a,c896a26f,78c06103,52225b04,d282f481,b04bece2,60de6995,b58c4f0d,9c6121e0,d94f45da,f5da7f13,cfefef99)
-,S(76bb4d25,c7a005dc,49a5295f,bb92bf1e,5d0dd5f6,609c2008,54f37361,23a6f9f3,e2295c71,21478f52,d4d18aa,72cd82ae,1995d37e,6ef2e05a,4dea6ee2,6371fbe3)
-,S(8735fb32,6950ae71,31cd03f7,cc1511d8,65c87e84,748c9824,8ccc576e,5480ed8,39cfeb15,665f371f,535d56c1,317a8d62,29ea2827,943a98f1,cee7f685,86e47eec)
-,S(8521a020,a8e7cc5e,d9534d74,d4656581,33869bc,1940548d,6cc35ba4,e3c4323a,c63014ea,99a0c8e0,aca2a93e,ce85729d,d615d7d8,6bc5670e,31180979,791b7d43)
-,S(2f63a396,4d14ca73,2167e033,306863d8,f75605c7,4ea6c917,890dd50d,fc20f53b,e2295c71,21478f52,d4d18aa,72cd82ae,1995d37e,6ef2e05a,4dea6ee2,6371fbe3)
-,S(c479fee8,e2202b42,1c9346fb,4e59f720,95a70dea,2ade9641,58bad01c,2c37f,6897db9d,5a3d8948,ea97069e,de75661,b1ad74e4,2b1daa4f,533d88ba,67137731)
-,S(4d756b2,e61d9806,e02ec830,f1fd44f8,46438689,c528346,15a1addf,ac864bcd,c90704c9,8f72d8be,638a36dc,e8768909,fac7921c,dc54d314,89fbb3e,6da9018a)
-,S(baeab1a8,bca6e88,740deeb5,93887afe,295155ba,fa50f307,baf2de95,2841dca6,a88e0162,de086e20,587faa5c,bca8f572,318c17e9,5668819c,13c4d5d9,2d1c75dc)
-,S(47bbafcd,f612ca3e,703f7721,623bde14,926fc00d,a6bf9e9,41ddb2d5,587626e3,d160b834,5807531a,327a7269,3627dab6,f9770a12,14eb182d,fa73ad15,41b57ff0)
-,S(f9d5cda0,a41aea0e,f0afc533,e1b50808,3acc6043,e04e55bb,77421ab,1cebb8ec,f8a4cc8e,538e1b7f,627c6713,7c896eb1,c5548159,f434695a,ea394c63,592d41cf)
-,S(26e2e75b,aeb04a72,8b893f67,d27ebfc6,b8984a88,6fbd8eee,5719107d,5a413f0f,61b51308,ac8643f0,7c18eda7,ef9a4196,aa66221,1b809acc,2b2cb538,23010ef8)
-,S(318ef624,4d411c97,913a7bb3,8f07b960,e6c57afa,f1edc5bf,e708da8a,954f68ac,37d97203,d25d9c14,53827e27,9474afb2,92943df0,8ba1108a,43644c86,ee6c87e4)
-,S(eac612b0,84cb452a,35d49c60,c0d0c0bd,359eb909,68a80186,92a028f,ece4c331,23575727,9da3d12b,c0004d12,e06a9cda,8405c91e,b8118260,2793e62f,16c2a29e)
-,S(9f5ed380,fa5188a1,cb1c84df,cb7e2aad,939cdfde,581d765b,95c1c25,43016afb,423923ad,75aa7eb1,259434a7,1d58c469,f6268a38,5cd8fb7c,4be64d63,fad9bcea)
-,S(ee534645,42ff07e2,8cf783ee,1da96e00,b81f531,9b9a4885,27f92302,acd12cac,c90704c9,8f72d8be,638a36dc,e8768909,fac7921c,dc54d314,89fbb3e,6da9018a)
-,S(1130e02f,29480660,b2cb4f68,81da3f2c,1bdaf64,8df3b6ce,1a333738,29e4165e,ef2b477b,a9c921df,5ff65b6e,7c6f96a8,627743ff,a53e3858,cd9d7673,f7435ac4)
-,S(e8d3f45a,b5be455f,dc3c2270,34100ca2,b8d203e1,35a9ed3d,5c8b703c,ef4d9b48,2d7d0b7e,4fb4131d,9f21966a,4a73b0f2,639ede1f,26b0ba25,a2580eb,30100c96)
-,S(eb5c4120,4917d176,6fea883b,e680dfd9,233d9654,97fac48b,76f5d0d4,7f6dbf73,97682462,a5c276b7,1568f961,f218a99e,4e528b1b,d4e255b0,acc27744,98ec84fe)
-,S(4097892c,5478baea,62e8da20,6d17fcd8,b8eed45f,24fb7648,fe742508,8da60a4e,10d4b884,5636de20,a009a491,83906957,9d88bc00,5ac1c7a7,3262898b,8bca16b)
-,S(9963e528,754cbba8,29622664,1aec11b0,3fba2739,40f61cbd,5d4c0478,5429f40,d670e3f1,6fc2382,46bf323f,127e7c14,576e3a64,5d5c41da,40f22b29,4fca876c)
-,S(853cffd4,4cb0a29e,390ad886,455566cc,2776ab74,55210e57,9bf1fcc0,c8d767ed,621fdd8d,dd929f3f,681a6f83,bf8061f8,2b4396a6,5df86be6,ec656a86,b1b730b0)
-,S(7fe309bb,e3e8edd3,3f5f94b,4247256e,41e62e69,fd9f2063,d878c6d6,3d85675f,23575727,9da3d12b,c0004d12,e06a9cda,8405c91e,b8118260,2793e62f,16c2a29e)
-,S(851695d4,9a83f8ef,919bb861,53cbcb16,630fb68a,ed0a766a,3ec693d6,8e6afa40,cb47a6a4,91313828,f036dbd7,64173a40,473d3969,5cc47758,511d0a81,badb02f1)
-,S(f31a7dfe,4d2e4a98,aaf3dd70,25441c7a,2da056b0,db6795e8,3a39d76f,b316bd92,17e98bbd,6e144db,959b7aea,cd766958,7be26db5,445ea19d,dab8bc90,1f527cb4)
-,S(2266b5e,d5cef55,81e36315,c5fc5c53,a1c73b12,c24c8de7,9afef2a0,7a50212c,7a60fc27,e4ab0ba6,ea71c036,52b83c5c,5ab5dac8,ea43dec2,84719f8c,6d7c3c81)
-,S(e0089ec9,4b25ac6,ff44b5da,2dfedb37,9fdaedf1,6bc8cecd,b1a95fa0,32431c73,17e98bbd,6e144db,959b7aea,cd766958,7be26db5,445ea19d,dab8bc90,1f527cb4)
-,S(896c7bc,5cfeaecc,37b111ea,2deee270,c7cb24eb,6eb1cb80,a84e87d3,89d997c1,66446d0d,b01a679b,59afb34a,2bbf8cfe,f0a44870,f0e074ce,78a51a36,9bbbf33c)
-,S(709c8403,ed2fd0f9,5fac44f0,dfa96ec2,e7b357e4,4ebb3dcf,6f688f20,39d3d67d,d160b834,5807531a,327a7269,3627dab6,f9770a12,14eb182d,fa73ad15,41b57ff0)
-,S(142f854d,ba0b87a2,9826f018,86ab51ff,b9f19214,c23c07c0,f008a375,a1effdb6,74bafd32,bdf2bdfd,c65a52c3,5ef4f4d,9332f7b2,a0512a9f,f821f858,484bb9c)
-,S(66abbf18,f58dd1c9,4b6072c,eb182abe,c31d3af9,6d91060b,d233f462,5716f289,d380d231,1bde27ad,2c400971,66c3744b,3e762c41,42b05998,95a6605f,739c5fdf)
-,S(7a1b727f,c05c6de2,79b2e027,108fae3e,87ca9249,eb44207f,286e1cbe,4728027,9de02272,226d60c0,97e5907c,407f9e07,d4bc6959,a2079419,139a9578,4e48cb7f)
-,S(3d98fe85,5d42f661,e35d8ce3,cfee5f17,57bc3de9,ecec02fa,11a2d90,378ac0e8,bdc6dc52,8a55814e,da6bcb58,e2a73b96,9d975c7,a3270483,b419b29b,5263f45)
-,S(34f8450b,18bffd3f,cbadcd08,5da5411b,ba2ef195,55753768,bf777307,3a2d4cb4,5771fe9d,21f791df,a78055a3,43570a8d,ce73e816,a9977e63,ec3b2a25,d2e38653)
-,S(76dac07e,bc240289,791e6a9a,56abdc7,bdf2fac1,e7a0100b,5a0ee09e,3455cc33,8b4502cd,420d4202,39a5ad3c,fa10b0b2,6ccd084d,5faed560,7de07a6,fb7b4093)
-,S(929effed,d3b91ccb,a985ab23,bef45c16,6d5ffaa3,22562529,b84d051c,f94044a1,640db9f3,9dda2f51,ee3ef3db,775315aa,c06346f1,e31ff76f,83a24bb0,8fc93242)
-,S(1ea4ff3b,5a0a78a9,fda9d73a,ea5f0629,886b083a,6d3beea4,d6bc96e1,b50da944,9edf1300,7f2b7fb2,92301e8c,77ddb63e,5f2d23c4,315118c1,8b1b6273,86bba11a)
-,S(86fef7a7,b4d3b079,2c83a223,d1c929d6,6ca15202,f4b956a1,cf7ed2cb,83dba7c4,61b51308,ac8643f0,7c18eda7,ef9a4196,aa66221,1b809acc,2b2cb538,23010ef8)
-,S(c979de1c,1318f60f,a9929ff3,a6621b9,d15226e8,2883556e,36a5f77b,28e3b585,d7bd974a,b40a1bd3,a35a1ee9,16d32768,48e7bcfc,be70851d,8ba758b9,996d2099)
-,S(2cdce338,ae1f5aa0,55c76cb5,acbd084e,3284bb5d,b8cf9b4a,141cc8ee,1aa61e59,17e98bbd,6e144db,959b7aea,cd766958,7be26db5,445ea19d,dab8bc90,1f527cb4)
-,S(80c5672b,3f773975,b5829101,6fa9de9b,33863263,8a978db,934f04c1,3a909b85,55ebf47c,6835f232,5ea232b3,8af64478,65cbdce1,2f2c5a79,abc445fd,518ab677)
-,S(521e20fc,9c7c0514,47f31e74,5bb81662,dac66374,9b891a6f,d9681cb6,21e3155c,61b51308,ac8643f0,7c18eda7,ef9a4196,aa66221,1b809acc,2b2cb538,23010ef8)
-,S(b084fee5,5da62ee0,ebdebf3b,245e0d4e,def46aac,bfdc9f43,261d3e2b,eed946a0,80f3b79c,ba5f8457,3f2aa473,1d10c1f,9b6543e1,b8cec5e0,e89cd6df,868cc2aa)
-,S(d8a3fd95,b7ed8e8a,674da307,94911393,303917c2,60944d1f,76261274,fba45757,5ec78d3d,16cc44e8,a26002a4,4a491ef3,a801c3ff,45555ea5,1ffc13c0,63d96081)
-,S(c2d4b350,75ee5eb,3e2c8d3c,f33649dc,dd4f4c29,728bbdae,3f3ff02d,f3dc7415,6120ecff,80d4804d,6dcfe173,882249c1,a0d2dc3b,ceaee73e,74e49d8b,79445b15)
-,S(9fa3c7bd,6b8544f2,df48309,596ea267,8f8537e1,dc406ec7,cf2c67d1,ae5f1e8a,c8268dfc,2da263eb,ac7d81d8,6b8b504d,6d6bc20f,745eef75,bc9bb378,1193744b)
-,S(0,0,0,0,0,0,0,1,34b8595b,6ecec7d7,fc92428,9be8c5bf,b8c2c696,a33b88a7,aee2f57d,4524f93e)
-,S(969360d2,54e2dba9,e79d0789,c339250d,c438330a,a66addf9,f27e2ee8,fc9036ac,99bb92f2,4fe59864,a6504cb5,d4407301,f5bb78f,f1f8b31,875ae5c8,644408f3)
-,S(318f2c1f,3dc109ad,ed9d8958,e2e6eb8c,e26eec32,f2725ad1,68e116c7,497e7044,75b3371,ac71e480,9d8398ec,8376914e,3aab7ea6,bcb96a5,15c6b39b,a6d2ba60)
-,S(bcea96e9,4ff1b11e,6e8a09d6,ec594b00,597e2b0a,37b0e581,50a58d59,33220419,7f0c4863,45a07ba8,c0d55b8c,fe2ef3e0,649abc1e,47313a1f,1763291f,79733985)
-,S(4ed9d2a,7f32fa30,fd059de5,ee512073,47d68d12,b77df5b8,6a83a814,3fcdd5c0,5ec78d3d,16cc44e8,a26002a4,4a491ef3,a801c3ff,45555ea5,1ffc13c0,63d96081)
-,S(875c93a1,8ac5a86e,9e85e4aa,3c5ad0ac,3d697192,6384b05b,568604b3,8fee8fb6,3738dc95,97add4e5,c0a39af1,b98ccba2,7bb43333,e35ab481,54c52779,9191144b)
-,S(47a7cc2e,1cbd64c8,301443ed,be1ab328,85dce80e,a6d8c847,4eb9be09,6db5fecf,d160b834,5807531a,327a7269,3627dab6,f9770a12,14eb182d,fa73ad15,41b57ff0)
-,S(3e8a6608,17f28243,f363d67e,b874856f,1fc9810a,9da6c3f3,a72dd23f,ec45513e,8a8d4c8f,37d2572,6ca66369,59c9eca3,82bd076e,867092a0,b428ad99,2c54a63b)
-,S(9556e393,974bcd02,c6356a53,fce819d4,887b188c,99b8de16,1e5d3697,d595cdce,23575727,9da3d12b,c0004d12,e06a9cda,8405c91e,b8118260,2793e62f,16c2a29e)
-,S(5245e215,52c6f785,2cccef32,e9f54774,cfb6bb9b,c363ae72,71174df4,6d77fdcc,ca78f1f0,894a6670,19609169,78c251dc,64f969e,c25be4b4,202508d3,22d07f3b)
-,S(5d6f8ab3,ca0a5fca,611b7738,16adb4f8,df73ad68,5ce45286,75101d00,54ff3eca,640db9f3,9dda2f51,ee3ef3db,775315aa,c06346f1,e31ff76f,83a24bb0,8fc93242)
-,S(a4465b18,b7f702e2,a8165184,834ce490,68fb0a51,6658734b,6229eda0,605911a2,c8c7236a,68522b1a,3f5c650e,4673345d,844bcccc,1ca54b7e,ab3ad885,6e6ee7e4)
-,S(d49b0640,1e240c43,21b2b173,3b640c6a,e2c4b389,2d3f4f73,8faac78b,9995cf2e,75b3371,ac71e480,9d8398ec,8376914e,3aab7ea6,bcb96a5,15c6b39b,a6d2ba60)
-,S(599db82f,8229ad47,7a662726,d2d351a6,f76cefd7,8fc376b1,ef6e344e,4e4dae3a,284268b5,4bf5e42c,5ca5e116,e92cd897,b7184303,418f7ae2,7458a745,6692db96)
-,S(a0627644,ebc6a423,1f3e113b,eedbf9d8,21d9374a,af2e7c55,14ee7395,aa9992b7,859f03d8,1b54f459,158e3fc9,ad47c3a3,a54a2537,15bc213d,7b8e6072,9283bfae)
-};
-static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)] = {
- S(ec823227,1d0e875a,8e1cf7e6,f9a35a15,749be650,1cc885c4,f383060b,46bd3d33,aa140b83,97ca0dcd,a15dcd4c,7509bb87,9a34231e,d0d3a586,543bba01,ae7545b8)
-,S(66abbf18,f58dd1c9,4b6072c,eb182abe,c31d3af9,6d91060b,d233f462,5716f289,d380d231,1bde27ad,2c400971,66c3744b,3e762c41,42b05998,95a6605f,739c5fdf)
-,S(4d756b2,e61d9806,e02ec830,f1fd44f8,46438689,c528346,15a1addf,ac864bcd,36f8fb36,708d2741,9c75c923,178976f6,5386de3,23ab2ceb,f76044c0,9256faa5)
-,S(875c93a1,8ac5a86e,9e85e4aa,3c5ad0ac,3d697192,6384b05b,568604b3,8fee8fb6,c8c7236a,68522b1a,3f5c650e,4673345d,844bcccc,1ca54b7e,ab3ad885,6e6ee7e4)
-,S(f3a864ac,edc7852f,f4dfae93,5f8588a6,96ff17bf,7233134e,6704ceb,16f3b74c,39cfeb15,665f371f,535d56c1,317a8d62,29ea2827,943a98f1,cee7f685,86e47eec)
-,S(3e8a6608,17f28243,f363d67e,b874856f,1fc9810a,9da6c3f3,a72dd23f,ec45513e,8a8d4c8f,37d2572,6ca66369,59c9eca3,82bd076e,867092a0,b428ad99,2c54a63b)
-,S(47bbafcd,f612ca3e,703f7721,623bde14,926fc00d,a6bf9e9,41ddb2d5,587626e3,d160b834,5807531a,327a7269,3627dab6,f9770a12,14eb182d,fa73ad15,41b57ff0)
-,S(709c8403,ed2fd0f9,5fac44f0,dfa96ec2,e7b357e4,4ebb3dcf,6f688f20,39d3d67d,2e9f47cb,a7f8ace5,cd858d96,c9d82549,688f5ed,eb14e7d2,58c52e9,be4a7c3f)
-,S(66fd0ada,7159c2df,fdd925e7,2eb70772,743e39e3,4c10d5cf,f58eac4e,a30e4157,8a8d4c8f,37d2572,6ca66369,59c9eca3,82bd076e,867092a0,b428ad99,2c54a63b)
-,S(60d5d771,4e1e7589,e0b1e68c,ed7f881,73fca809,eae35685,65334942,79962dc2,99bb92f2,4fe59864,a6504cb5,d4407301,f5bb78f,f1f8b31,875ae5c8,644408f3)
-,S(3d98fe85,5d42f661,e35d8ce3,cfee5f17,57bc3de9,ecec02fa,11a2d90,378ac0e8,bdc6dc52,8a55814e,da6bcb58,e2a73b96,9d975c7,a3270483,b419b29b,5263f45)
-,S(2f63a396,4d14ca73,2167e033,306863d8,f75605c7,4ea6c917,890dd50d,fc20f53b,1dd6a38e,deb870ad,f2b2e755,8d327d51,e66a2c81,910d1fa5,b215911c,9c8e004c)
-,S(bcea96e9,4ff1b11e,6e8a09d6,ec594b00,597e2b0a,37b0e581,50a58d59,33220419,80f3b79c,ba5f8457,3f2aa473,1d10c1f,9b6543e1,b8cec5e0,e89cd6df,868cc2aa)
-,S(ae3796a4,823f3eb4,ea4bd677,110dc3fb,45537c3c,4d10d2e8,e758a3be,4875db83,ef2b477b,a9c921df,5ff65b6e,7c6f96a8,627743ff,a53e3858,cd9d7673,f7435ac4)
-,S(5245e215,52c6f785,2cccef32,e9f54774,cfb6bb9b,c363ae72,71174df4,6d77fdcc,ca78f1f0,894a6670,19609169,78c251dc,64f969e,c25be4b4,202508d3,22d07f3b)
-,S(26e2e75b,aeb04a72,8b893f67,d27ebfc6,b8984a88,6fbd8eee,5719107d,5a413f0f,61b51308,ac8643f0,7c18eda7,ef9a4196,aa66221,1b809acc,2b2cb538,23010ef8)
-,S(e0089ec9,4b25ac6,ff44b5da,2dfedb37,9fdaedf1,6bc8cecd,b1a95fa0,32431c73,e8167442,f91ebb24,6a648515,328996a7,841d924a,bba15e62,2547436e,e0ad7f7b)
-,S(f8f16642,266d8dc6,abfdbbc6,f6c47711,a192c051,7f00a633,b78076a7,a7884e72,4f6d0e5,148ce7e1,772f84c,b08903b8,71d2beb5,d0934488,8ab1c75d,42232790)
-,S(23082df9,a86b80fc,5185ee3c,6493763b,14a6e237,baf686aa,f589b649,8573d04c,bdc6dc52,8a55814e,da6bcb58,e2a73b96,9d975c7,a3270483,b419b29b,5263f45)
-,S(76dac07e,bc240289,791e6a9a,56abdc7,bdf2fac1,e7a0100b,5a0ee09e,3455cc33,8b4502cd,420d4202,39a5ad3c,fa10b0b2,6ccd084d,5faed560,7de07a6,fb7b4093)
-,S(8735fb32,6950ae71,31cd03f7,cc1511d8,65c87e84,748c9824,8ccc576e,5480ed8,c63014ea,99a0c8e0,aca2a93e,ce85729d,d615d7d8,6bc5670e,31180979,791b7d43)
-,S(969360d2,54e2dba9,e79d0789,c339250d,c438330a,a66addf9,f27e2ee8,fc9036ac,66446d0d,b01a679b,59afb34a,2bbf8cfe,f0a44870,f0e074ce,78a51a36,9bbbf33c)
-,S(74f5ba33,89d075d3,eebaa54d,73e9f038,881b7329,5623e833,b5e87beb,29ba3246,74bafd32,bdf2bdfd,c65a52c3,5ef4f4d,9332f7b2,a0512a9f,f821f858,484bb9c)
-,S(a4465b18,b7f702e2,a8165184,834ce490,68fb0a51,6658734b,6229eda0,605911a2,c8c7236a,68522b1a,3f5c650e,4673345d,844bcccc,1ca54b7e,ab3ad885,6e6ee7e4)
-,S(eac612b0,84cb452a,35d49c60,c0d0c0bd,359eb909,68a80186,92a028f,ece4c331,23575727,9da3d12b,c0004d12,e06a9cda,8405c91e,b8118260,2793e62f,16c2a29e)
-,S(f31a7dfe,4d2e4a98,aaf3dd70,25441c7a,2da056b0,db6795e8,3a39d76f,b316bd92,e8167442,f91ebb24,6a648515,328996a7,841d924a,bba15e62,2547436e,e0ad7f7b)
-,S(470ce05d,53f77ff0,c5ab8770,a4c11a3e,23f7d4fe,f5c0ab36,1b940a8a,6b8cf7b9,57d6adb1,5f0b1daa,1fce91d7,ccf75d45,9480b056,6f81ff8a,85612173,1cd8f2b8)
-,S(d9fbce92,515652cf,3714f87b,e16e505,91a28eb4,1bf7053,2ab42ebd,be900212,2d7d0b7e,4fb4131d,9f21966a,4a73b0f2,639ede1f,26b0ba25,a2580eb,30100c96)
-,S(1ea4ff3b,5a0a78a9,fda9d73a,ea5f0629,886b083a,6d3beea4,d6bc96e1,b50da944,9edf1300,7f2b7fb2,92301e8c,77ddb63e,5f2d23c4,315118c1,8b1b6273,86bba11a)
-,S(3d303d12,f8eb67d0,ecaee514,bdd90e57,b58b6d6a,c896a26f,78c06103,52225b04,2d7d0b7e,4fb4131d,9f21966a,4a73b0f2,639ede1f,26b0ba25,a2580eb,30100c96)
-,S(9fa3c7bd,6b8544f2,df48309,596ea267,8f8537e1,dc406ec7,cf2c67d1,ae5f1e8a,37d97203,d25d9c14,53827e27,9474afb2,92943df0,8ba1108a,43644c86,ee6c87e4)
-,S(92906a31,52682000,a59736ed,ef48a7b0,c78d6a49,8727b3b,893d3478,de04ada5,80f3b79c,ba5f8457,3f2aa473,1d10c1f,9b6543e1,b8cec5e0,e89cd6df,868cc2aa)
-,S(599db82f,8229ad47,7a662726,d2d351a6,f76cefd7,8fc376b1,ef6e344e,4e4dae3a,284268b5,4bf5e42c,5ca5e116,e92cd897,b7184303,418f7ae2,7458a745,6692db96)
-,S(ee534645,42ff07e2,8cf783ee,1da96e00,b81f531,9b9a4885,27f92302,acd12cac,c90704c9,8f72d8be,638a36dc,e8768909,fac7921c,dc54d314,89fbb3e,6da9018a)
-,S(7fe309bb,e3e8edd3,3f5f94b,4247256e,41e62e69,fd9f2063,d878c6d6,3d85675f,dca8a8d8,625c2ed4,3fffb2ed,1f956325,7bfa36e1,47ee7d9f,d86c19cf,e93d5991)
-,S(64eaaad1,9c7e9d01,5cc997ff,c2f23022,c59805d,48a48e1,970b8847,10211659,fb092f1a,eb73181e,f88d07b3,4f76fc47,8e2d414a,2f6cbb77,754e38a1,bddcd49f)
-,S(59e10f43,eb4b2fb0,94f2f66d,1404dd08,ab9c2442,50bd16e0,21feb78f,e0380d01,e2295c71,21478f52,d4d18aa,72cd82ae,1995d37e,6ef2e05a,4dea6ee2,6371fbe3)
-,S(c979de1c,1318f60f,a9929ff3,a6621b9,d15226e8,2883556e,36a5f77b,28e3b585,d7bd974a,b40a1bd3,a35a1ee9,16d32768,48e7bcfc,be70851d,8ba758b9,996d2099)
-,S(64dd1439,5d19a544,a7a1e81b,b9d079b3,593e7022,6bcd444e,6dc8197a,1a6dc3e6,d380d231,1bde27ad,2c400971,66c3744b,3e762c41,42b05998,95a6605f,739c5fdf)
-,S(d8a3fd95,b7ed8e8a,674da307,94911393,303917c2,60944d1f,76261274,fba45757,a13872c2,e933bb17,5d9ffd5b,b5b6e10c,57fe3c00,baaaa15a,e003ec3e,9c269bae)
-,S(acee4285,57fc32a1,d3d14199,30cadaf0,613a630a,7a7e8c14,d08d05df,19143c3a,ca78f1f0,894a6670,19609169,78c251dc,64f969e,c25be4b4,202508d3,22d07f3b)
-,S(5d771e5d,6dc6c87,5ede8bae,4b27a9d4,3c5f8da2,8e84f5c3,501299c8,db16484c,859f03d8,1b54f459,158e3fc9,ad47c3a3,a54a2537,15bc213d,7b8e6072,9283bfae)
-,S(e8d3f45a,b5be455f,dc3c2270,34100ca2,b8d203e1,35a9ed3d,5c8b703c,ef4d9b48,2d7d0b7e,4fb4131d,9f21966a,4a73b0f2,639ede1f,26b0ba25,a2580eb,30100c96)
-,S(9963e528,754cbba8,29622664,1aec11b0,3fba2739,40f61cbd,5d4c0478,5429f40,298f1c0e,f903dc7d,b940cdc0,ed8183eb,a891c59b,a2a3be25,bf0dd4d5,b03574c3)
-,S(cbdb65,553cd5d8,ff61cf33,e53fdd9a,cf0ee159,c21dc578,be5bac2b,7973c229,ca78f1f0,894a6670,19609169,78c251dc,64f969e,c25be4b4,202508d3,22d07f3b)
-,S(5029bff6,d4c80347,738230c8,cb252906,471b5bc1,3d26a533,304f5f0d,808f756c,97682462,a5c276b7,1568f961,f218a99e,4e528b1b,d4e255b0,acc27744,98ec84fe)
-,S(80c5672b,3f773975,b5829101,6fa9de9b,33863263,8a978db,934f04c1,3a909b85,55ebf47c,6835f232,5ea232b3,8af64478,65cbdce1,2f2c5a79,abc445fd,518ab677)
-,S(226e653f,c8df7744,9bacbf12,7d1dcbf9,87f05b2a,e7edbd28,1f564575,c48dcf18,5ec78d3d,16cc44e8,a26002a4,4a491ef3,a801c3ff,45555ea5,1ffc13c0,63d96081)
-,S(521e20fc,9c7c0514,47f31e74,5bb81662,dac66374,9b891a6f,d9681cb6,21e3155c,9e4aecf7,5379bc0f,83e71258,1065be69,f5599dde,e47f6533,d4d34ac6,dcfeed37)
-,S(5472b5d0,4ba1ca80,236183b1,46e596e5,4b20b83b,248988c8,bbe8460e,3666f262,a829524e,a0f4e255,e0316e28,3308a2ba,6b7f4fa9,907e0075,7a9ede8b,e3270977)
-,S(2ecd421e,47399e76,60d10143,1789a437,89b54d23,31d1cb78,49cabda2,bc5174f9,c8268dfc,2da263eb,ac7d81d8,6b8b504d,6d6bc20f,745eef75,bc9bb378,1193744b)
-,S(4097892c,5478baea,62e8da20,6d17fcd8,b8eed45f,24fb7648,fe742508,8da60a4e,10d4b884,5636de20,a009a491,83906957,9d88bc00,5ac1c7a7,3262898b,8bca16b)
-,S(eb5c4120,4917d176,6fea883b,e680dfd9,233d9654,97fac48b,76f5d0d4,7f6dbf73,6897db9d,5a3d8948,ea97069e,de75661,b1ad74e4,2b1daa4f,533d88ba,67137731)
-,S(92b866ad,a37a3f2f,bc607717,96b2c74f,57dde74c,da8e015f,792df531,7eb21fa6,55ebf47c,6835f232,5ea232b3,8af64478,65cbdce1,2f2c5a79,abc445fd,518ab677)
-,S(a78dab,f2f2ef7f,4d424752,aa1aeaf5,50bec241,bf9ad129,3b9fe680,32b6141b,9de02272,226d60c0,97e5907c,407f9e07,d4bc6959,a2079419,139a9578,4e48cb7f)
-,S(b084fee5,5da62ee0,ebdebf3b,245e0d4e,def46aac,bfdc9f43,261d3e2b,eed946a0,80f3b79c,ba5f8457,3f2aa473,1d10c1f,9b6543e1,b8cec5e0,e89cd6df,868cc2aa)
-,S(ff1755e,623c8369,f55edda4,2a5deef0,b32c57f4,80c5884f,d2a2dde1,b1c078c4,640db9f3,9dda2f51,ee3ef3db,775315aa,c06346f1,e31ff76f,83a24bb0,8fc93242)
-,S(2cdce338,ae1f5aa0,55c76cb5,acbd084e,3284bb5d,b8cf9b4a,141cc8ee,1aa61e59,e8167442,f91ebb24,6a648515,328996a7,841d924a,bba15e62,2547436e,e0ad7f7b)
-,S(3faf6744,2aebb675,c6b75911,e3d34f4c,a8f5b669,a4c438f2,2c163659,3e36c1ea,fc0b579e,cbaf2c74,7b5c792d,7b66abf2,dfb1a44d,2ed6a417,7e1fc6bd,a989b623)
-,S(34772cad,ad5888f2,53a810b7,5b175b8d,e3a454e4,26a1b5a5,c003f222,8e7b45c0,d380d231,1bde27ad,2c400971,66c3744b,3e762c41,42b05998,95a6605f,739c5fdf)
-,S(853cffd4,4cb0a29e,390ad886,455566cc,2776ab74,55210e57,9bf1fcc0,c8d767ed,621fdd8d,dd929f3f,681a6f83,bf8061f8,2b4396a6,5df86be6,ec656a86,b1b730b0)
-,S(1130e02f,29480660,b2cb4f68,81da3f2c,1bdaf64,8df3b6ce,1a333738,29e4165e,10d4b884,5636de20,a009a491,83906957,9d88bc00,5ac1c7a7,3262898b,8bca16b)
-,S(a0627644,ebc6a423,1f3e113b,eedbf9d8,21d9374a,af2e7c55,14ee7395,aa9992b7,7a60fc27,e4ab0ba6,ea71c036,52b83c5c,5ab5dac8,ea43dec2,84719f8c,6d7c3c81)
-,S(1e864d74,9e96a16a,c4299b88,226aaff9,9a45ab9c,203853ac,ea0378ef,5715ded6,6120ecff,80d4804d,6dcfe173,882249c1,a0d2dc3b,ceaee73e,74e49d8b,79445b15)
-};
-#else
- #error No known generator for the specified exhaustive test group order.
-#endif
-#else /* !defined(EXHAUSTIVE_TEST_ORDER) */
+#ifdef EXHAUSTIVE_TEST_ORDER
+# error Cannot compile precomputed_ecmult.c in exhaustive test mode
+#endif /* EXHAUSTIVE_TEST_ORDER */
#define WINDOW_G ECMULT_WINDOW_SIZE
-static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = {
+const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = {
S(79be667e,f9dcbbac,55a06295,ce870b07,29bfcdb,2dce28d9,59f2815b,16f81798,483ada77,26a3c465,5da4fbfc,e1108a8,fd17b448,a6855419,9c47d08f,fb10d4b8)
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 1
+#if WINDOW_G > 2
,S(f9308a01,9258c310,49344f85,f89d5229,b531c845,836f99b0,8601f113,bce036f9,388f7b0f,632de814,fe337e6,2a37f356,6500a999,34c2231b,6cb9fd75,84b8e672)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 2
+#if WINDOW_G > 3
,S(2f8bde4d,1a072093,55b4a725,a5c5128,e88b84bd,dc619ab7,cba8d569,b240efe4,d8ac2226,36e5e3d6,d4dba9dd,a6c9c426,f788271b,ab0d6840,dca87d3a,a6ac62d6)
,S(5cbdf064,6e5db4ea,a398f365,f2ea7a0e,3d419b7e,330e39c,e92bdded,cac4f9bc,6aebca40,ba255960,a3178d6d,861a54db,a813d0b8,13fde7b5,a5082628,87264da)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 4
+#if WINDOW_G > 4
,S(acd484e2,f0c7f653,9ad178a,9f559abd,e0979697,4c57e714,c35f110d,fc27ccbe,cc338921,b0a7d9fd,64380971,763b61e9,add888a4,375f8e0f,5cc262a,c64f9c37)
,S(774ae7f8,58a9411e,5ef4246b,70c65aac,5649980b,e5c17891,bbec1789,5da008cb,d984a032,eb6b5e19,243dd56,d7b7b365,372db1e2,dff9d6a8,301d74c9,c953c61b)
,S(f28773c2,d975288b,c7d1d205,c3748651,b075fbc6,610e58cd,deeddf8f,19405aa8,ab0902e,8d880a89,758212eb,65cdaf47,3a1a06da,521fa91f,29b5cb52,db03ed81)
,S(d7924d4f,7d43ea96,5a465ae3,95ff411,31e5946f,3c85f79e,44adbcf8,e27e080e,581e2872,a86c72a6,83842ec2,28cc6def,ea40af2b,d896d3a5,c504dc9f,f6a26b58)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 8
+#if WINDOW_G > 5
,S(defdea4c,db677750,a420fee8,7eacf21,eb9898ae,79b97687,66e4faa0,4a2d4a34,4211ab06,94635168,e997b0ea,d2a93dae,ced1f4a0,4a95c0f6,cfb199f6,9e56eb77)
,S(2b4ea0a7,97a443d2,93ef5cff,444f4979,f06acfeb,d7e86d27,74756561,38385b6c,85e89bc0,37945d93,b343083b,5a1c8613,1a01f60c,50269763,b570c854,e5c09b7a)
,S(352bbf4a,4cdd1256,4f93fa33,2ce33330,1d9ad402,71f81071,81340aef,25be59d5,321eb407,5348f534,d59c1825,9dda3e1f,4a1b3b2e,71b1039c,67bd3d8b,cf81998c)
@@ -191,7 +42,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(c44d12c7,65d812e,8acf28d7,cbb19f90,11ecd9e9,fdf281b0,e6a3b5e8,7d22e7db,2119a460,ce326cdc,76c45926,c982fdac,e106e86,1edf61c5,a039063f,e0e6482)
,S(6a245bf6,dc698504,c89a20cf,ded60853,152b6953,36c28063,b61c65cb,d269e6b4,e022cf42,c2bd4a70,8b3f5126,f16a24ad,8b33ba48,d0423b6e,fd5e6348,100d8a82)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 16
+#if WINDOW_G > 6
,S(1697ffa6,fd9de627,c077e3d2,fe541084,ce13300b,bec1146,f95ae57f,d0bd6a5,b9c398f1,86806f5d,27561506,e4557433,a2cf1500,9e498ae7,adee9d63,d01b2396)
,S(605bdb01,9981718b,986d0f07,e834cb0d,9deb8360,ffb7f61d,f982345e,f27a7479,2972d2d,e4f8d206,81a78d93,ec96fe23,c26bfae8,4fb14db4,3b01e1e9,56b8c49)
,S(62d14dab,4150bf49,7402fdc4,5a215e10,dcb01c35,4959b10c,fe31c7e9,d87ff33d,80fc06bd,8cc5b010,98088a19,50eed0db,1aa1329,67ab4722,35f56424,83b25eaf)
@@ -209,7 +60,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(754e3239,f325570c,dbbf4a87,deee8a66,b7f2b334,79d468fb,c1a50743,bf56cc18,673fb86,e5bda30f,b3cd0ed3,4ea49a0,23ee33d0,197a695d,c5d9809,3c536683)
,S(e3e6bd10,71a1e96a,ff57859c,82d570f0,33080066,1d1c952f,9fe26946,91d9b9e8,59c9e0bb,a394e76f,40c0aa58,379a3cb6,a5a22839,93e90c41,67002af4,920e37f5)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 32
+#if WINDOW_G > 7
,S(186b483d,56a0338,26ae73d8,8f732985,c4ccb1f3,2ba35f4b,4cc47fdc,f04aa6eb,3b952d32,c67cf77e,2e17446e,204180ab,21fb8090,895138b4,a4a797f8,6e80888b)
,S(df9d70a6,b9876ce5,44c98561,f4be4f72,5442e6d2,b737d9c9,1a832172,4ce0963f,55eb2daf,d84d6ccd,5f862b78,5dc39d4a,b1572227,20ef9da2,17b8c45c,f2ba2417)
,S(5edd5cc2,3c51e87a,497ca815,d5dce0f8,ab52554f,849ed899,5de64c5f,34ce7143,efae9c8d,bc141306,61e8cec0,30c89ad0,c13c66c0,d17a2905,cdc706ab,7399a868)
@@ -243,7 +94,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(c4191636,5abb2b5d,9192f5f,2dbeafec,208f020f,12570a18,4dbadc3e,58595997,4f14351,d0087efa,49d245b3,28984989,d5caf945,f34bfc0,ed16e96b,58fa9913)
,S(841d6063,a586fa47,5a724604,da03bc5b,92a2e0d2,e0a36acf,e4c73a55,14742881,73867f5,9c0659e8,1904f9a1,c7543698,e62562d6,744c169c,e7a36de0,1a8d6154)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 64
+#if WINDOW_G > 8
,S(5e95bb39,9a6971d3,76026947,f89bde2f,282b3381,928be4d,ed112ac4,d70e20d5,39f23f36,6809085b,eebfc711,81313775,a99c9aed,7d8ba38b,161384c7,46012865)
,S(36e4641a,53948fd4,76c39f8a,99fd974e,5ec07564,b5315d8b,f99471bc,a0ef2f66,d2424b1b,1abe4eb8,164227b0,85c9aa94,56ea1349,3fd563e0,6fd51cf5,694c78fc)
,S(336581e,a7bfbbb2,90c191a2,f507a41c,f5643842,170e914f,aeab27c2,c579f726,ead12168,595fe1be,99252129,b6e56b33,91f7ab14,10cd1e0e,f3dcdcab,d2fda224)
@@ -309,7 +160,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(809a20c6,7d64900f,fb698c4c,825f6d5f,2310fb04,51c86934,5b7319f6,45605721,9e994980,d9917e22,b76b0619,27fa0414,3d096ccc,54963e6a,5ebfa5f3,f8e286c1)
,S(1b38903a,43f7f114,ed4500b4,eac7083f,defece1c,f29c6352,8d563446,f972c180,4036edc9,31a60ae8,89353f77,fd53de4a,2708b26b,6f5da72a,d3394119,daf408f9)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 128
+#if WINDOW_G > 9
,S(90a80db6,eb294b9e,ab0b4e8d,dfa3efe7,263458ce,2d07566d,f4e6c588,68feef23,753c8b9f,9754f18d,87f21145,d9e2936b,5ee050b2,7bbd9681,442c76e9,2fcf91e6)
,S(c2c80f84,4b705998,12d62546,f60340e,3e6f3605,4a14546e,6dc25d47,376bea9b,86ca160d,68f4d4e7,18b495b8,91d3b1b5,73b871a7,2b4cf61,23abd448,3aa79c64)
,S(9cf60674,4cf4b5f3,fdf989d3,f19fb265,2d00cfe1,d5fcd692,a323ce11,a28e7553,8147cbf7,b973fcc1,5b57b6a3,cfad6863,edd0f30e,3c45b85d,c300c513,c247759d)
@@ -439,7 +290,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(367807c9,a3606b4e,1b8c2616,ad528030,1dfcf686,40eddf02,fc59317c,230e9a86,1f023f2f,a2bbece7,3dba14c,124095cb,fdc4f92f,281a14,8304a412,c16ecae6)
,S(8ec4fdc3,9891f6af,1374e06f,c44b815,1b82541,75fc4909,acba5941,201af62b,2dc6cae5,cac2d887,83dca0e5,3c798f8,fe067bcf,5fc29751,13756cf7,ef4e5f1b)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 256
+#if WINDOW_G > 10
,S(5cc24a6d,4c5b24f9,14542f91,e5fa937f,ffa08551,51b8b842,8729b06a,9178a263,b1da8635,81531a1f,bfb38a4e,419fa1fe,ca8d55a8,3ddbcb98,da19d5cf,fb7da472)
,S(83905926,c03905c3,a9644a6c,da810dd2,92602a50,50c52a21,9134fc4d,e3599e9f,4293260f,e8af6792,a20b115e,aa837638,9094298b,21d9de16,cf20e0c5,7a46089a)
,S(944b097e,4721e9dd,f8204ac3,d3878fa,e8fa6c14,34ae4822,481b2985,6589b6c7,5fc47565,30e9b095,f8b79643,1e745b99,1525bd4c,4764e8e,e8af4b96,9bd6ddf6)
@@ -697,7 +548,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(d9a0c689,95283291,972d6a72,897181b2,6b4ae317,4e98a676,f75bbfbf,81be876e,d36ae886,a0acbc9a,d1564d97,4d3f0309,e9039b93,bb350d92,2b7f8c7a,c6bfa042)
,S(c7a36324,6aeb7c8c,991b2aa7,10abdf5c,fff29912,30b3a69f,be2dd481,7c7c3e0a,1298fdd7,e448d2d,79986302,6b4b2d49,2b32148,d6d1e5f8,15f5b0c0,5c9e21f)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 512
+#if WINDOW_G > 11
,S(635cd7a0,5064d3bc,66535a0,4dbf563a,640d2464,c7fe0ac4,8304214f,e4985a86,e40265,913e77f6,46735cfc,af9e2f30,e8d5f047,f3281a4c,e0453e27,e9e3ae1f)
,S(5da624f,38edea25,37f5b632,695b481a,eca54bb7,2169cadc,c5b91e10,989ee5f5,d3ea6dba,a1080300,fffaeeb2,43a5256e,48c28822,37b16505,a220d771,ca721779)
,S(aa4f64a2,b19775ec,92c1f687,1fe4b6d5,ce05278c,2976c80e,fe19284f,e87d4d5e,f39f117e,95fce0fb,6976e949,9583a66c,224ea028,8396518e,293793dc,3ed4f240)
@@ -1211,7 +1062,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(dd879eba,f870ee3d,f6e3f03,60833e12,fbdf844c,d6a5b76c,19802485,6649bbc1,e7bb04c3,a99a5c11,dd7a324c,1d5696c8,b041a12a,c6a538f7,3094716c,9943c55a)
,S(7aa4afbe,29b06a1e,da9319d5,72572b13,1df68f74,1b5f8d8e,d00ccc04,80f8016e,15f79a9f,b77b8587,7f02e3c1,3202b972,423fd00d,937c0d2d,c9d94b17,53fe7130)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 1024
+#if WINDOW_G > 12
,S(9145f3f5,876a3265,5d7fee64,f2d6e660,c34354e9,1571d68b,a19e4cc5,8c39f890,45e0a95d,cf369fd0,5bc9ba7f,da108d6f,37650c1d,5766b6c9,98ecda28,b285d8ff)
,S(5450b752,36fb010d,1ef37afa,2077f3dc,a5a7f6c8,91a21317,13df740f,4511ac9e,a7c7ed57,62bef86d,4de1084d,3ded2d7b,13ff563a,67109ef0,8b6f0180,fe6ba175)
,S(ba6c0d72,5e48de2c,cb8256d6,7417d075,bbf2766f,d13501b8,4c32cf88,c0666a0f,cd2a9132,7137299f,50eda669,3a599032,bdecd64b,91bcc640,5ed186f3,e525e442)
@@ -2237,7 +2088,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(6d109d8c,2505808f,7b4052d6,b170ec80,c68373bb,e02f2b62,1cd29773,a96acb3e,c8dea0de,511f6e40,5141088e,cb023bff,200c870b,9cb952ad,c5038b28,eadc9a57)
,S(9c219898,6d202467,c055c734,73557505,6105b3e,2874dbf2,8f7f0c39,66e1af88,8637496c,8eddff12,f9dd4146,36565e0f,efddf5cd,52d48455,ec9fb2d,8dd0914a)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 2048
+#if WINDOW_G > 13
,S(22ca5039,e660f60,1036dd75,2bb973b,dcd104b5,dcb8f2e7,4de8edc6,c292b03a,86fd4471,1ef6b81e,4416449a,708fa0c9,cb6731ba,c403e58e,da5e915f,646b11e8)
,S(cfc18f02,cc004640,f2116fdd,1f6ca202,2e39be25,df75e27c,80bde842,e6b6f938,d62b58df,4f79d0e5,97ba2923,f708cbe5,c09236a4,d9d01398,6451d684,6290df7d)
,S(3388bcc2,3425458e,991f46ca,6235c50a,57490556,becbaf25,9d3c14f9,9a80a087,7d4aa2f8,6f65783,1c22316,6958fbf3,6c3dca5,d4ac413b,3102e13e,a645c016)
@@ -4287,7 +4138,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(d5acff71,e82a455c,3a1ce292,689e8686,f441ce1b,e644e79a,bd6d0efe,29270865,aac6d48f,1b46e970,a044971a,ad13f033,ca8cde96,958d870e,dc7d80,1d26d5e8)
,S(5cf51c1b,2210d85,9d765e17,32109514,8f03fc57,51004b6a,91f098e2,e2711596,1eeb19e0,610df459,2c31e58e,aa2a2148,17fe9ee,a3995838,f395bdcf,26d5c3b3)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 4096
+#if WINDOW_G > 14
,S(21456873,b58e3687,52c75800,8d3bcae,9efaf1f5,c5727842,25e3d854,8fd421cb,a2f2d10,c85e8a0f,4a136ad0,5df991ce,7d3c5585,a263d5cf,da50a4f4,3db76cb0)
,S(c10de5c0,51ae2d73,28ac06ca,cca840b4,ed7ab204,21c6122f,1d68fe7f,7893d38d,ee30e086,a891484,2ad4041,f9ab9c57,cff1a315,f0642d31,b31c2914,faac99e0)
,S(be778032,f12c1b77,9bba3d9e,d290ca90,30ac7050,bdd77a2,7eac09be,eea65c0b,8657348b,a1e27a63,1dd2a54b,e2d5270f,4cca817a,219c5378,4d4f73ba,2c932e63)
@@ -8386,22 +8237,22 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] =
,S(1e70619c,381a6adc,e5d925e0,c9c74f97,3c02ff64,ff2662d7,34efc485,d2bce895,c923f771,f543ffed,42935c28,8474aaaf,80a46ad4,3c579ce0,bb5e663d,668b24b3)
#endif
};
-static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)] = {
+const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)] = {
S(8f68b9d2,f63b5f33,9239c1ad,981f162e,e88c5678,723ea335,1b7b444c,9ec4c0da,662a9f2d,ba063986,de1d90c2,b6be215d,bbea2cfe,95510bfd,f23cbf79,501fff82)
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 1
+#if WINDOW_G > 2
,S(38381dbe,2e509f22,8ba93363,f2451f08,fd845cb3,51d954be,18e2b8ed,d23809fa,e4a32d0a,fb917dc,b09405a5,520eb1cc,3681fccb,32d8f24d,bd707518,331fed52)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 2
+#if WINDOW_G > 3
,S(49262724,e4372ae6,f6921b82,aa4699a1,f186aea5,40122630,3ea42648,97c2a310,1337e773,bca7abf9,5a2cfa56,9714303b,6d163612,a75ff8ce,c41b681,5e27ded0)
,S(e306568c,1a240c90,d5e253b3,e477e2f8,4dcc1a56,ff06db8d,1384b079,cebd2d31,eac6fe3,78934260,888f2b10,7f7d0db6,ffbc8042,be373826,692b4083,92546e44)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 4
+#if WINDOW_G > 4
,S(3b9e100e,2428cefc,271b0e76,23fbd633,74ebf8d9,aab41dd9,c530c39e,363136b0,fafb9815,2d16bb71,df1533eb,8f475b26,a2ae28a3,3ad31f81,953ec16f,6cdbbc8a)
,S(bb0aad49,712ac9a9,2b76ca80,f5dedef7,17ca0768,8107beee,9608f047,2f485d3f,ea699c53,c5835479,8ecd201f,7297da34,895a5afa,31670bff,e7939250,3ca2f975)
,S(79090ac8,e4eefcc0,d4e8eb19,7afe0113,e1e58b4d,b01123de,4aeed33a,36718dc9,eaab722b,91905b8f,13d816cb,cd9aaa56,dd36afb7,ba9008b,963322b1,1cfae7c5)
,S(e77c81ad,e9f97b55,1c03dbbc,e549ba66,8dd71de7,cd775ad2,a269694c,7f60c7d1,3acf1478,eef81321,c5fc3b32,3ea81543,631470f7,1c2986d3,4ec581f2,82d72449)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 8
+#if WINDOW_G > 5
,S(de2b5ce9,dbce511c,f2d8878e,3ded87cc,3d633dae,a2d45341,501fb3a4,55ccf6b0,f10576f3,d3c3e0e1,bbf717e9,8b1a3744,65b8c45a,c66318bb,34829eb7,11100666)
,S(d07bddff,d491a2fe,1ea59fbd,7c121217,29659ca5,de46658b,26b1460b,13c03c56,b2ad4708,cd3c97dd,f9c40e2,a1de04d5,61d963ff,8cc2eea7,6be3f60c,2b405ce7)
,S(82403e7c,5d3016af,3765ec4c,396ce8e1,f8da45c,434b8257,10edab41,bb6a4d51,d09661b,e27cb767,4456badd,b3e84051,99ab6ccc,4ec67c1b,11e92ead,7b463b19)
@@ -8411,7 +8262,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(1f56f096,b18a7499,a153a5ae,acf8be05,8496dd23,da8e6c19,215628fb,c0567ed0,fef22b8a,3b52f490,83004436,b65cd69,c94189f4,1a93c0d5,1fc13cb4,379dff58)
,S(1d9f69b1,a4a47432,e386f525,234aa30,79e947cf,cf203297,4e0fc05b,638e213b,d898ec17,949c0761,b38500c3,a2b1da24,5438d5b3,d3f6f720,41f15d6e,e4d4ccbb)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 16
+#if WINDOW_G > 6
,S(128c913,4d9dcb78,12fc4361,5c67ad0,55213354,dc8008b1,aeb5a9dc,fb629efd,fee3e54a,dd152610,d9725936,99d662,c160c8e4,ec6f76e4,5ff41818,be67c96)
,S(21ec012f,5a95b94d,244b8d51,9756075c,301f2854,8e2c51fc,49c0e3d9,d1a9685,2def2105,77af497f,4c7fef71,6949f28e,7418eda6,fd5fc162,d128de19,3cde08ae)
,S(688f5202,fb9d8bc0,9e480e89,4c7cfc74,761c3be7,7dafb11c,58422836,3e331cc5,96ba7d59,63b541a7,2ec7cabd,92403434,1a393eaf,89eebd94,62d9c218,c7302cd3)
@@ -8429,7 +8280,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(144e88f6,3e73abff,72cac11e,7ddccf79,19e744e6,278941ae,18d1b797,e098e4e5,63cdbf3,5df3c655,c58197f,ea54633d,158705cf,7dc2eb3b,4e09f83c,3021837c)
,S(9436e3dc,489ecd8e,2d16a739,c9c73e3d,60e5bc93,68157039,75b8efbd,5c3a9081,1460531f,50cb6ebf,d1aa7806,ea84e7f7,8e8d76b2,b3a66d5e,3a0bf60,39a7e59c)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 32
+#if WINDOW_G > 7
,S(9d3c2561,7a56d10b,46d9b01a,1710d193,e840e005,df669e76,1936c275,20890db9,6bbdc0bc,4c4ae9bc,c2dfee9b,82da9b94,1f89ffcd,e8af2aca,4467ce3,78521ea3)
,S(29f98e50,f51b7f8b,e18c6ae0,b453c4f2,d0aca5a8,b0e61d2d,dda8506,3fdb76c8,daf3bcdc,ae8e031c,73eb8b21,14058063,58a6ec30,ad379186,df80e3c7,f0e5d28f)
,S(d67d30c2,c71daa36,1805e31,1dd6046e,17a89752,94d76e1a,538af074,4dc22c94,48b9b0e7,12c807b0,b92e690a,a2e068cc,e87ebbbe,aaf4bd96,9c1114bb,a54f670c)
@@ -8463,7 +8314,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(6f97ac0f,27b87905,6b442d13,e566978e,91f0cc1d,d6ac1e64,e9764a35,325dd1b7,83c6e70c,fac6c707,226ce1cc,691b38a0,7e937f5a,5f2d9c81,4dd0d3ff,9f433d32)
,S(72c5d60f,eb014e2d,ba8265ca,d454f261,2d6abcd4,b2236bad,c94c4801,561dce1f,e3119a19,7ef91963,b3b28216,3c5d3acb,97b281b6,d246cbf0,690b40da,63978fe2)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 64
+#if WINDOW_G > 8
,S(a96d2da0,1b10186,6998659d,f441a1b0,2af32b94,aae8c6ea,707d9ed0,d5f33825,660d7d83,5da5235,9f7cfd41,28c370aa,5659ea71,16a91690,6c0e8108,a513f9f)
,S(2cce6f63,4d815ecc,1981d200,87616677,d906aa27,990c4875,17314dc5,5be3c4fa,615210dd,bd599e91,1b6f997a,fd05475,b33cb274,c9ecb6e5,d3c23323,beba4b50)
,S(992b0084,525dd399,d98602d9,8b8d53b2,4558fafc,758a2f46,60e89bd6,a645f0c4,83ca98d2,26545a29,8c45f40b,11420602,f5a5c70e,595eea57,2da64d61,a4e2f98d)
@@ -8529,7 +8380,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(7590a4a,627d5e90,e42a4b0f,6be6497a,f906182d,d2dddc48,a8b6bbfd,f56c0504,d611e21a,b5498760,5c97e878,baa464bc,cc5bd875,353b69f8,1f93ce2a,a6c38587)
,S(94b1da0f,b310e056,db0dff72,81db3362,1fcc555d,bf3c973b,76097908,7fe19d6a,8318893d,d5b41a56,33a2ab4,ae4b953c,45c42e9c,8f2fd159,85286de3,fd4fc217)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 128
+#if WINDOW_G > 9
,S(b5af4299,bcdacef1,e07d081,daec2cfa,d6f8f821,38e151a5,f20e6d52,84c9a6cb,c0984407,8a7db82d,f572987e,b137dc09,c8cf65fd,aedcb20,43b2479b,ab95448d)
,S(5f49ad43,70744587,3980a153,c851829b,f8ef6141,9889bb0c,3c476847,6939c3e3,5c40d385,20f56c3d,ba08ae1,b40fc24a,2ae25c94,45cae0f7,d01d1800,747e04eb)
,S(f6bb067f,88ccc11c,64e30d1a,e6893942,16bea3bf,26ec9c64,5cde1b9e,487da385,315a476d,7268978c,d89d4ec8,adde4a83,28dbfdd9,f2bf44fe,ad4ed721,78288f55)
@@ -8659,7 +8510,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(8ac79852,75673818,69fe93c9,6141493e,72ca344,790487b,d5425ed6,9f5c5c18,bb314248,fcbfc867,d1972e04,b9ef1f90,775375f9,9d25bcec,684c72c9,bdd1c08e)
,S(560e9711,88cbc7ce,c61f3bf4,45f0ade3,6f3f174d,219a160,5f9c8692,3f848b9d,9e92dace,6775cc67,bfbbf21f,c64e6e9d,4d133f8a,c18ee277,bc80ef6d,d1b9cd2f)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 256
+#if WINDOW_G > 10
,S(8c464204,4b95eb66,cc95ca08,8469a1c0,28eea52f,7f709fe,e6d90700,f5fb943a,bf10fc5d,7bc25181,301b3a61,5ebea597,a3492025,953c3aa5,1a11271e,689d0223)
,S(d293f74c,f3578b71,35377247,cd8b0367,7f2245da,f87453cb,4d8d1cc2,871eb5aa,86c2013a,61dfea14,63e45931,eb09050f,f080e3d4,ae357423,ba7afbed,a64a6f26)
,S(35997241,ae757b1f,c767611e,c76eb935,fefbf7a7,33666aff,4c6bd744,d7687d35,bcbea61f,246bcd4c,c3c7fd35,7bd393da,2f36e0ef,cca0df9c,5994f96f,c44b2aa0)
@@ -8917,7 +8768,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(6ca7e032,8bf5d67a,b804d431,e5f709e,bd3156e0,1d4511da,1dc67394,24d2e659,c428b133,f3683909,6551c2ff,2f870d80,d80aaaf4,6d2b1a69,7722057,5eca2647)
,S(60df19a9,83e9086c,4cbd20ba,fbafa8b2,346ae4ee,9c1ad4dd,99959e91,2df530d3,dae7b854,29f817a0,421c2f1e,e4e8e4d,a09d60f8,84701e31,d7a8b7b0,3b79cc48)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 512
+#if WINDOW_G > 11
,S(6f18d50e,5ef5f2ad,bad80ea1,c59a3847,5c22ff05,6ba52c7,e1d26d6d,d3686bce,d1d7ea0f,a166efd0,facf5c83,bc3786e0,d3f3405d,5b4578f5,13a336ad,f0d7d7a9)
,S(b634dc9d,5ed51336,4ac9569a,8ddee9d9,fcdfe00e,65e59233,b3be8a07,2fd949e0,d72e46e2,c61b2575,f25a505c,bdc3456e,fdf18976,6562a1a7,e86d036,eb31db69)
,S(5a37fcf0,2dba24f3,e6646171,43dbc5ab,40d91983,69f5cf0e,4fc566fd,97445910,a960d1d8,13f8746c,662a9582,614c7847,33e6153d,2975cb59,c3342463,75c69d1e)
@@ -9431,7 +9282,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(edc9e8b1,3c61a1e0,8f93be12,495084ed,b5a90eca,4f11dc03,cd217b1a,56285a2c,66c2fd21,7e8fac7,4fd43b1b,58c80661,b0e8df85,a2fddfda,5e9f99fe,621e1f3d)
,S(dd94295f,8388a498,2aaa4164,927b81ba,cae9d002,b3ddb6b6,97082c70,f1e2c66e,838c060c,d409c1ea,9bcd9e8c,b1fac83f,3b08c2b2,482d8f4b,fd3ebc18,8d6706b)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 1024
+#if WINDOW_G > 12
,S(7e068e04,dfc0be48,e5c0d3b3,5bfc734e,96e96ddd,d0ac4876,92f74535,685ab7e,df2cd146,90d225c8,d04052e6,93f14bf,69351e08,79883646,2c88401e,4ec70d0a)
,S(73a9bfcf,d10aac9b,46e659f,76c439a0,b7c2a073,7dec217a,21f43f39,949a5052,73b91529,3ea7b052,682062c,8f86cbf,bf379df6,91a9cec2,4a1424a9,b3be10dd)
,S(efddff84,c8cc754,e6e34678,d85809bc,55cc224b,f69be05a,daa847ec,d408c55b,65dd8f41,8264aab2,efe6ed7e,c45b4ac,8aac218a,3b6713fc,1736dd6d,c2f188d5)
@@ -10457,7 +10308,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(8a663e7,40cad237,12dad6f9,1bb5d266,30542933,853e02eb,665e198d,a5c6e53b,e206b399,51175d15,daccf711,63b0918c,786a8fe,2077812,c0c1eee6,bb5c1e99)
,S(2b0956cb,e0ca91ee,f44fac0a,76cc0b00,5f4a5ae8,27e63696,fe1aa8b4,a92941a1,a31e6fa8,dd454eaf,90ee1e11,62627e3,3f84b8fe,da29f423,ddf2a962,386cffd2)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 2048
+#if WINDOW_G > 13
,S(e0144151,7a2ac970,eb6381df,7e11fb4c,6b009980,6a0d330,d5429126,e662c3bd,8238ce9b,a691c80c,21563934,18d18dbf,aeb3fd34,48863a1e,7f4ba360,408dfcee)
,S(be3213e,8b062f33,caf16c53,5a0b666,f344e0d7,1e28aeba,8b215a3,7ec86c37,552523ba,eaca38a4,cd795683,852a2643,550fb83d,d1db0adc,3f1b29c9,7d51cd1f)
,S(623393b6,bfbfbd64,fc7aa1db,9e58e274,1c18eb6d,b5eb30ab,c4fe167e,a9e8ff2b,7c0e4174,f0fd5bf2,a025e316,fa3b7b97,1339a197,b52b0b50,bad4dfe,34e42cd3)
@@ -12507,7 +12358,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(4beaa213,3c6161d0,c7da6d7c,8eb09cba,2e8ca505,a790a7d3,9d0c16b,8053bb52,da4c083c,9cbdda2e,c6626d8f,e4b2ee13,703496c,c298bffa,db1acdca,99d4f6d5)
,S(a8d31ec6,53ce1834,a78de363,c5f9abd1,8594b34,4d34f5fc,8a10e81f,5804831,b4d6a9cd,7cbbe370,9edb5601,cb7c8ba4,8c462c18,594a4bce,64f4c286,dac5cff9)
#endif
-#if ECMULT_TABLE_SIZE(WINDOW_G) > 4096
+#if WINDOW_G > 14
,S(a8f11586,f3df4945,a753c485,ee0fd4d,e410771d,bddc1b26,c9ff10e0,77b915b7,a4ad6f16,dbd741bc,71b2dfd2,dd2d340,3816bf73,4e73cc10,abcfa6bb,d0f161a4)
,S(13e697c3,812d4772,254082da,372892d4,e1a66e1a,eb16bbd4,f7a0d531,c979cb2,87fa7baa,f6def12d,31e42c14,f672c0d6,a9d0e2e1,ceee2546,d65bd01,c18df57f)
,S(3cae4590,821a9697,a5963269,b44f0222,98f60021,b6048b3f,49c6fd4d,8650c7a,e03f8745,60418449,ad97f28,41664745,349329d,268c1c43,86e25147,6e44b234)
@@ -16606,6 +16457,4 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G
,S(1b9a142f,fc4d03ea,4b079f2d,b05fad98,8ddb2d32,b359967f,c173801f,63320825,59bda7ed,5b691c20,4fc8f8ac,f53be298,ae628954,a8134d0f,dd097e67,be9ff9b6)
#endif
};
-#endif
#undef S
-#endif
diff --git a/src/secp256k1/src/precomputed_ecmult.h b/src/secp256k1/src/precomputed_ecmult.h
new file mode 100644
index 0000000000..949b62c874
--- /dev/null
+++ b/src/secp256k1/src/precomputed_ecmult.h
@@ -0,0 +1,35 @@
+/*****************************************************************************************************
+ * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor *
+ * Distributed under the MIT software license, see the accompanying *
+ * file COPYING or https://www.opensource.org/licenses/mit-license.php. *
+ *****************************************************************************************************/
+
+#ifndef SECP256K1_PRECOMPUTED_ECMULT_H
+#define SECP256K1_PRECOMPUTED_ECMULT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "group.h"
+#if defined(EXHAUSTIVE_TEST_ORDER)
+#if EXHAUSTIVE_TEST_ORDER == 13
+# define WINDOW_G 4
+# elif EXHAUSTIVE_TEST_ORDER == 199
+# define WINDOW_G 8
+# else
+# error No known generator for the specified exhaustive test group order.
+# endif
+static secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)];
+static secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)];
+#else /* !defined(EXHAUSTIVE_TEST_ORDER) */
+# define WINDOW_G ECMULT_WINDOW_SIZE
+extern const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)];
+extern const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)];
+#endif /* defined(EXHAUSTIVE_TEST_ORDER) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SECP256K1_PRECOMPUTED_ECMULT_H */
diff --git a/src/secp256k1/src/ecmult_gen_static_prec_table.h b/src/secp256k1/src/precomputed_ecmult_gen.c
index bf4e5ea248..d67291fcf5 100644
--- a/src/secp256k1/src/ecmult_gen_static_prec_table.h
+++ b/src/secp256k1/src/precomputed_ecmult_gen.c
@@ -1,13 +1,17 @@
-/* This file was automatically generated by gen_ecmult_gen_static_prec_table. */
+/* This file was automatically generated by precompute_ecmult_gen. */
/* See ecmult_gen_impl.h for details about the contents of this file. */
-#ifndef SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H
-#define SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H
+#if defined HAVE_CONFIG_H
+# include "libsecp256k1-config.h"
+#endif
+#include "../include/secp256k1.h"
#include "group.h"
-#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)
+#include "ecmult_gen.h"
+#include "precomputed_ecmult_gen.h"
#ifdef EXHAUSTIVE_TEST_ORDER
-static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)];
-#else
-static const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)] = {
+# error Cannot compile precomputed_ecmult_gen.c in exhaustive test mode
+#endif /* EXHAUSTIVE_TEST_ORDER */
+#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)
+const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)] = {
#if ECMULT_GEN_PREC_BITS == 2
{S(3a9ed373,6eed3eec,9aeb5ac0,21b54652,56817b1f,8de6cd0,fbcee548,ba044bb5,7bcc5928,bdc9c023,dfc663b8,9e4f6969,ab751798,8e600ec1,d242010c,45c7974a),
S(e44d7675,c3cb2857,4e133c01,a74f4afc,5ce684f8,4a789711,603f7c4f,50abef58,25bcb62f,fe2e2ce2,196ad86c,a006e20,8c64d21b,b25320a3,b5574b9c,1e1bfb4b),
@@ -9743,6 +9747,4 @@ S(244b87a4,fcecef37,76c16c5c,24c7785,be3b3c13,46595363,b8c066ec,45bfe561,9642f5f
S(9de52b81,157165cc,aef44485,4c2b3535,a599a79,80d024de,5334b385,ecbb2e91,74fca165,26fe2f87,a41ce510,4dd5634,5cf98c11,803c0392,3eb4b8b7,60240c02)}
#endif
};
-#endif /* EXHAUSTIVE_TEST_ORDER */
-#undef SC
-#endif /* SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H */
+#undef S
diff --git a/src/secp256k1/src/precomputed_ecmult_gen.h b/src/secp256k1/src/precomputed_ecmult_gen.h
new file mode 100644
index 0000000000..7256ad2e30
--- /dev/null
+++ b/src/secp256k1/src/precomputed_ecmult_gen.h
@@ -0,0 +1,26 @@
+/*********************************************************************************
+ * Copyright (c) 2013, 2014, 2015, 2021 Thomas Daede, Cory Fields, Pieter Wuille *
+ * Distributed under the MIT software license, see the accompanying *
+ * file COPYING or https://www.opensource.org/licenses/mit-license.php. *
+ *********************************************************************************/
+
+#ifndef SECP256K1_PRECOMPUTED_ECMULT_GEN_H
+#define SECP256K1_PRECOMPUTED_ECMULT_GEN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "group.h"
+#include "ecmult_gen.h"
+#ifdef EXHAUSTIVE_TEST_ORDER
+static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)];
+#else
+extern const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)];
+#endif /* defined(EXHAUSTIVE_TEST_ORDER) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SECP256K1_PRECOMPUTED_ECMULT_GEN_H */
diff --git a/src/secp256k1/src/secp256k1.c b/src/secp256k1/src/secp256k1.c
index 36fde24c3d..8f34c35283 100644
--- a/src/secp256k1/src/secp256k1.c
+++ b/src/secp256k1/src/secp256k1.c
@@ -423,8 +423,12 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m
unsigned int offset = 0;
secp256k1_rfc6979_hmac_sha256 rng;
unsigned int i;
+ secp256k1_scalar msg;
+ unsigned char msgmod32[32];
+ secp256k1_scalar_set_b32(&msg, msg32, NULL);
+ secp256k1_scalar_get_b32(msgmod32, &msg);
/* We feed a byte array to the PRNG as input, consisting of:
- * - the private key (32 bytes) and message (32 bytes), see RFC 6979 3.2d.
+ * - the private key (32 bytes) and reduced message (32 bytes), see RFC 6979 3.2d.
* - optionally 32 extra bytes of data, see RFC 6979 3.6 Additional Data.
* - optionally 16 extra bytes with the algorithm name.
* Because the arguments have distinct fixed lengths it is not possible for
@@ -432,7 +436,7 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m
* nonces.
*/
buffer_append(keydata, &offset, key32, 32);
- buffer_append(keydata, &offset, msg32, 32);
+ buffer_append(keydata, &offset, msgmod32, 32);
if (data != NULL) {
buffer_append(keydata, &offset, data, 32);
}
diff --git a/src/secp256k1/src/testrand.h b/src/secp256k1/src/testrand.h
index 667d1867bd..bd149bb1b4 100644
--- a/src/secp256k1/src/testrand.h
+++ b/src/secp256k1/src/testrand.h
@@ -17,11 +17,14 @@
SECP256K1_INLINE static void secp256k1_testrand_seed(const unsigned char *seed16);
/** Generate a pseudorandom number in the range [0..2**32-1]. */
-static uint32_t secp256k1_testrand32(void);
+SECP256K1_INLINE static uint32_t secp256k1_testrand32(void);
+
+/** Generate a pseudorandom number in the range [0..2**64-1]. */
+SECP256K1_INLINE static uint64_t secp256k1_testrand64(void);
/** Generate a pseudorandom number in the range [0..2**bits-1]. Bits must be 1 or
* more. */
-static uint32_t secp256k1_testrand_bits(int bits);
+SECP256K1_INLINE static uint64_t secp256k1_testrand_bits(int bits);
/** Generate a pseudorandom number in the range [0..range-1]. */
static uint32_t secp256k1_testrand_int(uint32_t range);
diff --git a/src/secp256k1/src/testrand_impl.h b/src/secp256k1/src/testrand_impl.h
index c8d30ef6a8..e9b9d7ded4 100644
--- a/src/secp256k1/src/testrand_impl.h
+++ b/src/secp256k1/src/testrand_impl.h
@@ -14,37 +14,64 @@
#include "testrand.h"
#include "hash.h"
-static secp256k1_rfc6979_hmac_sha256 secp256k1_test_rng;
-static uint32_t secp256k1_test_rng_precomputed[8];
-static int secp256k1_test_rng_precomputed_used = 8;
+static uint64_t secp256k1_test_state[4];
static uint64_t secp256k1_test_rng_integer;
static int secp256k1_test_rng_integer_bits_left = 0;
SECP256K1_INLINE static void secp256k1_testrand_seed(const unsigned char *seed16) {
- secp256k1_rfc6979_hmac_sha256_initialize(&secp256k1_test_rng, seed16, 16);
+ static const unsigned char PREFIX[19] = "secp256k1 test init";
+ unsigned char out32[32];
+ secp256k1_sha256 hash;
+ int i;
+
+ /* Use SHA256(PREFIX || seed16) as initial state. */
+ secp256k1_sha256_initialize(&hash);
+ secp256k1_sha256_write(&hash, PREFIX, sizeof(PREFIX));
+ secp256k1_sha256_write(&hash, seed16, 16);
+ secp256k1_sha256_finalize(&hash, out32);
+ for (i = 0; i < 4; ++i) {
+ uint64_t s = 0;
+ int j;
+ for (j = 0; j < 8; ++j) s = (s << 8) | out32[8*i + j];
+ secp256k1_test_state[i] = s;
+ }
+ secp256k1_test_rng_integer_bits_left = 0;
}
-SECP256K1_INLINE static uint32_t secp256k1_testrand32(void) {
- if (secp256k1_test_rng_precomputed_used == 8) {
- secp256k1_rfc6979_hmac_sha256_generate(&secp256k1_test_rng, (unsigned char*)(&secp256k1_test_rng_precomputed[0]), sizeof(secp256k1_test_rng_precomputed));
- secp256k1_test_rng_precomputed_used = 0;
- }
- return secp256k1_test_rng_precomputed[secp256k1_test_rng_precomputed_used++];
+SECP256K1_INLINE static uint64_t rotl(const uint64_t x, int k) {
+ return (x << k) | (x >> (64 - k));
+}
+
+SECP256K1_INLINE static uint64_t secp256k1_testrand64(void) {
+ /* Test-only Xoshiro256++ RNG. See https://prng.di.unimi.it/ */
+ const uint64_t result = rotl(secp256k1_test_state[0] + secp256k1_test_state[3], 23) + secp256k1_test_state[0];
+ const uint64_t t = secp256k1_test_state[1] << 17;
+ secp256k1_test_state[2] ^= secp256k1_test_state[0];
+ secp256k1_test_state[3] ^= secp256k1_test_state[1];
+ secp256k1_test_state[1] ^= secp256k1_test_state[2];
+ secp256k1_test_state[0] ^= secp256k1_test_state[3];
+ secp256k1_test_state[2] ^= t;
+ secp256k1_test_state[3] = rotl(secp256k1_test_state[3], 45);
+ return result;
}
-static uint32_t secp256k1_testrand_bits(int bits) {
- uint32_t ret;
+SECP256K1_INLINE static uint64_t secp256k1_testrand_bits(int bits) {
+ uint64_t ret;
if (secp256k1_test_rng_integer_bits_left < bits) {
- secp256k1_test_rng_integer |= (((uint64_t)secp256k1_testrand32()) << secp256k1_test_rng_integer_bits_left);
- secp256k1_test_rng_integer_bits_left += 32;
+ secp256k1_test_rng_integer = secp256k1_testrand64();
+ secp256k1_test_rng_integer_bits_left = 64;
}
ret = secp256k1_test_rng_integer;
secp256k1_test_rng_integer >>= bits;
secp256k1_test_rng_integer_bits_left -= bits;
- ret &= ((~((uint32_t)0)) >> (32 - bits));
+ ret &= ((~((uint64_t)0)) >> (64 - bits));
return ret;
}
+SECP256K1_INLINE static uint32_t secp256k1_testrand32(void) {
+ return secp256k1_testrand_bits(32);
+}
+
static uint32_t secp256k1_testrand_int(uint32_t range) {
/* We want a uniform integer between 0 and range-1, inclusive.
* B is the smallest number such that range <= 2**B.
@@ -85,7 +112,19 @@ static uint32_t secp256k1_testrand_int(uint32_t range) {
}
static void secp256k1_testrand256(unsigned char *b32) {
- secp256k1_rfc6979_hmac_sha256_generate(&secp256k1_test_rng, b32, 32);
+ int i;
+ for (i = 0; i < 4; ++i) {
+ uint64_t val = secp256k1_testrand64();
+ b32[0] = val;
+ b32[1] = val >> 8;
+ b32[2] = val >> 16;
+ b32[3] = val >> 24;
+ b32[4] = val >> 32;
+ b32[5] = val >> 40;
+ b32[6] = val >> 48;
+ b32[7] = val >> 56;
+ b32 += 8;
+ }
}
static void secp256k1_testrand_bytes_test(unsigned char *bytes, size_t len) {
@@ -109,7 +148,7 @@ static void secp256k1_testrand256_test(unsigned char *b32) {
}
static void secp256k1_testrand_flip(unsigned char *b, size_t len) {
- b[secp256k1_testrand_int(len)] ^= (1 << secp256k1_testrand_int(8));
+ b[secp256k1_testrand_int(len)] ^= (1 << secp256k1_testrand_bits(3));
}
static void secp256k1_testrand_init(const char* hexseed) {
diff --git a/src/secp256k1/src/tests.c b/src/secp256k1/src/tests.c
index 712fc655fa..dd53173930 100644
--- a/src/secp256k1/src/tests.c
+++ b/src/secp256k1/src/tests.c
@@ -28,6 +28,8 @@
#include "modinv64_impl.h"
#endif
+#define CONDITIONAL_TEST(cnt, nam) if (count < (cnt)) { printf("Skipping %s (iteration count too low)\n", nam); } else
+
static int count = 64;
static secp256k1_context *ctx = NULL;
@@ -100,6 +102,12 @@ void random_group_element_jacobian_test(secp256k1_gej *gej, const secp256k1_ge *
gej->infinity = ge->infinity;
}
+void random_gej_test(secp256k1_gej *gej) {
+ secp256k1_ge ge;
+ random_group_element_test(&ge);
+ random_group_element_jacobian_test(gej, &ge);
+}
+
void random_scalar_order_test(secp256k1_scalar *num) {
do {
unsigned char b32[32];
@@ -443,14 +451,18 @@ void run_ctz_tests(void) {
/***** HASH TESTS *****/
-void run_sha256_tests(void) {
- static const char *inputs[8] = {
+void run_sha256_known_output_tests(void) {
+ static const char *inputs[] = {
"", "abc", "message digest", "secure hash algorithm", "SHA256 is considered to be safe",
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"For this sample, this 63-byte string will be used as input data",
- "This is exactly 64 bytes long, not counting the terminating byte"
+ "This is exactly 64 bytes long, not counting the terminating byte",
+ "aaaaa",
};
- static const unsigned char outputs[8][32] = {
+ static const unsigned int repeat[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1000000/5
+ };
+ static const unsigned char outputs[][32] = {
{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55},
{0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad},
{0xf7, 0x84, 0x6f, 0x55, 0xcf, 0x23, 0xe1, 0x4e, 0xeb, 0xea, 0xb5, 0xb4, 0xe1, 0x55, 0x0c, 0xad, 0x5b, 0x50, 0x9e, 0x33, 0x48, 0xfb, 0xc4, 0xef, 0xa3, 0xa1, 0x41, 0x3d, 0x39, 0x3c, 0xb6, 0x50},
@@ -458,27 +470,146 @@ void run_sha256_tests(void) {
{0x68, 0x19, 0xd9, 0x15, 0xc7, 0x3f, 0x4d, 0x1e, 0x77, 0xe4, 0xe1, 0xb5, 0x2d, 0x1f, 0xa0, 0xf9, 0xcf, 0x9b, 0xea, 0xea, 0xd3, 0x93, 0x9f, 0x15, 0x87, 0x4b, 0xd9, 0x88, 0xe2, 0xa2, 0x36, 0x30},
{0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1},
{0xf0, 0x8a, 0x78, 0xcb, 0xba, 0xee, 0x08, 0x2b, 0x05, 0x2a, 0xe0, 0x70, 0x8f, 0x32, 0xfa, 0x1e, 0x50, 0xc5, 0xc4, 0x21, 0xaa, 0x77, 0x2b, 0xa5, 0xdb, 0xb4, 0x06, 0xa2, 0xea, 0x6b, 0xe3, 0x42},
- {0xab, 0x64, 0xef, 0xf7, 0xe8, 0x8e, 0x2e, 0x46, 0x16, 0x5e, 0x29, 0xf2, 0xbc, 0xe4, 0x18, 0x26, 0xbd, 0x4c, 0x7b, 0x35, 0x52, 0xf6, 0xb3, 0x82, 0xa9, 0xe7, 0xd3, 0xaf, 0x47, 0xc2, 0x45, 0xf8}
+ {0xab, 0x64, 0xef, 0xf7, 0xe8, 0x8e, 0x2e, 0x46, 0x16, 0x5e, 0x29, 0xf2, 0xbc, 0xe4, 0x18, 0x26, 0xbd, 0x4c, 0x7b, 0x35, 0x52, 0xf6, 0xb3, 0x82, 0xa9, 0xe7, 0xd3, 0xaf, 0x47, 0xc2, 0x45, 0xf8},
+ {0xcd, 0xc7, 0x6e, 0x5c, 0x99, 0x14, 0xfb, 0x92, 0x81, 0xa1, 0xc7, 0xe2, 0x84, 0xd7, 0x3e, 0x67, 0xf1, 0x80, 0x9a, 0x48, 0xa4, 0x97, 0x20, 0x0e, 0x04, 0x6d, 0x39, 0xcc, 0xc7, 0x11, 0x2c, 0xd0},
};
- int i;
- for (i = 0; i < 8; i++) {
+ unsigned int i, ninputs;
+
+ /* Skip last input vector for low iteration counts */
+ ninputs = sizeof(inputs)/sizeof(inputs[0]) - 1;
+ CONDITIONAL_TEST(16, "run_sha256_known_output_tests 1000000") ninputs++;
+
+ for (i = 0; i < ninputs; i++) {
unsigned char out[32];
secp256k1_sha256 hasher;
+ unsigned int j;
+ /* 1. Run: simply write the input bytestrings */
+ j = repeat[i];
secp256k1_sha256_initialize(&hasher);
- secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), strlen(inputs[i]));
+ while (j > 0) {
+ secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), strlen(inputs[i]));
+ j--;
+ }
secp256k1_sha256_finalize(&hasher, out);
CHECK(secp256k1_memcmp_var(out, outputs[i], 32) == 0);
+ /* 2. Run: split the input bytestrings randomly before writing */
if (strlen(inputs[i]) > 0) {
int split = secp256k1_testrand_int(strlen(inputs[i]));
secp256k1_sha256_initialize(&hasher);
- secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), split);
- secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i] + split), strlen(inputs[i]) - split);
+ j = repeat[i];
+ while (j > 0) {
+ secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), split);
+ secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i] + split), strlen(inputs[i]) - split);
+ j--;
+ }
secp256k1_sha256_finalize(&hasher, out);
CHECK(secp256k1_memcmp_var(out, outputs[i], 32) == 0);
}
}
}
+/** SHA256 counter tests
+
+The tests verify that the SHA256 counter doesn't wrap around at message length
+2^i bytes for i = 20, ..., 33. This wide range aims at being independent of the
+implementation of the counter and it catches multiple natural 32-bit overflows
+(e.g., counting bits, counting bytes, counting blocks, ...).
+
+The test vectors have been generated using following Python script which relies
+on https://github.com/cloudtools/sha256/ (v0.3 on Python v3.10.2).
+
+```
+from sha256 import sha256
+from copy import copy
+
+def midstate_c_definition(hasher):
+ ret = ' {{0x' + hasher.state[0].hex('_', 4).replace('_', ', 0x') + '},\n'
+ ret += ' {0x00}, ' + str(hex(hasher.state[1])) + '}'
+ return ret
+
+def output_c_literal(hasher):
+ return '{0x' + hasher.digest().hex('_').replace('_', ', 0x') + '}'
+
+MESSAGE = b'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno'
+assert(len(MESSAGE) == 64)
+BYTE_BOUNDARIES = [(2**b)//len(MESSAGE) - 1 for b in range(20, 34)]
+
+midstates = []
+digests = []
+hasher = sha256()
+for i in range(BYTE_BOUNDARIES[-1] + 1):
+ if i in BYTE_BOUNDARIES:
+ midstates.append(midstate_c_definition(hasher))
+ hasher_copy = copy(hasher)
+ hasher_copy.update(MESSAGE)
+ digests.append(output_c_literal(hasher_copy))
+ hasher.update(MESSAGE)
+
+for x in midstates:
+ print(x + ',')
+
+for x in digests:
+ print(x + ',')
+```
+*/
+void run_sha256_counter_tests(void) {
+ static const char *input = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno";
+ static const secp256k1_sha256 midstates[] = {
+ {{0xa2b5c8bb, 0x26c88bb3, 0x2abdc3d2, 0x9def99a3, 0xdfd21a6e, 0x41fe585b, 0x7ef2c440, 0x2b79adda},
+ {0x00}, 0xfffc0},
+ {{0xa0d29445, 0x9287de66, 0x76aabd71, 0x41acd765, 0x0c7528b4, 0x84e14906, 0x942faec6, 0xcc5a7b26},
+ {0x00}, 0x1fffc0},
+ {{0x50449526, 0xb9f1d657, 0xa0fc13e9, 0x50860f10, 0xa550c431, 0x3fbc97c1, 0x7bbb2d89, 0xdb67bac1},
+ {0x00}, 0x3fffc0},
+ {{0x54a6efdc, 0x46762e7b, 0x88bfe73f, 0xbbd149c7, 0x41620c43, 0x1168da7b, 0x2c5960f9, 0xeccffda6},
+ {0x00}, 0x7fffc0},
+ {{0x2515a8f5, 0x5faa2977, 0x3a850486, 0xac858cad, 0x7b7276ee, 0x235c0385, 0xc53a157c, 0x7cb3e69c},
+ {0x00}, 0xffffc0},
+ {{0x34f39828, 0x409fedb7, 0x4bbdd0fb, 0x3b643634, 0x7806bf2e, 0xe0d1b713, 0xca3f2e1e, 0xe38722c2},
+ {0x00}, 0x1ffffc0},
+ {{0x389ef5c5, 0x38c54167, 0x8f5d56ab, 0x582a75cc, 0x8217caef, 0xf10947dd, 0x6a1998a8, 0x048f0b8c},
+ {0x00}, 0x3ffffc0},
+ {{0xd6c3f394, 0x0bee43b9, 0x6783f497, 0x29fa9e21, 0x6ce491c1, 0xa81fe45e, 0x2fc3859a, 0x269012d0},
+ {0x00}, 0x7ffffc0},
+ {{0x6dd3c526, 0x44d88aa0, 0x806a1bae, 0xfbcc0d32, 0x9d6144f3, 0x9d2bd757, 0x9851a957, 0xb50430ad},
+ {0x00}, 0xfffffc0},
+ {{0x2add4021, 0xdfe8a9e6, 0xa56317c6, 0x7a15f5bb, 0x4a48aacd, 0x5d368414, 0x4f00e6f0, 0xd9355023},
+ {0x00}, 0x1fffffc0},
+ {{0xb66666b4, 0xdbeac32b, 0x0ea351ae, 0xcba9da46, 0x6278b874, 0x8c508e23, 0xe16ca776, 0x8465bac1},
+ {0x00}, 0x3fffffc0},
+ {{0xb6744789, 0x9cce87aa, 0xc4c478b7, 0xf38404d8, 0x2e38ba62, 0xa3f7019b, 0x50458fe7, 0x3047dbec},
+ {0x00}, 0x7fffffc0},
+ {{0x8b1297ba, 0xba261a80, 0x2ba1b0dd, 0xfbc67d6d, 0x61072c4e, 0x4b5a2a0f, 0x52872760, 0x2dfeb162},
+ {0x00}, 0xffffffc0},
+ {{0x24f33cf7, 0x41ad6583, 0x41c8ff5d, 0xca7ef35f, 0x50395756, 0x021b743e, 0xd7126cd7, 0xd037473a},
+ {0x00}, 0x1ffffffc0},
+ };
+ static const unsigned char outputs[][32] = {
+ {0x0e, 0x83, 0xe2, 0xc9, 0x4f, 0xb2, 0xb8, 0x2b, 0x89, 0x06, 0x92, 0x78, 0x04, 0x03, 0x48, 0x5c, 0x48, 0x44, 0x67, 0x61, 0x77, 0xa4, 0xc7, 0x90, 0x9e, 0x92, 0x55, 0x10, 0x05, 0xfe, 0x39, 0x15},
+ {0x1d, 0x1e, 0xd7, 0xb8, 0xa3, 0xa7, 0x8a, 0x79, 0xfd, 0xa0, 0x05, 0x08, 0x9c, 0xeb, 0xf0, 0xec, 0x67, 0x07, 0x9f, 0x8e, 0x3c, 0x0d, 0x8e, 0xf9, 0x75, 0x55, 0x13, 0xc1, 0xe8, 0x77, 0xf8, 0xbb},
+ {0x66, 0x95, 0x6c, 0xc9, 0xe0, 0x39, 0x65, 0xb6, 0xb0, 0x05, 0xd1, 0xaf, 0xaf, 0xf3, 0x1d, 0xb9, 0xa4, 0xda, 0x6f, 0x20, 0xcd, 0x3a, 0xae, 0x64, 0xc2, 0xdb, 0xee, 0xf5, 0xb8, 0x8d, 0x57, 0x0e},
+ {0x3c, 0xbb, 0x1c, 0x12, 0x5e, 0x17, 0xfd, 0x54, 0x90, 0x45, 0xa7, 0x7b, 0x61, 0x6c, 0x1d, 0xfe, 0xe6, 0xcc, 0x7f, 0xee, 0xcf, 0xef, 0x33, 0x35, 0x50, 0x62, 0x16, 0x70, 0x2f, 0x87, 0xc3, 0xc9},
+ {0x53, 0x4d, 0xa8, 0xe7, 0x1e, 0x98, 0x73, 0x8d, 0xd9, 0xa3, 0x54, 0xa5, 0x0e, 0x59, 0x2c, 0x25, 0x43, 0x6f, 0xaa, 0xa2, 0xf5, 0x21, 0x06, 0x3e, 0xc9, 0x82, 0x06, 0x94, 0x98, 0x72, 0x9d, 0xa7},
+ {0xef, 0x7e, 0xe9, 0x6b, 0xd3, 0xe5, 0xb7, 0x41, 0x4c, 0xc8, 0xd3, 0x07, 0x52, 0x9a, 0x5a, 0x8b, 0x4e, 0x1e, 0x75, 0xa4, 0x17, 0x78, 0xc8, 0x36, 0xcd, 0xf8, 0x2e, 0xd9, 0x57, 0xe3, 0xd7, 0x07},
+ {0x87, 0x16, 0xfb, 0xf9, 0xa5, 0xf8, 0xc4, 0x56, 0x2b, 0x48, 0x52, 0x8e, 0x2d, 0x30, 0x85, 0xb6, 0x4c, 0x56, 0xb5, 0xd1, 0x16, 0x9c, 0xcf, 0x32, 0x95, 0xad, 0x03, 0xe8, 0x05, 0x58, 0x06, 0x76},
+ {0x75, 0x03, 0x80, 0x28, 0xf2, 0xa7, 0x63, 0x22, 0x1a, 0x26, 0x9c, 0x68, 0xe0, 0x58, 0xfc, 0x73, 0xeb, 0x42, 0xf6, 0x86, 0x16, 0x24, 0x4b, 0xbc, 0x24, 0xf7, 0x02, 0xc8, 0x3d, 0x90, 0xe2, 0xb0},
+ {0xdf, 0x49, 0x0f, 0x15, 0x7b, 0x7d, 0xbf, 0xe0, 0xd4, 0xcf, 0x47, 0xc0, 0x80, 0x93, 0x4a, 0x61, 0xaa, 0x03, 0x07, 0x66, 0xb3, 0x38, 0x5d, 0xc8, 0xc9, 0x07, 0x61, 0xfb, 0x97, 0x10, 0x2f, 0xd8},
+ {0x77, 0x19, 0x40, 0x56, 0x41, 0xad, 0xbc, 0x59, 0xda, 0x1e, 0xc5, 0x37, 0x14, 0x63, 0x7b, 0xfb, 0x79, 0xe2, 0x7a, 0xb1, 0x55, 0x42, 0x99, 0x42, 0x56, 0xfe, 0x26, 0x9d, 0x0f, 0x7e, 0x80, 0xc6},
+ {0x50, 0xe7, 0x2a, 0x0e, 0x26, 0x44, 0x2f, 0xe2, 0x55, 0x2d, 0xc3, 0x93, 0x8a, 0xc5, 0x86, 0x58, 0x22, 0x8c, 0x0c, 0xbf, 0xb1, 0xd2, 0xca, 0x87, 0x2a, 0xe4, 0x35, 0x26, 0x6f, 0xcd, 0x05, 0x5e},
+ {0xe4, 0x80, 0x6f, 0xdb, 0x3d, 0x7d, 0xba, 0xde, 0x50, 0x3f, 0xea, 0x00, 0x3d, 0x46, 0x59, 0x64, 0xfd, 0x58, 0x1c, 0xa1, 0xb8, 0x7d, 0x5f, 0xac, 0x94, 0x37, 0x9e, 0xa0, 0xc0, 0x9c, 0x93, 0x8b},
+ {0x2c, 0xf3, 0xa9, 0xf6, 0x15, 0x25, 0x80, 0x70, 0x76, 0x99, 0x7d, 0xf1, 0xc3, 0x2f, 0xa3, 0x31, 0xff, 0x92, 0x35, 0x2e, 0x8d, 0x04, 0x13, 0x33, 0xd8, 0x0d, 0xdb, 0x4a, 0xf6, 0x8c, 0x03, 0x34},
+ {0xec, 0x12, 0x24, 0x9f, 0x35, 0xa4, 0x29, 0x8b, 0x9e, 0x4a, 0x95, 0xf8, 0x61, 0xaf, 0x61, 0xc5, 0x66, 0x55, 0x3e, 0x3f, 0x2a, 0x98, 0xea, 0x71, 0x16, 0x6b, 0x1c, 0xd9, 0xe4, 0x09, 0xd2, 0x8e},
+ };
+ unsigned int i;
+ for (i = 0; i < sizeof(midstates)/sizeof(midstates[0]); i++) {
+ unsigned char out[32];
+ secp256k1_sha256 hasher = midstates[i];
+ secp256k1_sha256_write(&hasher, (const unsigned char*)input, strlen(input));
+ secp256k1_sha256_finalize(&hasher, out);
+ CHECK(secp256k1_memcmp_var(out, outputs[i], 32) == 0);
+ }
+}
+
void run_hmac_sha256_tests(void) {
static const char *keys[6] = {
"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
@@ -790,7 +921,7 @@ void signed30_to_uint16(uint16_t* out, const secp256k1_modinv32_signed30* in) {
void mutate_sign_signed30(secp256k1_modinv32_signed30* x) {
int i;
for (i = 0; i < 16; ++i) {
- int pos = secp256k1_testrand_int(8);
+ int pos = secp256k1_testrand_bits(3);
if (x->v[pos] > 0 && x->v[pos + 1] <= 0x3fffffff) {
x->v[pos] -= 0x40000000;
x->v[pos + 1] += 1;
@@ -862,7 +993,7 @@ void mutate_sign_signed62(secp256k1_modinv64_signed62* x) {
static const int64_t M62 = (int64_t)(UINT64_MAX >> 2);
int i;
for (i = 0; i < 8; ++i) {
- int pos = secp256k1_testrand_int(4);
+ int pos = secp256k1_testrand_bits(2);
if (x->v[pos] > 0 && x->v[pos + 1] <= M62) {
x->v[pos] -= (M62 + 1);
x->v[pos + 1] += 1;
@@ -2451,13 +2582,65 @@ void run_field_convert(void) {
CHECK(secp256k1_memcmp_var(&fes2, &fes, sizeof(fes)) == 0);
}
-int fe_secp256k1_memcmp_var(const secp256k1_fe *a, const secp256k1_fe *b) {
- secp256k1_fe t = *b;
+/* Returns true if two field elements have the same representation. */
+int fe_identical(const secp256k1_fe *a, const secp256k1_fe *b) {
+ int ret = 1;
#ifdef VERIFY
- t.magnitude = a->magnitude;
- t.normalized = a->normalized;
+ ret &= (a->magnitude == b->magnitude);
+ ret &= (a->normalized == b->normalized);
#endif
- return secp256k1_memcmp_var(a, &t, sizeof(secp256k1_fe));
+ /* Compare the struct member that holds the limbs. */
+ ret &= (secp256k1_memcmp_var(a->n, b->n, sizeof(a->n)) == 0);
+ return ret;
+}
+
+void run_field_half(void) {
+ secp256k1_fe t, u;
+ int m;
+
+ /* Check magnitude 0 input */
+ secp256k1_fe_get_bounds(&t, 0);
+ secp256k1_fe_half(&t);
+#ifdef VERIFY
+ CHECK(t.magnitude == 1);
+ CHECK(t.normalized == 0);
+#endif
+ CHECK(secp256k1_fe_normalizes_to_zero(&t));
+
+ /* Check non-zero magnitudes in the supported range */
+ for (m = 1; m < 32; m++) {
+ /* Check max-value input */
+ secp256k1_fe_get_bounds(&t, m);
+
+ u = t;
+ secp256k1_fe_half(&u);
+#ifdef VERIFY
+ CHECK(u.magnitude == (m >> 1) + 1);
+ CHECK(u.normalized == 0);
+#endif
+ secp256k1_fe_normalize_weak(&u);
+ secp256k1_fe_add(&u, &u);
+ CHECK(check_fe_equal(&t, &u));
+
+ /* Check worst-case input: ensure the LSB is 1 so that P will be added,
+ * which will also cause all carries to be 1, since all limbs that can
+ * generate a carry are initially even and all limbs of P are odd in
+ * every existing field implementation. */
+ secp256k1_fe_get_bounds(&t, m);
+ CHECK(t.n[0] > 0);
+ CHECK((t.n[0] & 1) == 0);
+ --t.n[0];
+
+ u = t;
+ secp256k1_fe_half(&u);
+#ifdef VERIFY
+ CHECK(u.magnitude == (m >> 1) + 1);
+ CHECK(u.normalized == 0);
+#endif
+ secp256k1_fe_normalize_weak(&u);
+ secp256k1_fe_add(&u, &u);
+ CHECK(check_fe_equal(&t, &u));
+ }
}
void run_field_misc(void) {
@@ -2467,9 +2650,13 @@ void run_field_misc(void) {
secp256k1_fe q;
secp256k1_fe fe5 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 5);
int i, j;
- for (i = 0; i < 5*count; i++) {
+ for (i = 0; i < 1000 * count; i++) {
secp256k1_fe_storage xs, ys, zs;
- random_fe(&x);
+ if (i & 1) {
+ random_fe(&x);
+ } else {
+ random_fe_test(&x);
+ }
random_fe_non_zero(&y);
/* Test the fe equality and comparison operations. */
CHECK(secp256k1_fe_cmp_var(&x, &x) == 0);
@@ -2483,13 +2670,13 @@ void run_field_misc(void) {
CHECK(x.normalized && x.magnitude == 1);
#endif
secp256k1_fe_cmov(&x, &x, 1);
- CHECK(fe_secp256k1_memcmp_var(&x, &z) != 0);
- CHECK(fe_secp256k1_memcmp_var(&x, &q) == 0);
+ CHECK(!fe_identical(&x, &z));
+ CHECK(fe_identical(&x, &q));
secp256k1_fe_cmov(&q, &z, 1);
#ifdef VERIFY
CHECK(!q.normalized && q.magnitude == z.magnitude);
#endif
- CHECK(fe_secp256k1_memcmp_var(&q, &z) == 0);
+ CHECK(fe_identical(&q, &z));
secp256k1_fe_normalize_var(&x);
secp256k1_fe_normalize_var(&z);
CHECK(!secp256k1_fe_equal_var(&x, &z));
@@ -2537,6 +2724,14 @@ void run_field_misc(void) {
secp256k1_fe_add(&q, &x);
CHECK(check_fe_equal(&y, &z));
CHECK(check_fe_equal(&q, &y));
+ /* Check secp256k1_fe_half. */
+ z = x;
+ secp256k1_fe_half(&z);
+ secp256k1_fe_add(&z, &z);
+ CHECK(check_fe_equal(&x, &z));
+ secp256k1_fe_add(&z, &z);
+ secp256k1_fe_half(&z);
+ CHECK(check_fe_equal(&x, &z));
}
}
@@ -3338,6 +3533,37 @@ void run_ge(void) {
test_intialized_inf();
}
+void test_gej_cmov(const secp256k1_gej *a, const secp256k1_gej *b) {
+ secp256k1_gej t = *a;
+ secp256k1_gej_cmov(&t, b, 0);
+ CHECK(gej_xyz_equals_gej(&t, a));
+ secp256k1_gej_cmov(&t, b, 1);
+ CHECK(gej_xyz_equals_gej(&t, b));
+}
+
+void run_gej(void) {
+ int i;
+ secp256k1_gej a, b;
+
+ /* Tests for secp256k1_gej_cmov */
+ for (i = 0; i < count; i++) {
+ secp256k1_gej_set_infinity(&a);
+ secp256k1_gej_set_infinity(&b);
+ test_gej_cmov(&a, &b);
+
+ random_gej_test(&a);
+ test_gej_cmov(&a, &b);
+ test_gej_cmov(&b, &a);
+
+ b = a;
+ test_gej_cmov(&a, &b);
+
+ random_gej_test(&b);
+ test_gej_cmov(&a, &b);
+ test_gej_cmov(&b, &a);
+ }
+}
+
void test_ec_combine(void) {
secp256k1_scalar sum = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0);
secp256k1_pubkey data[6];
@@ -4052,6 +4278,174 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e
}
}
+int test_ecmult_multi_random(secp256k1_scratch *scratch) {
+ /* Large random test for ecmult_multi_* functions which exercises:
+ * - Few or many inputs (0 up to 128, roughly exponentially distributed).
+ * - Few or many 0*P or a*INF inputs (roughly uniformly distributed).
+ * - Including or excluding an nonzero a*G term (or such a term at all).
+ * - Final expected result equal to infinity or not (roughly 50%).
+ * - ecmult_multi_var, ecmult_strauss_single_batch, ecmult_pippenger_single_batch
+ */
+
+ /* These 4 variables define the eventual input to the ecmult_multi function.
+ * g_scalar is the G scalar fed to it (or NULL, possibly, if g_scalar=0), and
+ * scalars[0..filled-1] and gejs[0..filled-1] are the scalars and points
+ * which form its normal inputs. */
+ int filled = 0;
+ secp256k1_scalar g_scalar = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0);
+ secp256k1_scalar scalars[128];
+ secp256k1_gej gejs[128];
+ /* The expected result, and the computed result. */
+ secp256k1_gej expected, computed;
+ /* Temporaries. */
+ secp256k1_scalar sc_tmp;
+ secp256k1_ge ge_tmp;
+ /* Variables needed for the actual input to ecmult_multi. */
+ secp256k1_ge ges[128];
+ ecmult_multi_data data;
+
+ int i;
+ /* Which multiplication function to use */
+ int fn = secp256k1_testrand_int(3);
+ secp256k1_ecmult_multi_func ecmult_multi = fn == 0 ? secp256k1_ecmult_multi_var :
+ fn == 1 ? secp256k1_ecmult_strauss_batch_single :
+ secp256k1_ecmult_pippenger_batch_single;
+ /* Simulate exponentially distributed num. */
+ int num_bits = 2 + secp256k1_testrand_int(6);
+ /* Number of (scalar, point) inputs (excluding g). */
+ int num = secp256k1_testrand_int((1 << num_bits) + 1);
+ /* Number of those which are nonzero. */
+ int num_nonzero = secp256k1_testrand_int(num + 1);
+ /* Whether we're aiming to create an input with nonzero expected result. */
+ int nonzero_result = secp256k1_testrand_bits(1);
+ /* Whether we will provide nonzero g multiplicand. In some cases our hand
+ * is forced here based on num_nonzero and nonzero_result. */
+ int g_nonzero = num_nonzero == 0 ? nonzero_result :
+ num_nonzero == 1 && !nonzero_result ? 1 :
+ (int)secp256k1_testrand_bits(1);
+ /* Which g_scalar pointer to pass into ecmult_multi(). */
+ const secp256k1_scalar* g_scalar_ptr = (g_nonzero || secp256k1_testrand_bits(1)) ? &g_scalar : NULL;
+ /* How many EC multiplications were performed in this function. */
+ int mults = 0;
+ /* How many randomization steps to apply to the input list. */
+ int rands = (int)secp256k1_testrand_bits(3);
+ if (rands > num_nonzero) rands = num_nonzero;
+
+ secp256k1_gej_set_infinity(&expected);
+ secp256k1_gej_set_infinity(&gejs[0]);
+ secp256k1_scalar_set_int(&scalars[0], 0);
+
+ if (g_nonzero) {
+ /* If g_nonzero, set g_scalar to nonzero value r. */
+ random_scalar_order_test(&g_scalar);
+ if (!nonzero_result) {
+ /* If expected=0 is desired, add a (a*r, -(1/a)*g) term to compensate. */
+ CHECK(num_nonzero > filled);
+ random_scalar_order_test(&sc_tmp);
+ secp256k1_scalar_mul(&scalars[filled], &sc_tmp, &g_scalar);
+ secp256k1_scalar_inverse_var(&sc_tmp, &sc_tmp);
+ secp256k1_scalar_negate(&sc_tmp, &sc_tmp);
+ secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &gejs[filled], &sc_tmp);
+ ++filled;
+ ++mults;
+ }
+ }
+
+ if (nonzero_result && filled < num_nonzero) {
+ /* If a nonzero result is desired, and there is space, add a random nonzero term. */
+ random_scalar_order_test(&scalars[filled]);
+ random_group_element_test(&ge_tmp);
+ secp256k1_gej_set_ge(&gejs[filled], &ge_tmp);
+ ++filled;
+ }
+
+ if (nonzero_result) {
+ /* Compute the expected result using normal ecmult. */
+ CHECK(filled <= 1);
+ secp256k1_ecmult(&expected, &gejs[0], &scalars[0], &g_scalar);
+ mults += filled + g_nonzero;
+ }
+
+ /* At this point we have expected = scalar_g*G + sum(scalars[i]*gejs[i] for i=0..filled-1). */
+ CHECK(filled <= 1 + !nonzero_result);
+ CHECK(filled <= num_nonzero);
+
+ /* Add entries to scalars,gejs so that there are num of them. All the added entries
+ * either have scalar=0 or point=infinity, so these do not change the expected result. */
+ while (filled < num) {
+ if (secp256k1_testrand_bits(1)) {
+ secp256k1_gej_set_infinity(&gejs[filled]);
+ random_scalar_order_test(&scalars[filled]);
+ } else {
+ secp256k1_scalar_set_int(&scalars[filled], 0);
+ random_group_element_test(&ge_tmp);
+ secp256k1_gej_set_ge(&gejs[filled], &ge_tmp);
+ }
+ ++filled;
+ }
+
+ /* Now perform cheapish transformations on gejs and scalars, for indices
+ * 0..num_nonzero-1, which do not change the expected result, but may
+ * convert some of them to be both non-0-scalar and non-infinity-point. */
+ for (i = 0; i < rands; ++i) {
+ int j;
+ secp256k1_scalar v, iv;
+ /* Shuffle the entries. */
+ for (j = 0; j < num_nonzero; ++j) {
+ int k = secp256k1_testrand_int(num_nonzero - j);
+ if (k != 0) {
+ secp256k1_gej gej = gejs[j];
+ secp256k1_scalar sc = scalars[j];
+ gejs[j] = gejs[j + k];
+ scalars[j] = scalars[j + k];
+ gejs[j + k] = gej;
+ scalars[j + k] = sc;
+ }
+ }
+ /* Perturb all consecutive pairs of inputs:
+ * a*P + b*Q -> (a+b)*P + b*(Q-P). */
+ for (j = 0; j + 1 < num_nonzero; j += 2) {
+ secp256k1_gej gej;
+ secp256k1_scalar_add(&scalars[j], &scalars[j], &scalars[j+1]);
+ secp256k1_gej_neg(&gej, &gejs[j]);
+ secp256k1_gej_add_var(&gejs[j+1], &gejs[j+1], &gej, NULL);
+ }
+ /* Transform the last input: a*P -> (v*a) * ((1/v)*P). */
+ CHECK(num_nonzero >= 1);
+ random_scalar_order_test(&v);
+ secp256k1_scalar_inverse(&iv, &v);
+ secp256k1_scalar_mul(&scalars[num_nonzero - 1], &scalars[num_nonzero - 1], &v);
+ secp256k1_ecmult(&gejs[num_nonzero - 1], &gejs[num_nonzero - 1], &iv, NULL);
+ ++mults;
+ }
+
+ /* Shuffle all entries (0..num-1). */
+ for (i = 0; i < num; ++i) {
+ int j = secp256k1_testrand_int(num - i);
+ if (j != 0) {
+ secp256k1_gej gej = gejs[i];
+ secp256k1_scalar sc = scalars[i];
+ gejs[i] = gejs[i + j];
+ scalars[i] = scalars[i + j];
+ gejs[i + j] = gej;
+ scalars[i + j] = sc;
+ }
+ }
+
+ /* Compute affine versions of all inputs. */
+ secp256k1_ge_set_all_gej_var(ges, gejs, filled);
+ /* Invoke ecmult_multi code. */
+ data.sc = scalars;
+ data.pt = ges;
+ CHECK(ecmult_multi(&ctx->error_callback, scratch, &computed, g_scalar_ptr, ecmult_multi_callback, &data, filled));
+ mults += num_nonzero + g_nonzero;
+ /* Compare with expected result. */
+ secp256k1_gej_neg(&computed, &computed);
+ secp256k1_gej_add_var(&computed, &computed, &expected, NULL);
+ CHECK(secp256k1_gej_is_infinity(&computed));
+ return mults;
+}
+
void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) {
secp256k1_scalar szero;
secp256k1_scalar sc;
@@ -4093,7 +4487,7 @@ void test_secp256k1_pippenger_bucket_window_inv(void) {
* for a given scratch space.
*/
void test_ecmult_multi_pippenger_max_points(void) {
- size_t scratch_size = secp256k1_testrand_int(256);
+ size_t scratch_size = secp256k1_testrand_bits(8);
size_t max_size = secp256k1_pippenger_scratch_size(secp256k1_pippenger_bucket_window_inv(PIPPENGER_MAX_BUCKET_WINDOW-1)+512, 12);
secp256k1_scratch *scratch;
size_t n_points_supported;
@@ -4242,6 +4636,7 @@ void test_ecmult_multi_batching(void) {
void run_ecmult_multi_tests(void) {
secp256k1_scratch *scratch;
+ int64_t todo = (int64_t)320 * count;
test_secp256k1_pippenger_bucket_window_inv();
test_ecmult_multi_pippenger_max_points();
@@ -4252,6 +4647,9 @@ void run_ecmult_multi_tests(void) {
test_ecmult_multi_batch_single(secp256k1_ecmult_pippenger_batch_single);
test_ecmult_multi(scratch, secp256k1_ecmult_strauss_batch_single);
test_ecmult_multi_batch_single(secp256k1_ecmult_strauss_batch_single);
+ while (todo > 0) {
+ todo -= test_ecmult_multi_random(scratch);
+ }
secp256k1_scratch_destroy(&ctx->error_callback, scratch);
/* Run test_ecmult_multi with space for exactly one point */
@@ -4347,7 +4745,7 @@ void test_constant_wnaf(const secp256k1_scalar *number, int w) {
secp256k1_scalar_add(&x, &x, &t);
}
/* Skew num because when encoding numbers as odd we use an offset */
- secp256k1_scalar_set_int(&scalar_skew, 1 << (skew == 2));
+ secp256k1_scalar_set_int(&scalar_skew, skew);
secp256k1_scalar_add(&num, &num, &scalar_skew);
CHECK(secp256k1_scalar_eq(&x, &num));
}
@@ -4540,8 +4938,8 @@ void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar* x, se
}
}
-void test_ecmult_constants(void) {
- /* Test ecmult_gen for:
+void test_ecmult_constants_2bit(void) {
+ /* Using test_ecmult_accumulate, test ecmult for:
* - For i in 0..36:
* - Key i
* - Key -i
@@ -4584,8 +4982,81 @@ void test_ecmult_constants(void) {
secp256k1_scratch_space_destroy(ctx, scratch);
}
+void test_ecmult_constants_sha(uint32_t prefix, size_t iter, const unsigned char* expected32) {
+ /* Using test_ecmult_accumulate, test ecmult for:
+ * - Key 0
+ * - Key 1
+ * - Key -1
+ * - For i in range(iter):
+ * - Key SHA256(LE32(prefix) || LE16(i))
+ */
+ secp256k1_scalar x;
+ secp256k1_sha256 acc;
+ unsigned char b32[32];
+ unsigned char inp[6];
+ size_t i;
+ secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 65536);
+
+ inp[0] = prefix & 0xFF;
+ inp[1] = (prefix >> 8) & 0xFF;
+ inp[2] = (prefix >> 16) & 0xFF;
+ inp[3] = (prefix >> 24) & 0xFF;
+ secp256k1_sha256_initialize(&acc);
+ secp256k1_scalar_set_int(&x, 0);
+ test_ecmult_accumulate(&acc, &x, scratch);
+ secp256k1_scalar_set_int(&x, 1);
+ test_ecmult_accumulate(&acc, &x, scratch);
+ secp256k1_scalar_negate(&x, &x);
+ test_ecmult_accumulate(&acc, &x, scratch);
+
+ for (i = 0; i < iter; ++i) {
+ secp256k1_sha256 gen;
+ inp[4] = i & 0xff;
+ inp[5] = (i >> 8) & 0xff;
+ secp256k1_sha256_initialize(&gen);
+ secp256k1_sha256_write(&gen, inp, sizeof(inp));
+ secp256k1_sha256_finalize(&gen, b32);
+ secp256k1_scalar_set_b32(&x, b32, NULL);
+ test_ecmult_accumulate(&acc, &x, scratch);
+ }
+ secp256k1_sha256_finalize(&acc, b32);
+ CHECK(secp256k1_memcmp_var(b32, expected32, 32) == 0);
+
+ secp256k1_scratch_space_destroy(ctx, scratch);
+}
+
void run_ecmult_constants(void) {
- test_ecmult_constants();
+ /* Expected hashes of all points in the tests below. Computed using an
+ * independent implementation. */
+ static const unsigned char expected32_6bit20[32] = {
+ 0x68, 0xb6, 0xed, 0x6f, 0x28, 0xca, 0xc9, 0x7f,
+ 0x8e, 0x8b, 0xd6, 0xc0, 0x61, 0x79, 0x34, 0x6e,
+ 0x5a, 0x8f, 0x2b, 0xbc, 0x3e, 0x1f, 0xc5, 0x2e,
+ 0x2a, 0xd0, 0x45, 0x67, 0x7f, 0x95, 0x95, 0x8e
+ };
+ static const unsigned char expected32_8bit8[32] = {
+ 0x8b, 0x65, 0x8e, 0xea, 0x86, 0xae, 0x3c, 0x95,
+ 0x90, 0xb6, 0x77, 0xa4, 0x8c, 0x76, 0xd9, 0xec,
+ 0xf5, 0xab, 0x8a, 0x2f, 0xfd, 0xdb, 0x19, 0x12,
+ 0x1a, 0xee, 0xe6, 0xb7, 0x6e, 0x05, 0x3f, 0xc6
+ };
+ /* For every combination of 6 bit positions out of 256, restricted to
+ * 20-bit windows (i.e., the first and last bit position are no more than
+ * 19 bits apart), all 64 bit patterns occur in the input scalars used in
+ * this test. */
+ CONDITIONAL_TEST(1, "test_ecmult_constants_sha 1024") {
+ test_ecmult_constants_sha(4808378u, 1024, expected32_6bit20);
+ }
+
+ /* For every combination of 8 consecutive bit positions, all 256 bit
+ * patterns occur in the input scalars used in this test. */
+ CONDITIONAL_TEST(3, "test_ecmult_constants_sha 2048") {
+ test_ecmult_constants_sha(1607366309u, 2048, expected32_8bit8);
+ }
+
+ CONDITIONAL_TEST(35, "test_ecmult_constants_2bit") {
+ test_ecmult_constants_2bit();
+ }
}
void test_ecmult_gen_blind(void) {
@@ -5851,14 +6322,14 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly
/* We generate two classes of numbers: nlow==1 "low" ones (up to 32 bytes), nlow==0 "high" ones (32 bytes with 129 top bits set, or larger than 32 bytes) */
nlow[n] = der ? 1 : (secp256k1_testrand_bits(3) != 0);
/* The length of the number in bytes (the first byte of which will always be nonzero) */
- nlen[n] = nlow[n] ? secp256k1_testrand_int(33) : 32 + secp256k1_testrand_int(200) * secp256k1_testrand_int(8) / 8;
+ nlen[n] = nlow[n] ? secp256k1_testrand_int(33) : 32 + secp256k1_testrand_int(200) * secp256k1_testrand_bits(3) / 8;
CHECK(nlen[n] <= 232);
/* The top bit of the number. */
nhbit[n] = (nlow[n] == 0 && nlen[n] == 32) ? 1 : (nlen[n] == 0 ? 0 : secp256k1_testrand_bits(1));
/* The top byte of the number (after the potential hardcoded 16 0xFF characters for "high" 32 bytes numbers) */
nhbyte[n] = nlen[n] == 0 ? 0 : (nhbit[n] ? 128 + secp256k1_testrand_bits(7) : 1 + secp256k1_testrand_int(127));
/* The number of zero bytes in front of the number (which is 0 or 1 in case of DER, otherwise we extend up to 300 bytes) */
- nzlen[n] = der ? ((nlen[n] == 0 || nhbit[n]) ? 1 : 0) : (nlow[n] ? secp256k1_testrand_int(3) : secp256k1_testrand_int(300 - nlen[n]) * secp256k1_testrand_int(8) / 8);
+ nzlen[n] = der ? ((nlen[n] == 0 || nhbit[n]) ? 1 : 0) : (nlow[n] ? secp256k1_testrand_int(3) : secp256k1_testrand_int(300 - nlen[n]) * secp256k1_testrand_bits(3) / 8);
if (nzlen[n] > ((nlen[n] == 0 || nhbit[n]) ? 1 : 0)) {
*certainly_not_der = 1;
}
@@ -5867,7 +6338,7 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly
nlenlen[n] = nlen[n] + nzlen[n] < 128 ? 0 : (nlen[n] + nzlen[n] < 256 ? 1 : 2);
if (!der) {
/* nlenlen[n] max 127 bytes */
- int add = secp256k1_testrand_int(127 - nlenlen[n]) * secp256k1_testrand_int(16) * secp256k1_testrand_int(16) / 256;
+ int add = secp256k1_testrand_int(127 - nlenlen[n]) * secp256k1_testrand_bits(4) * secp256k1_testrand_bits(4) / 256;
nlenlen[n] += add;
if (add != 0) {
*certainly_not_der = 1;
@@ -5881,7 +6352,7 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly
CHECK(tlen <= 856);
/* The length of the garbage inside the tuple. */
- elen = (der || indet) ? 0 : secp256k1_testrand_int(980 - tlen) * secp256k1_testrand_int(8) / 8;
+ elen = (der || indet) ? 0 : secp256k1_testrand_int(980 - tlen) * secp256k1_testrand_bits(3) / 8;
if (elen != 0) {
*certainly_not_der = 1;
}
@@ -5889,7 +6360,7 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly
CHECK(tlen <= 980);
/* The length of the garbage after the end of the tuple. */
- glen = der ? 0 : secp256k1_testrand_int(990 - tlen) * secp256k1_testrand_int(8) / 8;
+ glen = der ? 0 : secp256k1_testrand_int(990 - tlen) * secp256k1_testrand_bits(3) / 8;
if (glen != 0) {
*certainly_not_der = 1;
}
@@ -5904,7 +6375,7 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly
} else {
int tlenlen = tlen < 128 ? 0 : (tlen < 256 ? 1 : 2);
if (!der) {
- int add = secp256k1_testrand_int(127 - tlenlen) * secp256k1_testrand_int(16) * secp256k1_testrand_int(16) / 256;
+ int add = secp256k1_testrand_int(127 - tlenlen) * secp256k1_testrand_bits(4) * secp256k1_testrand_bits(4) / 256;
tlenlen += add;
if (add != 0) {
*certainly_not_der = 1;
@@ -6416,6 +6887,19 @@ void run_secp256k1_memczero_test(void) {
CHECK(secp256k1_memcmp_var(buf1, buf2, sizeof(buf1)) == 0);
}
+void run_secp256k1_byteorder_tests(void) {
+ const uint32_t x = 0xFF03AB45;
+ const unsigned char x_be[4] = {0xFF, 0x03, 0xAB, 0x45};
+ unsigned char buf[4];
+ uint32_t x_;
+
+ secp256k1_write_be32(buf, x);
+ CHECK(secp256k1_memcmp_var(buf, x_be, sizeof(buf)) == 0);
+
+ x_ = secp256k1_read_be32(buf);
+ CHECK(x == x_);
+}
+
void int_cmov_test(void) {
int r = INT_MAX;
int a = 0;
@@ -6616,7 +7100,8 @@ int main(int argc, char **argv) {
run_modinv_tests();
run_inverse_tests();
- run_sha256_tests();
+ run_sha256_known_output_tests();
+ run_sha256_counter_tests();
run_hmac_sha256_tests();
run_rfc6979_hmac_sha256_tests();
run_tagged_sha256_tests();
@@ -6625,6 +7110,7 @@ int main(int argc, char **argv) {
run_scalar_tests();
/* field tests */
+ run_field_half();
run_field_misc();
run_field_convert();
run_fe_mul();
@@ -6633,6 +7119,7 @@ int main(int argc, char **argv) {
/* group tests */
run_ge();
+ run_gej();
run_group_decompress();
/* ecmult tests */
@@ -6687,6 +7174,7 @@ int main(int argc, char **argv) {
/* util tests */
run_secp256k1_memczero_test();
+ run_secp256k1_byteorder_tests();
run_cmov_tests();
diff --git a/src/secp256k1/src/tests_exhaustive.c b/src/secp256k1/src/tests_exhaustive.c
index 6bae7a4778..6a4e2340f2 100644
--- a/src/secp256k1/src/tests_exhaustive.c
+++ b/src/secp256k1/src/tests_exhaustive.c
@@ -22,7 +22,8 @@
#include "assumptions.h"
#include "group.h"
#include "testrand_impl.h"
-#include "ecmult_gen_prec_impl.h"
+#include "ecmult_compute_table_impl.h"
+#include "ecmult_gen_compute_table_impl.h"
static int count = 2;
@@ -389,8 +390,9 @@ int main(int argc, char** argv) {
printf("running tests for core %lu (out of [0..%lu])\n", (unsigned long)this_core, (unsigned long)num_cores - 1);
}
- /* Recreate the ecmult_gen table using the right generator (as selected via EXHAUSTIVE_TEST_ORDER) */
- secp256k1_ecmult_gen_create_prec_table(&secp256k1_ecmult_gen_prec_table[0][0], &secp256k1_ge_const_g, ECMULT_GEN_PREC_BITS);
+ /* Recreate the ecmult{,_gen} tables using the right generator (as selected via EXHAUSTIVE_TEST_ORDER) */
+ secp256k1_ecmult_gen_compute_table(&secp256k1_ecmult_gen_prec_table[0][0], &secp256k1_ge_const_g, ECMULT_GEN_PREC_BITS);
+ secp256k1_ecmult_compute_two_tables(secp256k1_pre_g, secp256k1_pre_g_128, WINDOW_G, &secp256k1_ge_const_g);
while (count--) {
/* Build context */
diff --git a/src/secp256k1/src/util.h b/src/secp256k1/src/util.h
index 04227a7c9b..dac86bd77f 100644
--- a/src/secp256k1/src/util.h
+++ b/src/secp256k1/src/util.h
@@ -173,31 +173,6 @@ static SECP256K1_INLINE void *checked_realloc(const secp256k1_callback* cb, void
# define SECP256K1_GNUC_EXT
#endif
-/* If SECP256K1_{LITTLE,BIG}_ENDIAN is not explicitly provided, infer from various other system macros. */
-#if !defined(SECP256K1_LITTLE_ENDIAN) && !defined(SECP256K1_BIG_ENDIAN)
-/* Inspired by https://github.com/rofl0r/endianness.h/blob/9853923246b065a3b52d2c43835f3819a62c7199/endianness.h#L52L73 */
-# if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \
- defined(_X86_) || defined(__x86_64__) || defined(__i386__) || \
- defined(__i486__) || defined(__i586__) || defined(__i686__) || \
- defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) || \
- defined(__ARMEL__) || defined(__AARCH64EL__) || \
- (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \
- (defined(_LITTLE_ENDIAN) && _LITTLE_ENDIAN == 1) || \
- defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM) /* MSVC */
-# define SECP256K1_LITTLE_ENDIAN
-# endif
-# if (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \
- defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) || \
- defined(__MICROBLAZEEB__) || defined(__ARMEB__) || defined(__AARCH64EB__) || \
- (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \
- (defined(_BIG_ENDIAN) && _BIG_ENDIAN == 1)
-# define SECP256K1_BIG_ENDIAN
-# endif
-#endif
-#if defined(SECP256K1_LITTLE_ENDIAN) == defined(SECP256K1_BIG_ENDIAN)
-# error Please make sure that either SECP256K1_LITTLE_ENDIAN or SECP256K1_BIG_ENDIAN is set, see src/util.h.
-#endif
-
/* Zero memory if flag == 1. Flag must be 0 or 1. Constant time. */
static SECP256K1_INLINE void secp256k1_memczero(void *s, size_t len, int flag) {
unsigned char *p = (unsigned char *)s;
@@ -338,4 +313,20 @@ static SECP256K1_INLINE int secp256k1_ctz64_var(uint64_t x) {
#endif
}
+/* Read a uint32_t in big endian */
+SECP256K1_INLINE static uint32_t secp256k1_read_be32(const unsigned char* p) {
+ return (uint32_t)p[0] << 24 |
+ (uint32_t)p[1] << 16 |
+ (uint32_t)p[2] << 8 |
+ (uint32_t)p[3];
+}
+
+/* Write a uint32_t in big endian */
+SECP256K1_INLINE static void secp256k1_write_be32(unsigned char* p, uint32_t x) {
+ p[3] = x;
+ p[2] = x >> 8;
+ p[1] = x >> 16;
+ p[0] = x >> 24;
+}
+
#endif /* SECP256K1_UTIL_H */
diff --git a/src/secp256k1/src/valgrind_ctime_test.c b/src/secp256k1/src/valgrind_ctime_test.c
index ea6d4b3deb..6ff0085d34 100644
--- a/src/secp256k1/src/valgrind_ctime_test.c
+++ b/src/secp256k1/src/valgrind_ctime_test.c
@@ -166,7 +166,7 @@ void run_tests(secp256k1_context *ctx, unsigned char *key) {
ret = secp256k1_keypair_create(ctx, &keypair, key);
VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret));
CHECK(ret == 1);
- ret = secp256k1_schnorrsig_sign(ctx, sig, msg, &keypair, NULL);
+ ret = secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL);
VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret));
CHECK(ret == 1);
#endif
diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp
index 6965f40253..ea1a27c6f6 100644
--- a/src/support/lockedpool.cpp
+++ b/src/support/lockedpool.cpp
@@ -235,12 +235,6 @@ PosixLockedPageAllocator::PosixLockedPageAllocator()
#endif
}
-// Some systems (at least OS X) do not define MAP_ANONYMOUS yet and define
-// MAP_ANON which is deprecated
-#ifndef MAP_ANONYMOUS
-#define MAP_ANONYMOUS MAP_ANON
-#endif
-
void *PosixLockedPageAllocator::AllocateLocked(size_t len, bool *lockingSuccess)
{
void *addr;
diff --git a/src/sync.h b/src/sync.h
index 85000a9151..af7595e6fa 100644
--- a/src/sync.h
+++ b/src/sync.h
@@ -6,8 +6,11 @@
#ifndef BITCOIN_SYNC_H
#define BITCOIN_SYNC_H
+#ifdef DEBUG_LOCKCONTENTION
#include <logging.h>
#include <logging/timer.h>
+#endif
+
#include <threadsafety.h>
#include <util/macros.h>
@@ -136,8 +139,10 @@ private:
void Enter(const char* pszName, const char* pszFile, int nLine)
{
EnterCritical(pszName, pszFile, nLine, Base::mutex());
+#ifdef DEBUG_LOCKCONTENTION
if (Base::try_lock()) return;
LOG_TIME_MICROS_WITH_CATEGORY(strprintf("lock contention %s, %s:%d", pszName, pszFile, nLine), BCLog::LOCK);
+#endif
Base::lock();
}
diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp
index 7c502349b3..82b9617384 100644
--- a/src/test/blockfilter_index_tests.cpp
+++ b/src/test/blockfilter_index_tests.cpp
@@ -4,6 +4,7 @@
#include <blockfilter.h>
#include <chainparams.h>
+#include <consensus/merkle.h>
#include <consensus/validation.h>
#include <index/blockfilterindex.h>
#include <node/miner.h>
@@ -18,7 +19,6 @@
using node::BlockAssembler;
using node::CBlockTemplate;
-using node::IncrementExtraNonce;
BOOST_AUTO_TEST_SUITE(blockfilter_index_tests)
@@ -76,9 +76,12 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev,
for (const CMutableTransaction& tx : txns) {
block.vtx.push_back(MakeTransactionRef(tx));
}
- // IncrementExtraNonce creates a valid coinbase and merkleRoot
- unsigned int extraNonce = 0;
- IncrementExtraNonce(&block, prev, extraNonce);
+ {
+ CMutableTransaction tx_coinbase{*block.vtx.at(0)};
+ tx_coinbase.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1;
+ block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase));
+ block.hashMerkleRoot = BlockMerkleRoot(block);
+ }
while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce;
diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp
index 153ccd984b..0d95bd7670 100644
--- a/src/test/checkqueue_tests.cpp
+++ b/src/test/checkqueue_tests.cpp
@@ -19,13 +19,17 @@
#include <vector>
/**
- * Identical to TestingSetup but excludes lock contention logging, as some of
- * these tests are designed to be heavily contested to trigger race conditions
- * or other issues.
+ * Identical to TestingSetup but excludes lock contention logging if
+ * `DEBUG_LOCKCONTENTION` is defined, as some of these tests are designed to be
+ * heavily contested to trigger race conditions or other issues.
*/
struct NoLockLoggingTestingSetup : public TestingSetup {
NoLockLoggingTestingSetup()
+#ifdef DEBUG_LOCKCONTENTION
: TestingSetup{CBaseChainParams::MAIN, /*extra_args=*/{"-debugexclude=lock"}} {}
+#else
+ : TestingSetup{CBaseChainParams::MAIN} {}
+#endif
};
BOOST_FIXTURE_TEST_SUITE(checkqueue_tests, NoLockLoggingTestingSetup)
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index a17cc87730..f03ff5ba3a 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -34,8 +34,6 @@ static CService ip(uint32_t i)
return CService(CNetAddr(s), Params().GetDefaultPort());
}
-static NodeId id = 0;
-
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds);
BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)
@@ -59,6 +57,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
// Mock an outbound peer
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
+ NodeId id{0};
CNode dummyNode1{id++,
ServiceFlags(NODE_NETWORK | NODE_WITNESS),
/*sock=*/nullptr,
@@ -114,7 +113,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
peerLogic->FinalizeNode(dummyNode1);
}
-static void AddRandomOutboundPeer(std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman, ConnectionType connType)
+static void AddRandomOutboundPeer(NodeId& id, std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman, ConnectionType connType)
{
CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE);
vNodes.emplace_back(new CNode{id++,
@@ -138,6 +137,7 @@ static void AddRandomOutboundPeer(std::vector<CNode*>& vNodes, PeerManager& peer
BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
{
+ NodeId id{0};
const CChainParams& chainparams = Params();
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman);
auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr,
@@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
// Mock some outbound peers
for (int i = 0; i < max_outbound_full_relay; ++i) {
- AddRandomOutboundPeer(vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY);
+ AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY);
}
peerLogic->CheckForStaleTipAndEvictPeers();
@@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
// on the next check (since we're mocking the time to be in the future, the
// required time connected check should be satisfied).
SetMockTime(time_init);
- AddRandomOutboundPeer(vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY);
+ AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY);
SetMockTime(time_later);
peerLogic->CheckForStaleTipAndEvictPeers();
@@ -215,6 +215,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
BOOST_AUTO_TEST_CASE(block_relay_only_eviction)
{
+ NodeId id{0};
const CChainParams& chainparams = Params();
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman);
auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr,
@@ -232,7 +233,7 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction)
// Add block-relay-only peers up to the limit
for (int i = 0; i < max_outbound_block_relay; ++i) {
- AddRandomOutboundPeer(vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY);
+ AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY);
}
peerLogic->CheckForStaleTipAndEvictPeers();
@@ -241,7 +242,7 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction)
}
// Add an extra block-relay-only peer breaking the limit (mocks logic in ThreadOpenConnections)
- AddRandomOutboundPeer(vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY);
+ AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY);
peerLogic->CheckForStaleTipAndEvictPeers();
// The extra peer should only get marked for eviction after MINIMUM_CONNECT_TIME
@@ -297,6 +298,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
std::array<CNode*, 3> nodes;
banman->ClearBanned();
+ NodeId id{0};
nodes[0] = new CNode{id++,
NODE_NETWORK,
/*sock=*/nullptr,
@@ -403,6 +405,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
SetMockTime(nStartTime); // Overrides future calls to GetTime()
CAddress addr(ip(0xa0b0c001), NODE_NONE);
+ NodeId id{0};
CNode dummyNode{id++,
NODE_NETWORK,
/*sock=*/nullptr,
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index 55a9a78159..30add9c16d 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -311,7 +311,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
spend.vout.resize(1);
std::vector<CTxOut> utxos(1);
PrecomputedTransactionData txdata;
- txdata.Init(spend, std::move(utxos), /* force */ true);
+ txdata.Init(spend, std::move(utxos), /*force=*/true);
MutableTransactionSignatureCreator creator(&spend, 0, CAmount{0}, &txdata, SIGHASH_DEFAULT);
SignatureData sigdata;
BOOST_CHECK_MESSAGE(ProduceSignature(Merge(keys_priv, script_provider), creator, spks[n], sigdata), prv);
@@ -381,17 +381,17 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32);
Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT);
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE | XONLY_KEYS, {{"512077aab6e066f8a7419c5ab714c12c67d25007ed55a43cadcacb4d7a970a093f11"}}, OutputType::BECH32M);
- CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey
- CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Key origin start '[ character expected but not found, got 'd' instead"); // Missing start bracket in key origin
- CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin
+ CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "wpkh(): Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey
+ CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh(): Key origin start '[ character expected but not found, got 'd' instead"); // Missing start bracket in key origin
+ CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh(): Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin
// Basic single-key uncompressed
Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)",SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, std::nullopt);
Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, std::nullopt);
Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY);
- CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
- CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
- CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness
+ CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "wpkh(): Uncompressed keys are not allowed"); // No uncompressed keys in witness
+ CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "pk(): Uncompressed keys are not allowed"); // No uncompressed keys in witness
+ CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "wpkh(): Uncompressed keys are not allowed"); // No uncompressed keys in witness
// Some unconventional single-key constructions
Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY);
@@ -415,9 +415,9 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
// Mixed range xpubs and const pubkeys
Check("multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)","multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", RANGE | MIXED_PUBKEYS, {{"512102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e0762103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ec2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121035d30b6c66dc1e036c45369da8287518cf7e0d6ed1e2b905171c605708f14ca032103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"}}, std::nullopt,{{2},{1},{0},{}});
- CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint
- CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "Key path value 2147483648 is out of range"); // BIP 32 path element overflow
- CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "Key path value '1aa' is not a valid uint32"); // Path is not valid uint
+ CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo(): Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint
+ CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "pkh(): Key path value 2147483648 is out of range"); // BIP 32 path element overflow
+ CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "pkh(): Key path value '1aa' is not a valid uint32"); // Path is not valid uint
Check("pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh([01234567/10/20]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([01234567/10/20/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0)", "pkh([01234567/10/20/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{10, 20, 0xFFFFFFFFUL, 0}});
// Multisig constructions
@@ -430,11 +430,11 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", "sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}, OutputType::P2SH_SEGWIT);
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,pk(KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", "tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,pk(KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", SIGNABLE | XONLY_KEYS, {{"512017cf18db381d836d8923b1bdb246cfcd818da1a9f0e6e7907f187f0b2f937754"}}, OutputType::BECH32M);
CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))", "P2SH script is too large, 547 bytes is larger than 520 bytes"); // P2SH does not fit 16 compressed pubkeys in a redeemscript
- CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multiple ']' characters found for a single pubkey"); // Double key origin descriptor
- CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint
- CheckUnparsable("wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "No key provided"); // No public key with origin
- CheckUnparsable("wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint is not 4 bytes (7 characters instead of 8 characters)"); // Too short fingerprint
- CheckUnparsable("wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long fingerprint
+ CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: Multiple ']' characters found for a single pubkey"); // Double key origin descriptor
+ CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint
+ CheckUnparsable("wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: No key provided"); // No public key with origin
+ CheckUnparsable("wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: Fingerprint is not 4 bytes (7 characters instead of 8 characters)"); // Too short fingerprint
+ CheckUnparsable("wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long fingerprint
CheckUnparsable("multi(a,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(a,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multi threshold 'a' is not valid"); // Invalid threshold
CheckUnparsable("multi(0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multisig threshold cannot be 0, must be at least 1"); // Threshold of 0
CheckUnparsable("multi(3,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(3,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multisig threshold cannot be larger than the number of keys; threshold is 3 but only 2 keys specified"); // Threshold larger than number of keys
diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp
index a490bbfa1d..59adec075e 100644
--- a/src/test/fuzz/fuzz.cpp
+++ b/src/test/fuzz/fuzz.cpp
@@ -10,7 +10,9 @@
#include <test/util/setup_common.h>
#include <util/check.h>
#include <util/sock.h>
+#include <util/time.h>
+#include <csignal>
#include <cstdint>
#include <exception>
#include <fstream>
@@ -59,6 +61,7 @@ void FuzzFrameworkRegisterTarget(std::string_view name, TypeTestOneInput target,
Assert(it_ins.second);
}
+static std::string_view g_fuzz_target;
static TypeTestOneInput* g_test_one_input{nullptr};
void initialize()
@@ -92,9 +95,12 @@ void initialize()
should_abort = true;
}
Assert(!should_abort);
- std::string_view fuzz_target{Assert(std::getenv("FUZZ"))};
- const auto it = FuzzTargets().find(fuzz_target);
- Assert(it != FuzzTargets().end());
+ g_fuzz_target = Assert(std::getenv("FUZZ"));
+ const auto it = FuzzTargets().find(g_fuzz_target);
+ if (it == FuzzTargets().end()) {
+ std::cerr << "No fuzzer for " << g_fuzz_target << "." << std::endl;
+ std::exit(EXIT_FAILURE);
+ }
Assert(!g_test_one_input);
g_test_one_input = &std::get<0>(it->second);
std::get<1>(it->second)();
@@ -112,6 +118,35 @@ static bool read_stdin(std::vector<uint8_t>& data)
}
#endif
+#if defined(PROVIDE_FUZZ_MAIN_FUNCTION) && !defined(__AFL_LOOP)
+static bool read_file(fs::path p, std::vector<uint8_t>& data)
+{
+ uint8_t buffer[1024];
+ FILE* f = fsbridge::fopen(p, "rb");
+ if (f == nullptr) return false;
+ do {
+ const size_t length = fread(buffer, sizeof(uint8_t), sizeof(buffer), f);
+ if (ferror(f)) return false;
+ data.insert(data.end(), buffer, buffer + length);
+ } while (!feof(f));
+ fclose(f);
+ return true;
+}
+#endif
+
+#if defined(PROVIDE_FUZZ_MAIN_FUNCTION) && !defined(__AFL_LOOP)
+static fs::path g_input_path;
+void signal_handler(int signal)
+{
+ if (signal == SIGABRT) {
+ std::cerr << "Error processing input " << g_input_path << std::endl;
+ } else {
+ std::cerr << "Unexpected signal " << signal << " received\n";
+ }
+ std::_Exit(EXIT_FAILURE);
+}
+#endif
+
// This function is used by libFuzzer
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
@@ -151,10 +186,37 @@ int main(int argc, char** argv)
}
#else
std::vector<uint8_t> buffer;
- if (!read_stdin(buffer)) {
+ if (argc <= 1) {
+ if (!read_stdin(buffer)) {
+ return 0;
+ }
+ test_one_input(buffer);
return 0;
}
- test_one_input(buffer);
+ std::signal(SIGABRT, signal_handler);
+ int64_t start_time = GetTimeSeconds();
+ int tested = 0;
+ for (int i = 1; i < argc; ++i) {
+ fs::path input_path(*(argv + i));
+ if (fs::is_directory(input_path)) {
+ for (fs::directory_iterator it(input_path); it != fs::directory_iterator(); ++it) {
+ if (!fs::is_regular_file(it->path())) continue;
+ g_input_path = it->path();
+ Assert(read_file(it->path(), buffer));
+ test_one_input(buffer);
+ ++tested;
+ buffer.clear();
+ }
+ } else {
+ g_input_path = input_path;
+ Assert(read_file(input_path, buffer));
+ test_one_input(buffer);
+ ++tested;
+ buffer.clear();
+ }
+ }
+ int64_t end_time = GetTimeSeconds();
+ std::cout << g_fuzz_target << ": succeeded against " << tested << " files in " << (end_time - start_time) << "s." << std::endl;
#endif
return 0;
}
diff --git a/src/test/fuzz/http_request.cpp b/src/test/fuzz/http_request.cpp
index e3b62032bc..916e90e986 100644
--- a/src/test/fuzz/http_request.cpp
+++ b/src/test/fuzz/http_request.cpp
@@ -19,23 +19,8 @@
#include <string>
#include <vector>
-// workaround for libevent versions before 2.1.1,
-// when internal functions didn't have underscores at the end
-#if LIBEVENT_VERSION_NUMBER < 0x02010100
-extern "C" int evhttp_parse_firstline(struct evhttp_request*, struct evbuffer*);
-extern "C" int evhttp_parse_headers(struct evhttp_request*, struct evbuffer*);
-inline int evhttp_parse_firstline_(struct evhttp_request* r, struct evbuffer* b)
-{
- return evhttp_parse_firstline(r, b);
-}
-inline int evhttp_parse_headers_(struct evhttp_request* r, struct evbuffer* b)
-{
- return evhttp_parse_headers(r, b);
-}
-#else
extern "C" int evhttp_parse_firstline_(struct evhttp_request*, struct evbuffer*);
extern "C" int evhttp_parse_headers_(struct evhttp_request*, struct evbuffer*);
-#endif
std::string RequestMethodString(HTTPRequest::RequestMethod m);
diff --git a/src/test/fuzz/miniscript_decode.cpp b/src/test/fuzz/miniscript_decode.cpp
new file mode 100644
index 0000000000..4cc0a1be8f
--- /dev/null
+++ b/src/test/fuzz/miniscript_decode.cpp
@@ -0,0 +1,72 @@
+// 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.
+
+#include <core_io.h>
+#include <hash.h>
+#include <key.h>
+#include <script/miniscript.h>
+#include <script/script.h>
+#include <span.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+#include <util/strencodings.h>
+
+#include <optional>
+
+using miniscript::operator""_mst;
+
+
+struct Converter {
+ typedef CPubKey Key;
+
+ bool ToString(const Key& key, std::string& ret) const {
+ ret = HexStr(key);
+ return true;
+ }
+ const std::vector<unsigned char> ToPKBytes(const Key& key) const {
+ return {key.begin(), key.end()};
+ }
+ const std::vector<unsigned char> ToPKHBytes(const Key& key) const {
+ const auto h = Hash160(key);
+ return {h.begin(), h.end()};
+ }
+
+ template<typename I>
+ bool FromString(I first, I last, Key& key) const {
+ const auto bytes = ParseHex(std::string(first, last));
+ key.Set(bytes.begin(), bytes.end());
+ return key.IsValid();
+ }
+ template<typename I>
+ bool FromPKBytes(I first, I last, CPubKey& key) const {
+ key.Set(first, last);
+ return key.IsValid();
+ }
+ template<typename I>
+ bool FromPKHBytes(I first, I last, CPubKey& key) const {
+ assert(last - first == 20);
+ return false;
+ }
+};
+
+const Converter CONVERTER;
+
+FUZZ_TARGET(miniscript_decode)
+{
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider);
+ if (!script) return;
+
+ const auto ms = miniscript::FromScript(*script, CONVERTER);
+ if (!ms) return;
+
+ // We can roundtrip it to its string representation.
+ std::string ms_str;
+ assert(ms->ToString(CONVERTER, ms_str));
+ assert(*miniscript::FromString(ms_str, CONVERTER) == *ms);
+ // The Script representation must roundtrip since we parsed it this way the first time.
+ const CScript ms_script = ms->ToScript(CONVERTER);
+ assert(ms_script == *script);
+}
diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp
index fb11ea36ce..4981287152 100644
--- a/src/test/fuzz/net.cpp
+++ b/src/test/fuzz/net.cpp
@@ -52,16 +52,6 @@ FUZZ_TARGET_INIT(net, initialize_net)
}
},
[&] {
- const std::optional<CInv> inv_opt = ConsumeDeserializable<CInv>(fuzzed_data_provider);
- if (!inv_opt) {
- return;
- }
- node.AddKnownTx(inv_opt->hash);
- },
- [&] {
- node.PushTxInventory(ConsumeUInt256(fuzzed_data_provider));
- },
- [&] {
const std::optional<CService> service_opt = ConsumeDeserializable<CService>(fuzzed_data_provider);
if (!service_opt) {
return;
diff --git a/src/test/fuzz/node_eviction.cpp b/src/test/fuzz/node_eviction.cpp
index 2e90085744..6a363f00f7 100644
--- a/src/test/fuzz/node_eviction.cpp
+++ b/src/test/fuzz/node_eviction.cpp
@@ -26,7 +26,7 @@ FUZZ_TARGET(node_eviction)
/*m_last_block_time=*/std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<int64_t>()},
/*m_last_tx_time=*/std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<int64_t>()},
/*fRelevantServices=*/fuzzed_data_provider.ConsumeBool(),
- /*fRelayTxes=*/fuzzed_data_provider.ConsumeBool(),
+ /*m_relay_txs=*/fuzzed_data_provider.ConsumeBool(),
/*fBloomFilter=*/fuzzed_data_provider.ConsumeBool(),
/*nKeyedNetGroup=*/fuzzed_data_provider.ConsumeIntegral<uint64_t>(),
/*prefer_evict=*/fuzzed_data_provider.ConsumeBool(),
diff --git a/src/test/fuzz/script_format.cpp b/src/test/fuzz/script_format.cpp
index 241bdfe666..9186746bcf 100644
--- a/src/test/fuzz/script_format.cpp
+++ b/src/test/fuzz/script_format.cpp
@@ -29,7 +29,5 @@ FUZZ_TARGET_INIT(script_format, initialize_script_format)
(void)ScriptToAsmStr(script, /*fAttemptSighashDecode=*/fuzzed_data_provider.ConsumeBool());
UniValue o1(UniValue::VOBJ);
- ScriptPubKeyToUniv(script, o1, /*include_hex=*/fuzzed_data_provider.ConsumeBool());
- UniValue o3(UniValue::VOBJ);
- ScriptToUniv(script, o3);
+ ScriptToUniv(script, /*out=*/o1, /*include_hex=*/fuzzed_data_provider.ConsumeBool(), /*include_address=*/fuzzed_data_provider.ConsumeBool());
}
diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp
index 6dd8a36692..273aa0dc5c 100644
--- a/src/test/fuzz/transaction.cpp
+++ b/src/test/fuzz/transaction.cpp
@@ -102,6 +102,6 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction)
(void)IsWitnessStandard(tx, coins_view_cache);
UniValue u(UniValue::VOBJ);
- TxToUniv(tx, /*hashBlock=*/uint256::ZERO, u);
- TxToUniv(tx, /*hashBlock=*/uint256::ONE, u);
+ TxToUniv(tx, /*block_hash=*/uint256::ZERO, /*entry=*/u);
+ TxToUniv(tx, /*block_hash=*/uint256::ONE, /*entry=*/u);
}
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index df5b271d06..f686f4fd86 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -234,14 +234,18 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
::fRequireStandard = fuzzed_data_provider.ConsumeBool();
- // Make sure ProcessNewPackage on one transaction works and always fully validates the transaction.
+ // Make sure ProcessNewPackage on one transaction works.
// The result is not guaranteed to be the same as what is returned by ATMP.
const auto result_package = WITH_LOCK(::cs_main,
return ProcessNewPackage(chainstate, tx_pool, {tx}, true));
- auto it = result_package.m_tx_results.find(tx->GetWitnessHash());
- Assert(it != result_package.m_tx_results.end());
- Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
- it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
+ // If something went wrong due to a package-specific policy, it might not return a
+ // validation result for the transaction.
+ if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
+ auto it = result_package.m_tx_results.find(tx->GetWitnessHash());
+ Assert(it != result_package.m_tx_results.end());
+ Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID ||
+ it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
+ }
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
index f0c1b0d147..6766fbf2d9 100644
--- a/src/test/fuzz/util.cpp
+++ b/src/test/fuzz/util.cpp
@@ -225,7 +225,7 @@ void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman,
const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
const NetPermissionFlags permission_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS);
const int32_t version = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(MIN_PEER_PROTO_VERSION, std::numeric_limits<int32_t>::max());
- const bool filter_txs = fuzzed_data_provider.ConsumeBool();
+ const bool relay_txs{fuzzed_data_provider.ConsumeBool()};
const CNetMsgMaker mm{0};
@@ -241,7 +241,7 @@ void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman,
uint64_t{1}, // dummy nonce
std::string{}, // dummy subver
int32_t{}, // dummy starting_height
- filter_txs),
+ relay_txs),
};
(void)connman.ReceiveMsgFrom(node, msg_version);
@@ -255,10 +255,9 @@ void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman,
assert(node.nVersion == version);
assert(node.GetCommonVersion() == std::min(version, PROTOCOL_VERSION));
assert(node.nServices == remote_services);
- if (node.m_tx_relay != nullptr) {
- LOCK(node.m_tx_relay->cs_filter);
- assert(node.m_tx_relay->fRelayTxes == filter_txs);
- }
+ CNodeStateStats statestats;
+ assert(peerman.GetNodeStateStats(node.GetId(), statestats));
+ assert(statestats.m_relay_txs == (relay_txs && !node.IsBlockOnlyConn()));
node.m_permissionFlags = permission_flags;
if (successfully_connected) {
CSerializedNetMsg msg_verack{mm.Make(NetMsgType::VERACK)};
diff --git a/src/test/httpserver_tests.cpp b/src/test/httpserver_tests.cpp
new file mode 100644
index 0000000000..ee59ec6967
--- /dev/null
+++ b/src/test/httpserver_tests.cpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2012-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.
+
+#include <httpserver.h>
+#include <test/util/setup_common.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(httpserver_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(test_query_parameters)
+{
+ std::string uri {};
+
+ // No parameters
+ uri = "localhost:8080/rest/headers/someresource.json";
+ BOOST_CHECK(!GetQueryParameterFromUri(uri.c_str(), "p1").has_value());
+
+ // Single parameter
+ uri = "localhost:8080/rest/endpoint/someresource.json?p1=v1";
+ BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri.c_str(), "p1").value(), "v1");
+ BOOST_CHECK(!GetQueryParameterFromUri(uri.c_str(), "p2").has_value());
+
+ // Multiple parameters
+ uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2";
+ BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri.c_str(), "p1").value(), "v1");
+ BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri.c_str(), "p2").value(), "v2");
+
+ // If the query string contains duplicate keys, the first value is returned
+ uri = "/rest/endpoint/someresource.json?p1=v1&p1=v2";
+ BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri.c_str(), "p1").value(), "v1");
+
+ // Invalid query string syntax is the same as not having parameters
+ uri = "/rest/endpoint/someresource.json&p1=v1&p2=v2";
+ BOOST_CHECK(!GetQueryParameterFromUri(uri.c_str(), "p1").has_value());
+}
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp
new file mode 100644
index 0000000000..949d30dfd5
--- /dev/null
+++ b/src/test/miniscript_tests.cpp
@@ -0,0 +1,284 @@
+// Copyright (c) 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.
+
+
+#include <string>
+
+#include <test/util/setup_common.h>
+#include <boost/test/unit_test.hpp>
+
+#include <hash.h>
+#include <pubkey.h>
+#include <uint256.h>
+#include <crypto/ripemd160.h>
+#include <crypto/sha256.h>
+#include <script/miniscript.h>
+
+namespace {
+
+/** TestData groups various kinds of precomputed data necessary in this test. */
+struct TestData {
+ //! The only public keys used in this test.
+ std::vector<CPubKey> pubkeys;
+ //! A map from the public keys to their CKeyIDs (faster than hashing every time).
+ std::map<CPubKey, CKeyID> pkhashes;
+ std::map<CKeyID, CPubKey> pkmap;
+
+ // Various precomputed hashes
+ std::vector<std::vector<unsigned char>> sha256;
+ std::vector<std::vector<unsigned char>> ripemd160;
+ std::vector<std::vector<unsigned char>> hash256;
+ std::vector<std::vector<unsigned char>> hash160;
+
+ TestData()
+ {
+ // We generate 255 public keys and 255 hashes of each type.
+ for (int i = 1; i <= 255; ++i) {
+ // This 32-byte array functions as both private key data and hash preimage (31 zero bytes plus any nonzero byte).
+ unsigned char keydata[32] = {0};
+ keydata[31] = i;
+
+ // Compute CPubkey and CKeyID
+ CKey key;
+ key.Set(keydata, keydata + 32, true);
+ CPubKey pubkey = key.GetPubKey();
+ CKeyID keyid = pubkey.GetID();
+ pubkeys.push_back(pubkey);
+ pkhashes.emplace(pubkey, keyid);
+ pkmap.emplace(keyid, pubkey);
+
+ // Compute various hashes
+ std::vector<unsigned char> hash;
+ hash.resize(32);
+ CSHA256().Write(keydata, 32).Finalize(hash.data());
+ sha256.push_back(hash);
+ CHash256().Write(keydata).Finalize(hash);
+ hash256.push_back(hash);
+ hash.resize(20);
+ CRIPEMD160().Write(keydata, 32).Finalize(hash.data());
+ ripemd160.push_back(hash);
+ CHash160().Write(keydata).Finalize(hash);
+ hash160.push_back(hash);
+ }
+ }
+};
+
+//! Global TestData object
+std::unique_ptr<const TestData> g_testdata;
+
+/** A class encapsulating conversion routing for CPubKey. */
+struct KeyConverter {
+ typedef CPubKey Key;
+
+ //! Convert a public key to bytes.
+ std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; }
+
+ //! Convert a public key to its Hash160 bytes (precomputed).
+ std::vector<unsigned char> ToPKHBytes(const CPubKey& key) const
+ {
+ auto it = g_testdata->pkhashes.find(key);
+ assert(it != g_testdata->pkhashes.end());
+ return {it->second.begin(), it->second.end()};
+ }
+
+ //! Parse a public key from a range of hex characters.
+ template<typename I>
+ bool FromString(I first, I last, CPubKey& key) const {
+ auto bytes = ParseHex(std::string(first, last));
+ key.Set(bytes.begin(), bytes.end());
+ return key.IsValid();
+ }
+
+ template<typename I>
+ bool FromPKBytes(I first, I last, CPubKey& key) const {
+ key.Set(first, last);
+ return key.IsValid();
+ }
+
+ template<typename I>
+ bool FromPKHBytes(I first, I last, CPubKey& key) const {
+ assert(last - first == 20);
+ CKeyID keyid;
+ std::copy(first, last, keyid.begin());
+ auto it = g_testdata->pkmap.find(keyid);
+ assert(it != g_testdata->pkmap.end());
+ key = it->second;
+ return true;
+ }
+};
+
+//! Singleton instance of KeyConverter.
+const KeyConverter CONVERTER{};
+
+using miniscript::operator"" _mst;
+
+enum TestMode : int {
+ TESTMODE_INVALID = 0,
+ TESTMODE_VALID = 1,
+ TESTMODE_NONMAL = 2,
+ TESTMODE_NEEDSIG = 4,
+ TESTMODE_TIMELOCKMIX = 8
+};
+
+void Test(const std::string& ms, const std::string& hexscript, int mode, int opslimit = -1, int stacklimit = -1)
+{
+ auto node = miniscript::FromString(ms, CONVERTER);
+ if (mode == TESTMODE_INVALID) {
+ BOOST_CHECK_MESSAGE(!node || !node->IsValid(), "Unexpectedly valid: " + ms);
+ } else {
+ BOOST_CHECK_MESSAGE(node, "Unparseable: " + ms);
+ BOOST_CHECK_MESSAGE(node->IsValid(), "Invalid: " + ms);
+ BOOST_CHECK_MESSAGE(node->IsValidTopLevel(), "Invalid top level: " + ms);
+ auto computed_script = node->ToScript(CONVERTER);
+ BOOST_CHECK_MESSAGE(node->ScriptSize() == computed_script.size(), "Script size mismatch: " + ms);
+ if (hexscript != "?") BOOST_CHECK_MESSAGE(HexStr(computed_script) == hexscript, "Script mismatch: " + ms + " (" + HexStr(computed_script) + " vs " + hexscript + ")");
+ BOOST_CHECK_MESSAGE(node->IsNonMalleable() == !!(mode & TESTMODE_NONMAL), "Malleability mismatch: " + ms);
+ BOOST_CHECK_MESSAGE(node->NeedsSignature() == !!(mode & TESTMODE_NEEDSIG), "Signature necessity mismatch: " + ms);
+ BOOST_CHECK_MESSAGE((node->GetType() << "k"_mst) == !(mode & TESTMODE_TIMELOCKMIX), "Timelock mix mismatch: " + ms);
+ auto inferred_miniscript = miniscript::FromScript(computed_script, CONVERTER);
+ BOOST_CHECK_MESSAGE(inferred_miniscript, "Cannot infer miniscript from script: " + ms);
+ BOOST_CHECK_MESSAGE(inferred_miniscript->ToScript(CONVERTER) == computed_script, "Roundtrip failure: miniscript->script != miniscript->script->miniscript->script: " + ms);
+ if (opslimit != -1) BOOST_CHECK_MESSAGE((int)node->GetOps() == opslimit, "Ops limit mismatch: " << ms << " (" << node->GetOps() << " vs " << opslimit << ")");
+ if (stacklimit != -1) BOOST_CHECK_MESSAGE((int)node->GetStackSize() == stacklimit, "Stack limit mismatch: " << ms << " (" << node->GetStackSize() << " vs " << stacklimit << ")");
+ }
+}
+} // namespace
+
+BOOST_FIXTURE_TEST_SUITE(miniscript_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(fixed_tests)
+{
+ g_testdata.reset(new TestData());
+
+ // Validity rules
+ Test("l:older(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(1): valid
+ Test("l:older(0)", "?", TESTMODE_INVALID); // older(0): k must be at least 1
+ Test("l:older(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(2147483647): valid
+ Test("l:older(2147483648)", "?", TESTMODE_INVALID); // older(2147483648): k must be below 2^31
+ Test("u:after(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(1): valid
+ Test("u:after(0)", "?", TESTMODE_INVALID); // after(0): k must be at least 1
+ Test("u:after(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(2147483647): valid
+ Test("u:after(2147483648)", "?", TESTMODE_INVALID); // after(2147483648): k must be below 2^31
+ Test("andor(0,1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,B,B): valid
+ Test("andor(a:0,1,1)", "?", TESTMODE_INVALID); // andor(Wdu,B,B): X must be B
+ Test("andor(0,a:1,a:1)", "?", TESTMODE_INVALID); // andor(Bdu,W,W): Y and Z must be B/V/K
+ Test("andor(1,1,1)", "?", TESTMODE_INVALID); // andor(Bu,B,B): X must be d
+ Test("andor(n:or_i(0,after(1)),1,1)", "?", TESTMODE_VALID); // andor(Bdu,B,B): valid
+ Test("andor(or_i(0,after(1)),1,1)", "?", TESTMODE_INVALID); // andor(Bd,B,B): X must be u
+ Test("c:andor(0,pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // andor(Bdu,K,K): valid
+ Test("t:andor(0,v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,V,V): valid
+ Test("and_v(v:1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,B): valid
+ Test("t:and_v(v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,V): valid
+ Test("c:and_v(v:1,pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // and_v(V,K): valid
+ Test("and_v(1,1)", "?", TESTMODE_INVALID); // and_v(B,B): X must be V
+ Test("and_v(pk_k(02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),1)", "?", TESTMODE_INVALID); // and_v(K,B): X must be V
+ Test("and_v(v:1,a:1)", "?", TESTMODE_INVALID); // and_v(K,W): Y must be B/V/K
+ Test("and_b(1,a:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_b(B,W): valid
+ Test("and_b(1,1)", "?", TESTMODE_INVALID); // and_b(B,B): Y must W
+ Test("and_b(v:1,a:1)", "?", TESTMODE_INVALID); // and_b(V,W): X must be B
+ Test("and_b(a:1,a:1)", "?", TESTMODE_INVALID); // and_b(W,W): X must be B
+ Test("and_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:1)", "?", TESTMODE_INVALID); // and_b(K,W): X must be B
+ Test("or_b(0,a:0)", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_b(Bd,Wd): valid
+ Test("or_b(1,a:0)", "?", TESTMODE_INVALID); // or_b(B,Wd): X must be d
+ Test("or_b(0,a:1)", "?", TESTMODE_INVALID); // or_b(Bd,W): Y must be d
+ Test("or_b(0,0)", "?", TESTMODE_INVALID); // or_b(Bd,Bd): Y must W
+ Test("or_b(v:0,a:0)", "?", TESTMODE_INVALID); // or_b(V,Wd): X must be B
+ Test("or_b(a:0,a:0)", "?", TESTMODE_INVALID); // or_b(Wd,Wd): X must be B
+ Test("or_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:0)", "?", TESTMODE_INVALID); // or_b(Kd,Wd): X must be B
+ Test("t:or_c(0,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_c(Bdu,V): valid
+ Test("t:or_c(a:0,v:1)", "?", TESTMODE_INVALID); // or_c(Wdu,V): X must be B
+ Test("t:or_c(1,v:1)", "?", TESTMODE_INVALID); // or_c(Bu,V): X must be d
+ Test("t:or_c(n:or_i(0,after(1)),v:1)", "?", TESTMODE_VALID); // or_c(Bdu,V): valid
+ Test("t:or_c(or_i(0,after(1)),v:1)", "?", TESTMODE_INVALID); // or_c(Bd,V): X must be u
+ Test("t:or_c(0,1)", "?", TESTMODE_INVALID); // or_c(Bdu,B): Y must be V
+ Test("or_d(0,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_d(Bdu,B): valid
+ Test("or_d(a:0,1)", "?", TESTMODE_INVALID); // or_d(Wdu,B): X must be B
+ Test("or_d(1,1)", "?", TESTMODE_INVALID); // or_d(Bu,B): X must be d
+ Test("or_d(n:or_i(0,after(1)),1)", "?", TESTMODE_VALID); // or_d(Bdu,B): valid
+ Test("or_d(or_i(0,after(1)),1)", "?", TESTMODE_INVALID); // or_d(Bd,B): X must be u
+ Test("or_d(0,v:1)", "?", TESTMODE_INVALID); // or_d(Bdu,V): Y must be B
+ Test("or_i(1,1)", "?", TESTMODE_VALID); // or_i(B,B): valid
+ Test("t:or_i(v:1,v:1)", "?", TESTMODE_VALID); // or_i(V,V): valid
+ Test("c:or_i(pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_i(K,K): valid
+ Test("or_i(a:1,a:1)", "?", TESTMODE_INVALID); // or_i(W,W): X and Y must be B/V/K
+ Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid
+ Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid
+ Test("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_k
+ Test("pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "76a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_h
+
+
+ // Randomly generated test set that covers the majority of type and node type combinations
+ Test("lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868", TESTMODE_VALID | TESTMODE_NONMAL, 12, 4);
+ Test("uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 14, 6);
+ Test("or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", TESTMODE_VALID, 14, 6);
+ Test("j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268", TESTMODE_VALID | TESTMODE_NONMAL, 11, 2);
+ Test("t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", TESTMODE_VALID | TESTMODE_NONMAL, 12, 4);
+ Test("t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", TESTMODE_VALID | TESTMODE_NONMAL, 13, 6);
+ Test("or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", TESTMODE_VALID | TESTMODE_NONMAL, 15, 8);
+ Test("or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", TESTMODE_VALID, 16, 2);
+ Test("and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 11, 6);
+ Test("j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", TESTMODE_VALID | TESTMODE_NEEDSIG, 14, 5);
+ Test("and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", TESTMODE_VALID, 12, 2);
+ Test("j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", TESTMODE_VALID, 16, 3);
+ Test("and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", TESTMODE_VALID | TESTMODE_NONMAL, 15, 3);
+ Test("thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 13, 7);
+ Test("and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", TESTMODE_VALID, 14, 3);
+ Test("or_d(d:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b26968736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", TESTMODE_VALID, 14, 3);
+ Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", TESTMODE_VALID | TESTMODE_NEEDSIG, 8, 3);
+ Test("c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 10, 6);
+ Test("and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", TESTMODE_VALID, 14, 3);
+ Test("andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", TESTMODE_VALID, 20, 3);
+ Test("or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", TESTMODE_VALID | TESTMODE_NONMAL, 10, 3);
+ Test("thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", TESTMODE_VALID, 18, 5);
+ Test("and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", TESTMODE_VALID | TESTMODE_NEEDSIG, 13, 4);
+ Test("and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(4252898),a:older(16)))", "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TIMELOCKMIX, 12, 3);
+ Test("c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 12, 4);
+ Test("or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", TESTMODE_VALID | TESTMODE_NONMAL, 13, 4);
+ Test("c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 18, 4);
+ Test("c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 23, 5);
+ Test("c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 17, 6);
+ Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", TESTMODE_VALID, 18, 4);
+ Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 5);
+
+ // Misc unit tests
+ // A Script with a non minimal push is invalid
+ std::vector<unsigned char> nonminpush = ParseHex("0000210232780000feff00ffffffffffff21ff005f00ae21ae00000000060602060406564c2102320000060900fe00005f00ae21ae00100000060606060606000000000000000000000000000000000000000000000000000000000000000000");
+ const CScript nonminpush_script(nonminpush.begin(), nonminpush.end());
+ BOOST_CHECK(miniscript::FromScript(nonminpush_script, CONVERTER) == nullptr);
+ // A non-minimal VERIFY (<key> CHECKSIG VERIFY 1)
+ std::vector<unsigned char> nonminverify = ParseHex("2103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7ac6951");
+ const CScript nonminverify_script(nonminverify.begin(), nonminverify.end());
+ BOOST_CHECK(miniscript::FromScript(nonminverify_script, CONVERTER) == nullptr);
+ // A threshold as large as the number of subs is valid.
+ Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL);
+ // A threshold of 1 is valid.
+ Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL);
+ // A threshold with a k larger than the number of subs is invalid
+ Test("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_INVALID);
+ // A threshold with a k null is invalid
+ Test("thresh(0,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_INVALID);
+ // For CHECKMULTISIG the OP cost is the number of keys, but the stack size is the number of sigs (+1)
+ const auto ms_multi = miniscript::FromString("multi(1,03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", CONVERTER);
+ BOOST_CHECK(ms_multi);
+ BOOST_CHECK_EQUAL(ms_multi->GetOps(), 4); // 3 pubkeys + CMS
+ BOOST_CHECK_EQUAL(ms_multi->GetStackSize(), 3); // 1 sig + dummy elem + script push
+
+ // Timelock tests
+ Test("after(100)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock
+ Test("after(1000000000)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only timelock
+ Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid
+ Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid
+ /* This is correctly detected as non-malleable but for the wrong reason. The type system assumes that branches 1 and 2
+ can be spent together to create a non-malleble witness, but because of mixing of timelocks they cannot be spent together.
+ But since exactly one of the two after's can be satisfied, the witness involving the key cannot be malleated.
+ */
+ Test("thresh(2,ltv:after(1000000000),altv:after(100),a:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", "?", TESTMODE_VALID | TESTMODE_TIMELOCKMIX | TESTMODE_NONMAL); // thresh with k = 2
+ // This is actually non-malleable in practice, but we cannot detect it in type system. See above rationale
+ Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "?", TESTMODE_VALID); // thresh with k = 1
+
+
+ g_testdata.reset();
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp
index 6ec3fb0c6b..d519a4442f 100644
--- a/src/test/net_peer_eviction_tests.cpp
+++ b/src/test/net_peer_eviction_tests.cpp
@@ -478,8 +478,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
c.m_network = NET_IPV6;
}
},
- /* protected_peer_ids */ {0, 4},
- /* unprotected_peer_ids */ {1, 2, 3},
+ /*protected_peer_ids=*/{0, 4},
+ /*unprotected_peer_ids=*/{1, 2, 3},
random_context));
// Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer
@@ -627,7 +627,7 @@ BOOST_AUTO_TEST_CASE(peer_eviction_test)
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
if (candidate.id <= 7) {
- candidate.fRelayTxes = false;
+ candidate.m_relay_txs = false;
candidate.fRelevantServices = true;
}
},
@@ -646,7 +646,7 @@ BOOST_AUTO_TEST_CASE(peer_eviction_test)
number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
if (candidate.id <= 7) {
- candidate.fRelayTxes = false;
+ candidate.m_relay_txs = false;
candidate.fRelevantServices = true;
}
},
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index fcb1a80765..e7c01bd6d0 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -732,26 +732,31 @@ BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network)
BOOST_CHECK(IsReachable(NET_IPV6));
BOOST_CHECK(IsReachable(NET_ONION));
BOOST_CHECK(IsReachable(NET_I2P));
+ BOOST_CHECK(IsReachable(NET_CJDNS));
SetReachable(NET_IPV4, false);
SetReachable(NET_IPV6, false);
SetReachable(NET_ONION, false);
SetReachable(NET_I2P, false);
+ SetReachable(NET_CJDNS, false);
BOOST_CHECK(!IsReachable(NET_IPV4));
BOOST_CHECK(!IsReachable(NET_IPV6));
BOOST_CHECK(!IsReachable(NET_ONION));
BOOST_CHECK(!IsReachable(NET_I2P));
+ BOOST_CHECK(!IsReachable(NET_CJDNS));
SetReachable(NET_IPV4, true);
SetReachable(NET_IPV6, true);
SetReachable(NET_ONION, true);
SetReachable(NET_I2P, true);
+ SetReachable(NET_CJDNS, true);
BOOST_CHECK(IsReachable(NET_IPV4));
BOOST_CHECK(IsReachable(NET_IPV6));
BOOST_CHECK(IsReachable(NET_ONION));
BOOST_CHECK(IsReachable(NET_I2P));
+ BOOST_CHECK(IsReachable(NET_CJDNS));
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal)
diff --git a/src/test/rest_tests.cpp b/src/test/rest_tests.cpp
new file mode 100644
index 0000000000..20dfe4b41a
--- /dev/null
+++ b/src/test/rest_tests.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2012-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.
+
+#include <rest.h>
+#include <test/util/setup_common.h>
+
+#include <boost/test/unit_test.hpp>
+
+#include <string>
+
+BOOST_FIXTURE_TEST_SUITE(rest_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(test_query_string)
+{
+ std::string param;
+ RESTResponseFormat rf;
+ // No query string
+ rf = ParseDataFormat(param, "/rest/endpoint/someresource.json");
+ BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource");
+ BOOST_CHECK_EQUAL(rf, RESTResponseFormat::JSON);
+
+ // Query string with single parameter
+ rf = ParseDataFormat(param, "/rest/endpoint/someresource.bin?p1=v1");
+ BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource");
+ BOOST_CHECK_EQUAL(rf, RESTResponseFormat::BINARY);
+
+ // Query string with multiple parameters
+ rf = ParseDataFormat(param, "/rest/endpoint/someresource.hex?p1=v1&p2=v2");
+ BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource");
+ BOOST_CHECK_EQUAL(rf, RESTResponseFormat::HEX);
+
+ // Incorrectly formed query string will not be handled
+ rf = ParseDataFormat(param, "/rest/endpoint/someresource.json&p1=v1");
+ BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource.json&p1=v1");
+ BOOST_CHECK_EQUAL(rf, RESTResponseFormat::UNDEF);
+
+ // Omitted data format with query string should return UNDEF and hide query string
+ rf = ParseDataFormat(param, "/rest/endpoint/someresource?p1=v1");
+ BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource");
+ BOOST_CHECK_EQUAL(rf, RESTResponseFormat::UNDEF);
+
+ // Data format specified after query string
+ rf = ParseDataFormat(param, "/rest/endpoint/someresource?p1=v1.json");
+ BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource");
+ BOOST_CHECK_EQUAL(rf, RESTResponseFormat::UNDEF);
+}
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/script_segwit_tests.cpp b/src/test/script_segwit_tests.cpp
new file mode 100644
index 0000000000..2bad59805f
--- /dev/null
+++ b/src/test/script_segwit_tests.cpp
@@ -0,0 +1,164 @@
+// Copyright (c) 2012-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.
+
+#include <script/script.h>
+#include <test/util/setup_common.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(script_segwit_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Valid)
+{
+ uint256 dummy;
+ CScript p2wsh;
+ p2wsh << OP_0 << ToByteVector(dummy);
+ BOOST_CHECK(p2wsh.IsPayToWitnessScriptHash());
+
+ std::vector<unsigned char> bytes = {OP_0, 32};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_NotOp0)
+{
+ uint256 dummy;
+ CScript notp2wsh;
+ notp2wsh << OP_1 << ToByteVector(dummy);
+ BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Size)
+{
+ uint160 dummy;
+ CScript notp2wsh;
+ notp2wsh << OP_0 << ToByteVector(dummy);
+ BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Nop)
+{
+ uint256 dummy;
+ CScript notp2wsh;
+ notp2wsh << OP_0 << OP_NOP << ToByteVector(dummy);
+ BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_EmptyScript)
+{
+ CScript notp2wsh;
+ BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Pushdata)
+{
+ // A script is not P2WSH if OP_PUSHDATA is used to push the hash.
+ std::vector<unsigned char> bytes = {OP_0, OP_PUSHDATA1, 32};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash());
+
+ bytes = {OP_0, OP_PUSHDATA2, 32, 0};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash());
+
+ bytes = {OP_0, OP_PUSHDATA4, 32, 0, 0, 0};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash());
+}
+
+namespace {
+
+bool IsExpectedWitnessProgram(const CScript& script, const int expectedVersion, const std::vector<unsigned char>& expectedProgram)
+{
+ int actualVersion;
+ std::vector<unsigned char> actualProgram;
+ if (!script.IsWitnessProgram(actualVersion, actualProgram)) {
+ return false;
+ }
+ BOOST_CHECK_EQUAL(actualVersion, expectedVersion);
+ BOOST_CHECK(actualProgram == expectedProgram);
+ return true;
+}
+
+bool IsNoWitnessProgram(const CScript& script)
+{
+ int dummyVersion;
+ std::vector<unsigned char> dummyProgram;
+ return !script.IsWitnessProgram(dummyVersion, dummyProgram);
+}
+
+} // anonymous namespace
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Valid)
+{
+ // Witness programs have a minimum data push of 2 bytes.
+ std::vector<unsigned char> program = {42, 18};
+ CScript wit;
+ wit << OP_0 << program;
+ BOOST_CHECK(IsExpectedWitnessProgram(wit, 0, program));
+
+ wit.clear();
+ // Witness programs have a maximum data push of 40 bytes.
+ program.resize(40);
+ wit << OP_16 << program;
+ BOOST_CHECK(IsExpectedWitnessProgram(wit, 16, program));
+
+ program.resize(32);
+ std::vector<unsigned char> bytes = {OP_5, static_cast<unsigned char>(program.size())};
+ bytes.insert(bytes.end(), program.begin(), program.end());
+ BOOST_CHECK(IsExpectedWitnessProgram(CScript(bytes.begin(), bytes.end()), 5, program));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Version)
+{
+ std::vector<unsigned char> program(10);
+ CScript nowit;
+ nowit << OP_1NEGATE << program;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Size)
+{
+ std::vector<unsigned char> program(1);
+ CScript nowit;
+ nowit << OP_0 << program;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+
+ nowit.clear();
+ program.resize(41);
+ nowit << OP_0 << program;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Nop)
+{
+ std::vector<unsigned char> program(10);
+ CScript nowit;
+ nowit << OP_0 << OP_NOP << program;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_EmptyScript)
+{
+ CScript nowit;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Pushdata)
+{
+ // A script is no witness program if OP_PUSHDATA is used to push the hash.
+ std::vector<unsigned char> bytes = {OP_0, OP_PUSHDATA1, 32};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end())));
+
+ bytes = {OP_0, OP_PUSHDATA2, 32, 0};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end())));
+
+ bytes = {OP_0, OP_PUSHDATA4, 32, 0, 0, 0};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end())));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp
index 9c6950f11f..3f5353b5a2 100644
--- a/src/test/system_tests.cpp
+++ b/src/test/system_tests.cpp
@@ -12,7 +12,16 @@
// For details see https://github.com/bitcoin/bitcoin/pull/22348.
#define __kernel_entry
#endif
+#if defined(__GNUC__)
+// Boost 1.78 requires the following workaround.
+// See: https://github.com/boostorg/process/issues/235
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnarrowing"
+#endif
#include <boost/process.hpp>
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
#endif // ENABLE_EXTERNAL_SIGNER
#include <boost/test/unit_test.hpp>
diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp
index 6013080dc6..079b753304 100644
--- a/src/test/txpackage_tests.cpp
+++ b/src/test/txpackage_tests.cpp
@@ -73,19 +73,19 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
CKey parent_key;
parent_key.MakeNewKey(true);
CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
- auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[0], /*input_vout=*/0,
- /*input_height=*/ 0, /*input_signing_key=*/coinbaseKey,
- /*output_destination=*/ parent_locking_script,
- /*output_amount=*/ CAmount(49 * COIN), /*submit=*/false);
+ auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/parent_locking_script,
+ /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
CKey child_key;
child_key.MakeNewKey(true);
CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
- auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/ tx_parent, /*input_vout=*/0,
- /*input_height=*/ 101, /*input_signing_key=*/parent_key,
+ auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
+ /*input_height=*/101, /*input_signing_key=*/parent_key,
/*output_destination=*/child_locking_script,
- /*output_amount=*/ CAmount(48 * COIN), /*submit=*/false);
+ /*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {tx_parent, tx_child}, /*test_accept=*/true);
BOOST_CHECK_MESSAGE(result_parent_child.m_state.IsValid(),
@@ -98,7 +98,9 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
BOOST_CHECK(it_child != result_parent_child.m_tx_results.end());
BOOST_CHECK_MESSAGE(it_child->second.m_state.IsValid(),
"Package validation unexpectedly failed: " << it_child->second.m_state.GetRejectReason());
-
+ BOOST_CHECK(result_parent_child.m_package_feerate.has_value());
+ BOOST_CHECK(result_parent_child.m_package_feerate.value() ==
+ CFeeRate(2 * COIN, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child)));
// A single, giant transaction submitted through ProcessNewPackage fails on single tx policy.
CTransactionRef giant_ptx = create_placeholder_tx(999, 999);
@@ -110,6 +112,7 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
auto it_giant_tx = result_single_large.m_tx_results.find(giant_ptx->GetWitnessHash());
BOOST_CHECK(it_giant_tx != result_single_large.m_tx_results.end());
BOOST_CHECK_EQUAL(it_giant_tx->second.m_state.GetRejectReason(), "tx-size");
+ BOOST_CHECK(result_single_large.m_package_feerate == std::nullopt);
// Check that mempool size hasn't changed.
BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);
@@ -128,11 +131,11 @@ BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100Setup)
// Parent and Child Package
{
auto mtx_parent = CreateValidMempoolTransaction(m_coinbase_txns[0], 0, 0, coinbaseKey, spk,
- CAmount(49 * COIN), /* submit */ false);
+ CAmount(49 * COIN), /*submit=*/false);
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
auto mtx_child = CreateValidMempoolTransaction(tx_parent, 0, 101, placeholder_key, spk2,
- CAmount(48 * COIN), /* submit */ false);
+ CAmount(48 * COIN), /*submit=*/false);
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
PackageValidationState state;
@@ -218,26 +221,27 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
// Unrelated transactions are not allowed in package submission.
Package package_unrelated;
for (size_t i{0}; i < 10; ++i) {
- auto mtx = CreateValidMempoolTransaction(/* input_transaction */ m_coinbase_txns[i + 25], /* vout */ 0,
- /* input_height */ 0, /* input_signing_key */ coinbaseKey,
- /* output_destination */ parent_locking_script,
- /* output_amount */ CAmount(49 * COIN), /* submit */ false);
+ auto mtx = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[i + 25], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/parent_locking_script,
+ /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
package_unrelated.emplace_back(MakeTransactionRef(mtx));
}
auto result_unrelated_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_unrelated, /* test_accept */ false);
+ package_unrelated, /*test_accept=*/false);
BOOST_CHECK(result_unrelated_submit.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ BOOST_CHECK(result_unrelated_submit.m_package_feerate == std::nullopt);
// Parent and Child (and Grandchild) Package
Package package_parent_child;
Package package_3gen;
- auto mtx_parent = CreateValidMempoolTransaction(/* input_transaction */ m_coinbase_txns[0], /* vout */ 0,
- /* input_height */ 0, /* input_signing_key */ coinbaseKey,
- /* output_destination */ parent_locking_script,
- /* output_amount */ CAmount(49 * COIN), /* submit */ false);
+ auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/parent_locking_script,
+ /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
package_parent_child.push_back(tx_parent);
package_3gen.push_back(tx_parent);
@@ -245,10 +249,10 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
CKey child_key;
child_key.MakeNewKey(true);
CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
- auto mtx_child = CreateValidMempoolTransaction(/* input_transaction */ tx_parent, /* vout */ 0,
- /* input_height */ 101, /* input_signing_key */ parent_key,
- /* output_destination */ child_locking_script,
- /* output_amount */ CAmount(48 * COIN), /* submit */ false);
+ auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
+ /*input_height=*/101, /*input_signing_key=*/parent_key,
+ /*output_destination=*/child_locking_script,
+ /*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
package_parent_child.push_back(tx_child);
package_3gen.push_back(tx_child);
@@ -256,21 +260,22 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
CKey grandchild_key;
grandchild_key.MakeNewKey(true);
CScript grandchild_locking_script = GetScriptForDestination(PKHash(grandchild_key.GetPubKey()));
- auto mtx_grandchild = CreateValidMempoolTransaction(/* input_transaction */ tx_child, /* vout */ 0,
- /* input_height */ 101, /* input_signing_key */ child_key,
- /* output_destination */ grandchild_locking_script,
- /* output_amount */ CAmount(47 * COIN), /* submit */ false);
+ auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/tx_child, /*input_vout=*/0,
+ /*input_height=*/101, /*input_signing_key=*/child_key,
+ /*output_destination=*/grandchild_locking_script,
+ /*output_amount=*/CAmount(47 * COIN), /*submit=*/false);
CTransactionRef tx_grandchild = MakeTransactionRef(mtx_grandchild);
package_3gen.push_back(tx_grandchild);
// 3 Generations is not allowed.
{
auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_3gen, /* test_accept */ false);
+ package_3gen, /*test_accept=*/false);
BOOST_CHECK(result_3gen_submit.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ BOOST_CHECK(result_3gen_submit.m_package_feerate == std::nullopt);
}
// Child with missing parent.
@@ -280,18 +285,19 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
package_missing_parent.push_back(MakeTransactionRef(mtx_child));
{
const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_missing_parent, /* test_accept */ false);
+ package_missing_parent, /*test_accept=*/false);
BOOST_CHECK(result_missing_parent.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "package-not-child-with-unconfirmed-parents");
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ BOOST_CHECK(result_missing_parent.m_package_feerate == std::nullopt);
}
// Submit package with parent + child.
{
const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_parent_child, /* test_accept */ false);
+ package_parent_child, /*test_accept=*/false);
expected_pool_size += 2;
BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(),
"Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason());
@@ -305,12 +311,16 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child->GetHash())));
+
+ // Since both transactions have high feerates, they each passed validation individually.
+ // Package validation was unnecessary, so there is no package feerate.
+ BOOST_CHECK(submit_parent_child.m_package_feerate == std::nullopt);
}
// Already-in-mempool transactions should be detected and de-duplicated.
{
const auto submit_deduped = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- package_parent_child, /* test_accept */ false);
+ package_parent_child, /*test_accept=*/false);
BOOST_CHECK_MESSAGE(submit_deduped.m_state.IsValid(),
"Package validation unexpectedly failed: " << submit_deduped.m_state.GetRejectReason());
auto it_parent_deduped = submit_deduped.m_tx_results.find(tx_parent->GetWitnessHash());
@@ -325,6 +335,8 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child->GetHash())));
+
+ BOOST_CHECK(submit_deduped.m_package_feerate == std::nullopt);
}
}
@@ -340,10 +352,10 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
// and the mempool entry's wtxid returned.
CScript witnessScript = CScript() << OP_DROP << OP_TRUE;
CScript scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
- auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[0], /*vout=*/ 0,
- /*input_height=*/ 0, /*input_signing_key=*/ coinbaseKey,
- /*output_destination=*/ scriptPubKey,
- /*output_amount=*/ CAmount(49 * COIN), /*submit=*/ false);
+ auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/scriptPubKey,
+ /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
CTransactionRef ptx_parent = MakeTransactionRef(mtx_parent);
// Make two children with the same txid but different witnesses.
@@ -384,7 +396,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
// same-txid-different-witness.
{
const auto submit_witness1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- {ptx_parent, ptx_child1}, /*test_accept=*/ false);
+ {ptx_parent, ptx_child1}, /*test_accept=*/false);
BOOST_CHECK_MESSAGE(submit_witness1.m_state.IsValid(),
"Package validation unexpectedly failed: " << submit_witness1.m_state.GetRejectReason());
auto it_parent1 = submit_witness1.m_tx_results.find(ptx_parent->GetWitnessHash());
@@ -399,8 +411,12 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_parent->GetHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_child1->GetHash())));
+ // Child2 would have been validated individually.
+ BOOST_CHECK(submit_witness1.m_package_feerate == std::nullopt);
+
const auto submit_witness2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- {ptx_parent, ptx_child2}, /*test_accept=*/ false);
+ {ptx_parent, ptx_child2}, /*test_accept=*/false);
+ BOOST_CHECK(submit_witness2.m_package_feerate == std::nullopt);
BOOST_CHECK_MESSAGE(submit_witness2.m_state.IsValid(),
"Package validation unexpectedly failed: " << submit_witness2.m_state.GetRejectReason());
auto it_parent2_deduped = submit_witness2.m_tx_results.find(ptx_parent->GetWitnessHash());
@@ -417,13 +433,14 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
// Deduplication should work when wtxid != txid. Submit package with the already-in-mempool
// transactions again, which should not fail.
const auto submit_segwit_dedup = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- {ptx_parent, ptx_child1}, /*test_accept=*/ false);
+ {ptx_parent, ptx_child1}, /*test_accept=*/false);
BOOST_CHECK_MESSAGE(submit_segwit_dedup.m_state.IsValid(),
"Package validation unexpectedly failed: " << submit_segwit_dedup.m_state.GetRejectReason());
auto it_parent_dup = submit_segwit_dedup.m_tx_results.find(ptx_parent->GetWitnessHash());
auto it_child_dup = submit_segwit_dedup.m_tx_results.find(ptx_child1->GetWitnessHash());
BOOST_CHECK(it_parent_dup->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
BOOST_CHECK(it_child_dup->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
+ BOOST_CHECK(submit_witness2.m_package_feerate == std::nullopt);
}
// Try submitting Package1{child2, grandchild} where child2 is same-txid-different-witness as
@@ -436,16 +453,16 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
CKey grandchild_key;
grandchild_key.MakeNewKey(true);
CScript grandchild_locking_script = GetScriptForDestination(WitnessV0KeyHash(grandchild_key.GetPubKey()));
- auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/ ptx_child2, /* vout=*/ 0,
- /*input_height=*/ 0, /*input_signing_key=*/ child_key,
- /*output_destination=*/ grandchild_locking_script,
- /*output_amount=*/ CAmount(47 * COIN), /*submit=*/ false);
+ auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/ptx_child2, /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/child_key,
+ /*output_destination=*/grandchild_locking_script,
+ /*output_amount=*/CAmount(47 * COIN), /*submit=*/false);
CTransactionRef ptx_grandchild = MakeTransactionRef(mtx_grandchild);
// We already submitted child1 above.
{
const auto submit_spend_ignored = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
- {ptx_child2, ptx_grandchild}, /*test_accept=*/ false);
+ {ptx_child2, ptx_grandchild}, /*test_accept=*/false);
BOOST_CHECK_MESSAGE(submit_spend_ignored.m_state.IsValid(),
"Package validation unexpectedly failed: " << submit_spend_ignored.m_state.GetRejectReason());
auto it_child2_ignored = submit_spend_ignored.m_tx_results.find(ptx_child2->GetWitnessHash());
@@ -458,6 +475,9 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_child2->GetHash())));
BOOST_CHECK(!m_node.mempool->exists(GenTxid::Wtxid(ptx_child2->GetWitnessHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Wtxid(ptx_grandchild->GetWitnessHash())));
+
+ // Since child2 is ignored, grandchild would be validated individually.
+ BOOST_CHECK(submit_spend_ignored.m_package_feerate == std::nullopt);
}
// A package Package{parent1, parent2, parent3, child} where the parents are a mixture of
@@ -471,10 +491,10 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
acs_witness.stack.push_back(std::vector<unsigned char>(acs_script.begin(), acs_script.end()));
// parent1 will already be in the mempool
- auto mtx_parent1 = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[1], /*vout=*/ 0,
- /*input_height=*/ 0, /*input_signing_key=*/ coinbaseKey,
- /*output_destination=*/ acs_spk,
- /*output_amount=*/ CAmount(49 * COIN), /*submit=*/ true);
+ auto mtx_parent1 = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/acs_spk,
+ /*output_amount=*/CAmount(49 * COIN), /*submit=*/true);
CTransactionRef ptx_parent1 = MakeTransactionRef(mtx_parent1);
package_mixed.push_back(ptx_parent1);
@@ -489,10 +509,10 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
parent2_witness2.stack.push_back(std::vector<unsigned char>(grandparent2_script.begin(), grandparent2_script.end()));
// Create grandparent2 creating an output with multiple spending paths. Submit to mempool.
- auto mtx_grandparent2 = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[2], /* vout=*/ 0,
- /*input_height=*/ 0, /*input_signing_key=*/ coinbaseKey,
- /*output_destination=*/ grandparent2_spk,
- /*output_amount=*/ CAmount(49 * COIN), /*submit=*/ true);
+ auto mtx_grandparent2 = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[2], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/grandparent2_spk,
+ /*output_amount=*/CAmount(49 * COIN), /*submit=*/true);
CTransactionRef ptx_grandparent2 = MakeTransactionRef(mtx_grandparent2);
CMutableTransaction mtx_parent2_v1;
@@ -516,11 +536,11 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
BOOST_CHECK(parent2_v2_result.m_result_type == MempoolAcceptResult::ResultType::VALID);
package_mixed.push_back(ptx_parent2_v1);
- // parent3 will be a new transaction
- auto mtx_parent3 = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[3], /*vout=*/ 0,
- /*input_height=*/ 0, /*input_signing_key=*/ coinbaseKey,
- /*output_destination=*/ acs_spk,
- /*output_amount=*/ CAmount(49 * COIN), /*submit=*/ false);
+ // parent3 will be a new transaction. Put 0 fees on it to make it invalid on its own.
+ auto mtx_parent3 = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[3], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/acs_spk,
+ /*output_amount=*/CAmount(50 * COIN), /*submit=*/false);
CTransactionRef ptx_parent3 = MakeTransactionRef(mtx_parent3);
package_mixed.push_back(ptx_parent3);
@@ -536,7 +556,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
mtx_mixed_child.vin[0].scriptWitness = acs_witness;
mtx_mixed_child.vin[1].scriptWitness = acs_witness;
mtx_mixed_child.vin[2].scriptWitness = acs_witness;
- mtx_mixed_child.vout.push_back(CTxOut(145 * COIN, mixed_child_spk));
+ mtx_mixed_child.vout.push_back(CTxOut((48 + 49 + 50 - 1) * COIN, mixed_child_spk));
CTransactionRef ptx_mixed_child = MakeTransactionRef(mtx_mixed_child);
package_mixed.push_back(ptx_mixed_child);
@@ -568,6 +588,205 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
BOOST_CHECK(!m_node.mempool->exists(GenTxid::Wtxid(ptx_parent2_v1->GetWitnessHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_parent3->GetHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_mixed_child->GetHash())));
+
+ // package feerate should include parent3 and child. It should not include parent1 or parent2_v1.
+ BOOST_CHECK(mixed_result.m_package_feerate.has_value());
+ const CFeeRate expected_feerate(1 * COIN, GetVirtualTransactionSize(*ptx_parent3) + GetVirtualTransactionSize(*ptx_mixed_child));
+ BOOST_CHECK_MESSAGE(mixed_result.m_package_feerate.value() == expected_feerate,
+ strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
+ mixed_result.m_package_feerate.value().ToString()));
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
+{
+ mineBlocks(5);
+ LOCK(::cs_main);
+ size_t expected_pool_size = m_node.mempool->size();
+ CKey child_key;
+ child_key.MakeNewKey(true);
+ CScript parent_spk = GetScriptForDestination(WitnessV0KeyHash(child_key.GetPubKey()));
+ CKey grandchild_key;
+ grandchild_key.MakeNewKey(true);
+ CScript child_spk = GetScriptForDestination(WitnessV0KeyHash(grandchild_key.GetPubKey()));
+
+ // zero-fee parent and high-fee child package
+ const CAmount coinbase_value{50 * COIN};
+ const CAmount parent_value{coinbase_value - 0};
+ const CAmount child_value{parent_value - COIN};
+
+ Package package_cpfp;
+ auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/parent_spk,
+ /*output_amount=*/parent_value, /*submit=*/false);
+ CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
+ package_cpfp.push_back(tx_parent);
+
+ auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
+ /*input_height=*/101, /*input_signing_key=*/child_key,
+ /*output_destination=*/child_spk,
+ /*output_amount=*/child_value, /*submit=*/false);
+ CTransactionRef tx_child = MakeTransactionRef(mtx_child);
+ package_cpfp.push_back(tx_child);
+
+ // Package feerate is calculated using modified fees, and prioritisetransaction accepts negative
+ // fee deltas. This should be taken into account. De-prioritise the parent transaction by -1BTC,
+ // bringing the package feerate to 0.
+ m_node.mempool->PrioritiseTransaction(tx_parent->GetHash(), -1 * COIN);
+ {
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
+ package_cpfp, /*test_accept=*/ false);
+ BOOST_CHECK_MESSAGE(submit_cpfp_deprio.m_state.IsInvalid(),
+ "Package validation unexpectedly succeeded: " << submit_cpfp_deprio.m_state.GetRejectReason());
+ BOOST_CHECK(submit_cpfp_deprio.m_tx_results.empty());
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ const CFeeRate expected_feerate(0, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child));
+ BOOST_CHECK(submit_cpfp_deprio.m_package_feerate.has_value());
+ BOOST_CHECK(submit_cpfp_deprio.m_package_feerate.value() == CFeeRate{0});
+ BOOST_CHECK_MESSAGE(submit_cpfp_deprio.m_package_feerate.value() == expected_feerate,
+ strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
+ submit_cpfp_deprio.m_package_feerate.value().ToString()));
+ }
+
+ // Clear the prioritisation of the parent transaction.
+ WITH_LOCK(m_node.mempool->cs, m_node.mempool->ClearPrioritisation(tx_parent->GetHash()));
+
+ // Package CPFP: Even though the parent pays 0 absolute fees, the child pays 1 BTC which is
+ // enough for the package feerate to meet the threshold.
+ {
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ const auto submit_cpfp = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
+ package_cpfp, /*test_accept=*/ false);
+ expected_pool_size += 2;
+ BOOST_CHECK_MESSAGE(submit_cpfp.m_state.IsValid(),
+ "Package validation unexpectedly failed: " << submit_cpfp.m_state.GetRejectReason());
+ auto it_parent = submit_cpfp.m_tx_results.find(tx_parent->GetWitnessHash());
+ auto it_child = submit_cpfp.m_tx_results.find(tx_child->GetWitnessHash());
+ BOOST_CHECK(it_parent != submit_cpfp.m_tx_results.end());
+ BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
+ BOOST_CHECK(it_parent->second.m_base_fees.value() == 0);
+ BOOST_CHECK(it_child != submit_cpfp.m_tx_results.end());
+ BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
+ BOOST_CHECK(it_child->second.m_base_fees.value() == COIN);
+
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash())));
+ BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child->GetHash())));
+
+ const CFeeRate expected_feerate(coinbase_value - child_value,
+ GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child));
+ BOOST_CHECK(expected_feerate.GetFeePerK() > 1000);
+ BOOST_CHECK(submit_cpfp.m_package_feerate.has_value());
+ BOOST_CHECK_MESSAGE(submit_cpfp.m_package_feerate.value() == expected_feerate,
+ strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
+ submit_cpfp.m_package_feerate.value().ToString()));
+ }
+
+ // Just because we allow low-fee parents doesn't mean we allow low-feerate packages.
+ // This package just pays 200 satoshis total. This would be enough to pay for the child alone,
+ // but isn't enough for the entire package to meet the 1sat/vbyte minimum.
+ Package package_still_too_low;
+ auto mtx_parent_cheap = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/parent_spk,
+ /*output_amount=*/coinbase_value, /*submit=*/false);
+ CTransactionRef tx_parent_cheap = MakeTransactionRef(mtx_parent_cheap);
+ package_still_too_low.push_back(tx_parent_cheap);
+
+ auto mtx_child_cheap = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_cheap, /*input_vout=*/0,
+ /*input_height=*/101, /*input_signing_key=*/child_key,
+ /*output_destination=*/child_spk,
+ /*output_amount=*/coinbase_value - 200, /*submit=*/false);
+ CTransactionRef tx_child_cheap = MakeTransactionRef(mtx_child_cheap);
+ package_still_too_low.push_back(tx_child_cheap);
+
+ // Cheap package should fail with package-fee-too-low.
+ {
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
+ package_still_too_low, /*test_accept=*/false);
+ BOOST_CHECK_MESSAGE(submit_package_too_low.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
+ BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
+ BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "package-fee-too-low");
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ const CFeeRate child_feerate(200, GetVirtualTransactionSize(*tx_child_cheap));
+ BOOST_CHECK(child_feerate.GetFeePerK() > 1000);
+ const CFeeRate expected_feerate(200,
+ GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
+ BOOST_CHECK(expected_feerate.GetFeePerK() < 1000);
+ BOOST_CHECK(submit_package_too_low.m_package_feerate.has_value());
+ BOOST_CHECK_MESSAGE(submit_package_too_low.m_package_feerate.value() == expected_feerate,
+ strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
+ submit_package_too_low.m_package_feerate.value().ToString()));
+ }
+
+ // Package feerate includes the modified fees of the transactions.
+ // This means a child with its fee delta from prioritisetransaction can pay for a parent.
+ m_node.mempool->PrioritiseTransaction(tx_child_cheap->GetHash(), 1 * COIN);
+ // Now that the child's fees have "increased" by 1 BTC, the cheap package should succeed.
+ {
+ const auto submit_prioritised_package = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
+ package_still_too_low, /*test_accept=*/false);
+ expected_pool_size += 2;
+ BOOST_CHECK_MESSAGE(submit_prioritised_package.m_state.IsValid(),
+ "Package validation unexpectedly failed" << submit_prioritised_package.m_state.GetRejectReason());
+ const CFeeRate expected_feerate(1 * COIN + 200,
+ GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
+ BOOST_CHECK(submit_prioritised_package.m_package_feerate.has_value());
+ BOOST_CHECK_MESSAGE(submit_prioritised_package.m_package_feerate.value() == expected_feerate,
+ strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
+ submit_prioritised_package.m_package_feerate.value().ToString()));
+ }
+
+ // Package feerate is calculated without topology in mind; it's just aggregating fees and sizes.
+ // However, this should not allow parents to pay for children. Each transaction should be
+ // validated individually first, eliminating sufficient-feerate parents before they are unfairly
+ // included in the package feerate. It's also important that the low-fee child doesn't prevent
+ // the parent from being accepted.
+ Package package_rich_parent;
+ const CAmount high_parent_fee{1 * COIN};
+ auto mtx_parent_rich = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[2], /*input_vout=*/0,
+ /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
+ /*output_destination=*/parent_spk,
+ /*output_amount=*/coinbase_value - high_parent_fee, /*submit=*/false);
+ CTransactionRef tx_parent_rich = MakeTransactionRef(mtx_parent_rich);
+ package_rich_parent.push_back(tx_parent_rich);
+
+ auto mtx_child_poor = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_rich, /*input_vout=*/0,
+ /*input_height=*/101, /*input_signing_key=*/child_key,
+ /*output_destination=*/child_spk,
+ /*output_amount=*/coinbase_value - high_parent_fee, /*submit=*/false);
+ CTransactionRef tx_child_poor = MakeTransactionRef(mtx_child_poor);
+ package_rich_parent.push_back(tx_child_poor);
+
+ // Parent pays 1 BTC and child pays none. The parent should be accepted without the child.
+ {
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ const auto submit_rich_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
+ package_rich_parent, /*test_accept=*/false);
+ expected_pool_size += 1;
+ BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
+
+ // The child would have been validated on its own and failed, then submitted as a "package" of 1.
+ // The package feerate is just the child's feerate, which is 0sat/vb.
+ BOOST_CHECK(submit_rich_parent.m_package_feerate.has_value());
+ BOOST_CHECK_MESSAGE(submit_rich_parent.m_package_feerate.value() == CFeeRate(),
+ "expected 0, got " << submit_rich_parent.m_package_feerate.value().ToString());
+ BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
+ BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "package-fee-too-low");
+
+ auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetWitnessHash());
+ BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end());
+ BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
+ BOOST_CHECK(it_parent->second.m_state.GetRejectReason() == "");
+ BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee,
+ strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value()));
+
+ BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
+ BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent_rich->GetHash())));
+ BOOST_CHECK(!m_node.mempool->exists(GenTxid::Txid(tx_child_poor->GetHash())));
}
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/util/net.cpp b/src/test/util/net.cpp
index fe3cf52974..62b770753a 100644
--- a/src/test/util/net.cpp
+++ b/src/test/util/net.cpp
@@ -52,7 +52,7 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candida
/*m_last_block_time=*/std::chrono::seconds{random_context.randrange(100)},
/*m_last_tx_time=*/std::chrono::seconds{random_context.randrange(100)},
/*fRelevantServices=*/random_context.randbool(),
- /*fRelayTxes=*/random_context.randbool(),
+ /*m_relay_txs=*/random_context.randbool(),
/*fBloomFilter=*/random_context.randbool(),
/*nKeyedNetGroup=*/random_context.randrange(100),
/*prefer_evict=*/random_context.randbool(),
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 1881573e7a..b5d8411e1d 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -78,6 +78,31 @@ BOOST_AUTO_TEST_CASE(util_datadir)
BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
}
+namespace {
+class NoCopyOrMove
+{
+public:
+ int i;
+ explicit NoCopyOrMove(int i) : i{i} { }
+
+ NoCopyOrMove() = delete;
+ NoCopyOrMove(const NoCopyOrMove&) = delete;
+ NoCopyOrMove(NoCopyOrMove&&) = delete;
+ NoCopyOrMove& operator=(const NoCopyOrMove&) = delete;
+ NoCopyOrMove& operator=(NoCopyOrMove&&) = delete;
+
+ operator bool() const { return i != 0; }
+
+ int get_ip1() { return i + 1; }
+ bool test()
+ {
+ // Check that Assume can be used within a lambda and still call methods
+ [&]() { Assume(get_ip1()); }();
+ return Assume(get_ip1() != 5);
+ }
+};
+} // namespace
+
BOOST_AUTO_TEST_CASE(util_check)
{
// Check that Assert can forward
@@ -89,6 +114,14 @@ BOOST_AUTO_TEST_CASE(util_check)
// Check that Assume can be used as unary expression
const bool result{Assume(two == 2)};
Assert(result);
+
+ // Check that Assert doesn't require copy/move
+ NoCopyOrMove x{9};
+ Assert(x).i += 3;
+ Assert(x).test();
+
+ // Check nested Asserts
+ BOOST_CHECK_EQUAL(Assert((Assert(x).test() ? 3 : 0)), 3);
}
BOOST_AUTO_TEST_CASE(util_criticalsection)
diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp
index 7ae384ceb3..a15094e5c8 100644
--- a/src/torcontrol.cpp
+++ b/src/torcontrol.cpp
@@ -53,6 +53,7 @@ static const float RECONNECT_TIMEOUT_EXP = 1.5;
* this is belt-and-suspenders sanity limit to prevent memory exhaustion.
*/
static const int MAX_LINE_LENGTH = 100000;
+static const uint16_t DEFAULT_TOR_SOCKS_PORT = 9050;
/****** Low-level TorControlConnection ********/
@@ -338,6 +339,73 @@ TorController::~TorController()
}
}
+void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlReply& reply)
+{
+ // NOTE: We can only get here if -onion is unset
+ std::string socks_location;
+ if (reply.code == 250) {
+ for (const auto& line : reply.lines) {
+ if (0 == line.compare(0, 20, "net/listeners/socks=")) {
+ const std::string port_list_str = line.substr(20);
+ std::vector<std::string> port_list;
+ boost::split(port_list, port_list_str, boost::is_any_of(" "));
+ for (auto& portstr : port_list) {
+ if (portstr.empty()) continue;
+ if ((portstr[0] == '"' || portstr[0] == '\'') && portstr.size() >= 2 && (*portstr.rbegin() == portstr[0])) {
+ portstr = portstr.substr(1, portstr.size() - 2);
+ if (portstr.empty()) continue;
+ }
+ socks_location = portstr;
+ if (0 == portstr.compare(0, 10, "127.0.0.1:")) {
+ // Prefer localhost - ignore other ports
+ break;
+ }
+ }
+ }
+ }
+ if (!socks_location.empty()) {
+ LogPrint(BCLog::TOR, "tor: Get SOCKS port command yielded %s\n", socks_location);
+ } else {
+ LogPrintf("tor: Get SOCKS port command returned nothing\n");
+ }
+ } else if (reply.code == 510) { // 510 Unrecognized command
+ LogPrintf("tor: Get SOCKS port command failed with unrecognized command (You probably should upgrade Tor)\n");
+ } else {
+ LogPrintf("tor: Get SOCKS port command failed; error code %d\n", reply.code);
+ }
+
+ CService resolved;
+ Assume(!resolved.IsValid());
+ if (!socks_location.empty()) {
+ resolved = LookupNumeric(socks_location, DEFAULT_TOR_SOCKS_PORT);
+ }
+ if (!resolved.IsValid()) {
+ // Fallback to old behaviour
+ resolved = LookupNumeric("127.0.0.1", DEFAULT_TOR_SOCKS_PORT);
+ }
+
+ Assume(resolved.IsValid());
+ LogPrint(BCLog::TOR, "tor: Configuring onion proxy for %s\n", resolved.ToStringIPPort());
+ Proxy addrOnion = Proxy(resolved, true);
+ SetProxy(NET_ONION, addrOnion);
+
+ const auto onlynets = gArgs.GetArgs("-onlynet");
+
+ const bool onion_allowed_by_onlynet{
+ !gArgs.IsArgSet("-onlynet") ||
+ std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) {
+ return ParseNetwork(n) == NET_ONION;
+ })};
+
+ if (onion_allowed_by_onlynet) {
+ // If NET_ONION is reachable, then the below is a noop.
+ //
+ // If NET_ONION is not reachable, then none of -proxy or -onion was given.
+ // Since we are here, then -torcontrol and -torpassword were given.
+ SetReachable(NET_ONION, true);
+ }
+}
+
void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply)
{
if (reply.code == 250) {
@@ -381,25 +449,7 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply&
// Now that we know Tor is running setup the proxy for onion addresses
// if -onion isn't set to something else.
if (gArgs.GetArg("-onion", "") == "") {
- CService resolved(LookupNumeric("127.0.0.1", 9050));
- Proxy addrOnion = Proxy(resolved, true);
- SetProxy(NET_ONION, addrOnion);
-
- const auto onlynets = gArgs.GetArgs("-onlynet");
-
- const bool onion_allowed_by_onlynet{
- !gArgs.IsArgSet("-onlynet") ||
- std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) {
- return ParseNetwork(n) == NET_ONION;
- })};
-
- if (onion_allowed_by_onlynet) {
- // If NET_ONION is reachable, then the below is a noop.
- //
- // If NET_ONION is not reachable, then none of -proxy or -onion was given.
- // Since we are here, then -torcontrol and -torpassword were given.
- SetReachable(NET_ONION, true);
- }
+ _conn.Command("GETINFO net/listeners/socks", std::bind(&TorController::get_socks_cb, this, std::placeholders::_1, std::placeholders::_2));
}
// Finally - now create the service
diff --git a/src/torcontrol.h b/src/torcontrol.h
index 4ace3edcb1..81475aee74 100644
--- a/src/torcontrol.h
+++ b/src/torcontrol.h
@@ -140,6 +140,8 @@ private:
std::vector<uint8_t> clientNonce;
public:
+ /** Callback for GETINFO net/listeners/socks result */
+ void get_socks_cb(TorControlConnection& conn, const TorControlReply& reply);
/** Callback for ADD_ONION result */
void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply);
/** Callback for AUTHENTICATE result */
diff --git a/src/txdb.cpp b/src/txdb.cpp
index 9a39e90ccd..afcd1985f5 100644
--- a/src/txdb.cpp
+++ b/src/txdb.cpp
@@ -6,7 +6,6 @@
#include <txdb.h>
#include <chain.h>
-#include <node/ui_interface.h>
#include <pow.h>
#include <random.h>
#include <shutdown.h>
@@ -18,7 +17,6 @@
#include <stdint.h>
static constexpr uint8_t DB_COIN{'C'};
-static constexpr uint8_t DB_COINS{'c'};
static constexpr uint8_t DB_BLOCK_FILES{'f'};
static constexpr uint8_t DB_BLOCK_INDEX{'b'};
@@ -29,6 +27,7 @@ static constexpr uint8_t DB_REINDEX_FLAG{'R'};
static constexpr uint8_t DB_LAST_BLOCK{'l'};
// Keys used in previous version that might still be found in the DB:
+static constexpr uint8_t DB_COINS{'c'};
static constexpr uint8_t DB_TXINDEX_BLOCK{'T'};
// uint8_t DB_TXINDEX{'t'}
@@ -50,6 +49,15 @@ std::optional<bilingual_str> CheckLegacyTxindex(CBlockTreeDB& block_tree_db)
return std::nullopt;
}
+bool CCoinsViewDB::NeedsUpgrade()
+{
+ std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
+ // DB_COINS was deprecated in v0.15.0, commit
+ // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
+ cursor->Seek(std::make_pair(DB_COINS, uint256{}));
+ return cursor->Valid();
+}
+
namespace {
struct CoinEntry {
@@ -60,7 +68,7 @@ struct CoinEntry {
SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
};
-}
+} // namespace
CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe) :
m_db(std::make_unique<CDBWrapper>(ldb_path, nCacheSize, fMemory, fWipe, true)),
@@ -76,7 +84,7 @@ void CCoinsViewDB::ResizeCache(size_t new_cache_size)
// filesystem lock.
m_db.reset();
m_db = std::make_unique<CDBWrapper>(
- m_ldb_path, new_cache_size, m_is_memory, /*fWipe*/ false, /*obfuscate*/ true);
+ m_ldb_path, new_cache_size, m_is_memory, /*fWipe=*/false, /*obfuscate=*/true);
}
}
@@ -337,125 +345,3 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams,
return true;
}
-
-namespace {
-
-//! Legacy class to deserialize pre-pertxout database entries without reindex.
-class CCoins
-{
-public:
- //! whether transaction is a coinbase
- bool fCoinBase;
-
- //! unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped
- std::vector<CTxOut> vout;
-
- //! at which height this transaction was included in the active block chain
- int nHeight;
-
- //! empty constructor
- CCoins() : fCoinBase(false), vout(0), nHeight(0) { }
-
- template<typename Stream>
- void Unserialize(Stream &s) {
- unsigned int nCode = 0;
- // version
- unsigned int nVersionDummy;
- ::Unserialize(s, VARINT(nVersionDummy));
- // header code
- ::Unserialize(s, VARINT(nCode));
- fCoinBase = nCode & 1;
- std::vector<bool> vAvail(2, false);
- vAvail[0] = (nCode & 2) != 0;
- vAvail[1] = (nCode & 4) != 0;
- unsigned int nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1);
- // spentness bitmask
- while (nMaskCode > 0) {
- unsigned char chAvail = 0;
- ::Unserialize(s, chAvail);
- for (unsigned int p = 0; p < 8; p++) {
- bool f = (chAvail & (1 << p)) != 0;
- vAvail.push_back(f);
- }
- if (chAvail != 0)
- nMaskCode--;
- }
- // txouts themself
- vout.assign(vAvail.size(), CTxOut());
- for (unsigned int i = 0; i < vAvail.size(); i++) {
- if (vAvail[i])
- ::Unserialize(s, Using<TxOutCompression>(vout[i]));
- }
- // coinbase height
- ::Unserialize(s, VARINT_MODE(nHeight, VarIntMode::NONNEGATIVE_SIGNED));
- }
-};
-
-}
-
-/** Upgrade the database from older formats.
- *
- * Currently implemented: from the per-tx utxo model (0.8..0.14.x) to per-txout.
- */
-bool CCoinsViewDB::Upgrade() {
- std::unique_ptr<CDBIterator> pcursor(m_db->NewIterator());
- pcursor->Seek(std::make_pair(DB_COINS, uint256()));
- if (!pcursor->Valid()) {
- return true;
- }
-
- int64_t count = 0;
- LogPrintf("Upgrading utxo-set database...\n");
- LogPrintf("[0%%]..."); /* Continued */
- uiInterface.ShowProgress(_("Upgrading UTXO database").translated, 0, true);
- size_t batch_size = 1 << 24;
- CDBBatch batch(*m_db);
- int reportDone = 0;
- std::pair<unsigned char, uint256> key;
- std::pair<unsigned char, uint256> prev_key = {DB_COINS, uint256()};
- while (pcursor->Valid()) {
- if (ShutdownRequested()) {
- break;
- }
- if (pcursor->GetKey(key) && key.first == DB_COINS) {
- if (count++ % 256 == 0) {
- uint32_t high = 0x100 * *key.second.begin() + *(key.second.begin() + 1);
- int percentageDone = (int)(high * 100.0 / 65536.0 + 0.5);
- uiInterface.ShowProgress(_("Upgrading UTXO database").translated, percentageDone, true);
- if (reportDone < percentageDone/10) {
- // report max. every 10% step
- LogPrintf("[%d%%]...", percentageDone); /* Continued */
- reportDone = percentageDone/10;
- }
- }
- CCoins old_coins;
- if (!pcursor->GetValue(old_coins)) {
- return error("%s: cannot parse CCoins record", __func__);
- }
- COutPoint outpoint(key.second, 0);
- for (size_t i = 0; i < old_coins.vout.size(); ++i) {
- if (!old_coins.vout[i].IsNull() && !old_coins.vout[i].scriptPubKey.IsUnspendable()) {
- Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase);
- outpoint.n = i;
- CoinEntry entry(&outpoint);
- batch.Write(entry, newcoin);
- }
- }
- batch.Erase(key);
- if (batch.SizeEstimate() > batch_size) {
- m_db->WriteBatch(batch);
- batch.Clear();
- m_db->CompactRange(prev_key, key);
- prev_key = key;
- }
- pcursor->Next();
- } else {
- break;
- }
- }
- m_db->WriteBatch(batch);
- m_db->CompactRange({DB_COINS, uint256()}, key);
- uiInterface.ShowProgress("", 100, false);
- LogPrintf("[%s].\n", ShutdownRequested() ? "CANCELLED" : "DONE");
- return !ShutdownRequested();
-}
diff --git a/src/txdb.h b/src/txdb.h
index e70f3cd1f2..faa543b412 100644
--- a/src/txdb.h
+++ b/src/txdb.h
@@ -65,8 +65,8 @@ public:
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
std::unique_ptr<CCoinsViewCursor> Cursor() const override;
- //! Attempt to update from an older database format. Returns whether an error occurred.
- bool Upgrade();
+ //! Whether an unsupported database format is used.
+ bool NeedsUpgrade();
size_t EstimateSize() const override;
//! Dynamically alter the underlying leveldb cache size.
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index fb5652d0a0..f73cc5da5f 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -54,16 +54,6 @@ struct update_ancestor_state
int64_t modifySigOpsCost;
};
-struct update_fee_delta
-{
- explicit update_fee_delta(int64_t _feeDelta) : feeDelta(_feeDelta) { }
-
- void operator() (CTxMemPoolEntry &e) { e.UpdateFeeDelta(feeDelta); }
-
-private:
- int64_t feeDelta;
-};
-
bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp)
{
AssertLockHeld(cs_main);
@@ -99,7 +89,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee,
nModFeesWithAncestors{nFee},
nSigOpCostWithAncestors{sigOpCost} {}
-void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
+void CTxMemPoolEntry::UpdateFeeDelta(CAmount newFeeDelta)
{
nModFeesWithDescendants += newFeeDelta - feeDelta;
nModFeesWithAncestors += newFeeDelta - feeDelta;
@@ -338,7 +328,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
staged_ancestors = it->GetMemPoolParentsConst();
}
- return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /* entry_count */ 1,
+ return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1,
setAncestors, staged_ancestors,
limitAncestorCount, limitAncestorSize,
limitDescendantCount, limitDescendantSize, errString);
@@ -496,7 +486,7 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
CAmount delta{0};
ApplyDelta(entry.GetTx().GetHash(), delta);
if (delta) {
- mapTx.modify(newit, update_fee_delta(delta));
+ mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); });
}
// Update cachedInnerUsage to include contained transaction's usage.
@@ -704,6 +694,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
void CTxMemPool::_clear()
{
+ vTxHashes.clear();
mapTx.clear();
mapNextTx.clear();
totalTxSize = 0;
@@ -931,7 +922,7 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
delta += nFeeDelta;
txiter it = mapTx.find(hash);
if (it != mapTx.end()) {
- mapTx.modify(it, update_fee_delta(delta));
+ mapTx.modify(it, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); });
// Now update all ancestors' modified fees with descendants
setEntries setAncestors;
uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
diff --git a/src/txmempool.h b/src/txmempool.h
index e7e5a3c402..f5d5abc62e 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -101,7 +101,7 @@ private:
const unsigned int entryHeight; //!< Chain height when entering the mempool
const bool spendsCoinbase; //!< keep track of transactions that spend a coinbase
const int64_t sigOpCost; //!< Total sigop cost
- int64_t feeDelta{0}; //!< Used for determining the priority of the transaction for mining in a block
+ CAmount feeDelta{0}; //!< Used for determining the priority of the transaction for mining in a block
LockPoints lockPoints; //!< Track the height and time at which tx was final
// Information about descendants of this transaction that are in the
@@ -131,7 +131,7 @@ public:
std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; }
unsigned int GetHeight() const { return entryHeight; }
int64_t GetSigOpCost() const { return sigOpCost; }
- int64_t GetModifiedFee() const { return nFee + feeDelta; }
+ CAmount GetModifiedFee() const { return nFee + feeDelta; }
size_t DynamicMemoryUsage() const { return nUsageSize; }
const LockPoints& GetLockPoints() const { return lockPoints; }
@@ -140,8 +140,8 @@ public:
// Adjusts the ancestor state
void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps);
// Updates the fee delta used for mining priority score, and the
- // modified fees with descendants.
- void UpdateFeeDelta(int64_t feeDelta);
+ // modified fees with descendants/ancestors.
+ void UpdateFeeDelta(CAmount newFeeDelta);
// Update the LockPoints after a reorg
void UpdateLockPoints(const LockPoints& lp);
diff --git a/src/util/check.cpp b/src/util/check.cpp
new file mode 100644
index 0000000000..2a9f885560
--- /dev/null
+++ b/src/util/check.cpp
@@ -0,0 +1,14 @@
+// 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.
+
+#include <util/check.h>
+
+#include <tinyformat.h>
+
+void assertion_fail(const char* file, int line, const char* func, const char* assertion)
+{
+ auto str = strprintf("%s:%s %s: Assertion `%s' failed.\n", file, line, func, assertion);
+ fwrite(str.data(), 1, str.size(), stderr);
+ std::abort();
+}
diff --git a/src/util/check.h b/src/util/check.h
index a443c13cf2..4ee65c8d34 100644
--- a/src/util/check.h
+++ b/src/util/check.h
@@ -47,14 +47,26 @@ class NonFatalCheckError : public std::runtime_error
#endif
/** Helper for Assert() */
-template <typename T>
-T get_pure_r_value(T&& val)
+void assertion_fail(const char* file, int line, const char* func, const char* assertion);
+
+/** Helper for Assert()/Assume() */
+template <bool IS_ASSERT, typename T>
+T&& inline_assertion_check(T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion)
{
+ if constexpr (IS_ASSERT
+#ifdef ABORT_ON_FAILED_ASSUME
+ || true
+#endif
+ ) {
+ if (!val) {
+ assertion_fail(file, line, func, assertion);
+ }
+ }
return std::forward<T>(val);
}
/** Identity function. Abort if the value compares equal to zero */
-#define Assert(val) ([&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); assert(#val && check); return std::forward<decltype(get_pure_r_value(val))>(check); }())
+#define Assert(val) inline_assertion_check<true>(val, __FILE__, __LINE__, __func__, #val)
/**
* Assume is the identity function.
@@ -66,10 +78,6 @@ T get_pure_r_value(T&& val)
* - For non-fatal errors in interactive sessions (e.g. RPC or command line
* interfaces), CHECK_NONFATAL() might be more appropriate.
*/
-#ifdef ABORT_ON_FAILED_ASSUME
-#define Assume(val) Assert(val)
-#else
-#define Assume(val) ([&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); return std::forward<decltype(get_pure_r_value(val))>(check); }())
-#endif
+#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
#endif // BITCOIN_UTIL_CHECK_H
diff --git a/src/util/syscall_sandbox.cpp b/src/util/syscall_sandbox.cpp
index f2a9cf664d..a69f815ce4 100644
--- a/src/util/syscall_sandbox.cpp
+++ b/src/util/syscall_sandbox.cpp
@@ -821,7 +821,6 @@ bool SetupSyscallSandbox(bool log_syscall_violation_before_terminating)
return false;
}
}
- SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION);
return true;
}
diff --git a/src/util/syscall_sandbox.h b/src/util/syscall_sandbox.h
index f7a1cbdb55..dc02ce29e9 100644
--- a/src/util/syscall_sandbox.h
+++ b/src/util/syscall_sandbox.h
@@ -45,9 +45,6 @@ void SetSyscallSandboxPolicy(SyscallSandboxPolicy syscall_policy);
#if defined(USE_SYSCALL_SANDBOX)
//! Setup and enable the experimental syscall sandbox for the running process.
-//!
-//! SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION) is called as part of
-//! SetupSyscallSandbox(...).
[[nodiscard]] bool SetupSyscallSandbox(bool log_syscall_violation_before_terminating);
//! Invoke a disallowed syscall. Use for testing purposes.
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 8e45453d31..a7e66defcd 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -6,7 +6,16 @@
#include <util/system.h>
#ifdef ENABLE_EXTERNAL_SIGNER
+#if defined(__GNUC__)
+// Boost 1.78 requires the following workaround.
+// See: https://github.com/boostorg/process/issues/235
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnarrowing"
+#endif
#include <boost/process.hpp>
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
#endif // ENABLE_EXTERNAL_SIGNER
#include <chainparamsbase.h>
diff --git a/src/util/threadnames.cpp b/src/util/threadnames.cpp
index 764fffabd7..a5a86d2598 100644
--- a/src/util/threadnames.cpp
+++ b/src/util/threadnames.cpp
@@ -6,7 +6,9 @@
#include <config/bitcoin-config.h>
#endif
+#include <string>
#include <thread>
+#include <utility>
#if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__))
#include <pthread.h>
@@ -16,7 +18,7 @@
#include <util/threadnames.h>
#ifdef HAVE_SYS_PRCTL_H
-#include <sys/prctl.h> // For prctl, PR_SET_NAME, PR_GET_NAME
+#include <sys/prctl.h>
#endif
//! Set the thread's name at the process level. Does not affect the
diff --git a/src/validation.cpp b/src/validation.cpp
index 8406f65401..f4b316f67a 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -65,21 +65,22 @@
using node::BLOCKFILE_CHUNK_SIZE;
using node::BlockManager;
using node::BlockMap;
+using node::CBlockIndexHeightOnlyComparator;
using node::CBlockIndexWorkComparator;
using node::CCoinsStats;
using node::CoinStatsHashType;
+using node::fHavePruned;
+using node::fImporting;
+using node::fPruneMode;
+using node::fReindex;
using node::GetUTXOStats;
+using node::nPruneTarget;
using node::OpenBlockFile;
using node::ReadBlockFromDisk;
using node::SnapshotMetadata;
using node::UNDOFILE_CHUNK_SIZE;
using node::UndoReadFromDisk;
using node::UnlinkPrunedFiles;
-using node::fHavePruned;
-using node::fImporting;
-using node::fPruneMode;
-using node::fReindex;
-using node::nPruneTarget;
#define MICRO 0.000001
#define MILLI 0.001
@@ -107,24 +108,6 @@ const std::vector<std::string> CHECKLEVEL_DOC {
"each level includes the checks of the previous levels",
};
-bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIndex *pb) const {
- // First sort by most total work, ...
- if (pa->nChainWork > pb->nChainWork) return false;
- if (pa->nChainWork < pb->nChainWork) return true;
-
- // ... then by earliest time received, ...
- if (pa->nSequenceId < pb->nSequenceId) return false;
- if (pa->nSequenceId > pb->nSequenceId) return true;
-
- // Use pointer address as tie breaker (should only happen with blocks
- // loaded from disk, as those all have id 0).
- if (pa < pb) return false;
- if (pa > pb) return true;
-
- // Identical blocks.
- return false;
-}
-
/**
* Mutex to guard access to validation specific variables, such as reading
* or changing the chainstate.
@@ -152,14 +135,14 @@ arith_uint256 nMinimumChainWork;
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
-CBlockIndex* CChainState::FindForkInGlobalIndex(const CBlockLocator& locator) const
+const CBlockIndex* CChainState::FindForkInGlobalIndex(const CBlockLocator& locator) const
{
AssertLockHeld(cs_main);
// Find the latest block common to locator and chain - we expect that
// locator.vHave is sorted descending by height.
for (const uint256& hash : locator.vHave) {
- CBlockIndex* pindex{m_blockman.LookupBlockIndex(hash)};
+ const CBlockIndex* pindex{m_blockman.LookupBlockIndex(hash)};
if (pindex) {
if (m_chain.Contains(pindex)) {
return pindex;
@@ -272,7 +255,7 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip,
}
// Returns the script flags which should be checked for a given block
-static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& chainparams);
+static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Consensus::Params& chainparams);
static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, size_t limit, std::chrono::seconds age)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs)
@@ -477,6 +460,10 @@ public:
* partially submitted.
*/
const bool m_package_submission;
+ /** When true, use package feerates instead of individual transaction feerates for fee-based
+ * policies such as mempool min fee and min relay fee.
+ */
+ const bool m_package_feerates;
/** Parameters for single transaction mempool validation. */
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time,
@@ -489,6 +476,7 @@ public:
/* m_test_accept */ test_accept,
/* m_allow_bip125_replacement */ true,
/* m_package_submission */ false,
+ /* m_package_feerates */ false,
};
}
@@ -502,6 +490,7 @@ public:
/* m_test_accept */ true,
/* m_allow_bip125_replacement */ false,
/* m_package_submission */ false, // not submitting to mempool
+ /* m_package_feerates */ false,
};
}
@@ -515,6 +504,20 @@ public:
/* m_test_accept */ false,
/* m_allow_bip125_replacement */ false,
/* m_package_submission */ true,
+ /* m_package_feerates */ true,
+ };
+ }
+
+ /** Parameters for a single transaction within a package. */
+ static ATMPArgs SingleInPackageAccept(const ATMPArgs& package_args) {
+ return ATMPArgs{/* m_chainparams */ package_args.m_chainparams,
+ /* m_accept_time */ package_args.m_accept_time,
+ /* m_bypass_limits */ false,
+ /* m_coins_to_uncache */ package_args.m_coins_to_uncache,
+ /* m_test_accept */ package_args.m_test_accept,
+ /* m_allow_bip125_replacement */ true,
+ /* m_package_submission */ false,
+ /* m_package_feerates */ false, // only 1 transaction
};
}
@@ -527,14 +530,16 @@ public:
std::vector<COutPoint>& coins_to_uncache,
bool test_accept,
bool allow_bip125_replacement,
- bool package_submission)
+ bool package_submission,
+ bool package_feerates)
: m_chainparams{chainparams},
m_accept_time{accept_time},
m_bypass_limits{bypass_limits},
m_coins_to_uncache{coins_to_uncache},
m_test_accept{test_accept},
m_allow_bip125_replacement{allow_bip125_replacement},
- m_package_submission{package_submission}
+ m_package_submission{package_submission},
+ m_package_feerates{package_feerates}
{
}
};
@@ -836,9 +841,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
strprintf("%d", nSigOpsCost));
- // No transactions are allowed below minRelayTxFee except from disconnected
- // blocks
- if (!bypass_limits && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
+ // No individual transactions are allowed below minRelayTxFee and mempool min fee except from
+ // disconnected blocks and transactions in a package. Package transactions will be checked using
+ // package feerate later.
+ if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);
// Calculate in-mempool ancestors, up to a limit.
@@ -1030,7 +1036,7 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws)
// There is a similar check in CreateNewBlock() to prevent creating
// invalid blocks (using TestBlockValidity), however allowing such
// transactions into the mempool can be exploited as a DoS attack.
- unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus());
+ unsigned int currentBlockScriptVerifyFlags{GetBlockScriptFlags(*m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus())};
if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags,
ws.m_precomputed_txdata, m_active_chainstate.CoinsTip())) {
LogPrintf("BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s\n", hash.ToString(), state.ToString());
@@ -1222,12 +1228,27 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
m_viewmempool.PackageAddTransaction(ws.m_ptx);
}
+ // Transactions must meet two minimum feerates: the mempool minimum fee and min relay fee.
+ // For transactions consisting of exactly one child and its parents, it suffices to use the
+ // package feerate (total modified fees / total virtual size) to check this requirement.
+ const auto m_total_vsize = std::accumulate(workspaces.cbegin(), workspaces.cend(), int64_t{0},
+ [](int64_t sum, auto& ws) { return sum + ws.m_vsize; });
+ const auto m_total_modified_fees = std::accumulate(workspaces.cbegin(), workspaces.cend(), CAmount{0},
+ [](CAmount sum, auto& ws) { return sum + ws.m_modified_fees; });
+ const CFeeRate package_feerate(m_total_modified_fees, m_total_vsize);
+ TxValidationState placeholder_state;
+ if (args.m_package_feerates &&
+ !CheckFeeRate(m_total_vsize, m_total_modified_fees, placeholder_state)) {
+ package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-fee-too-low");
+ return PackageMempoolAcceptResult(package_state, package_feerate, {});
+ }
+
// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
// because it's unnecessary. Also, CPFP carve out can increase the limit for individual
// transactions, but this exemption is not extended to packages in CheckPackageLimits().
std::string err_string;
if (txns.size() > 1 && !PackageMempoolChecks(txns, package_state)) {
- return PackageMempoolAcceptResult(package_state, std::move(results));
+ return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
}
for (Workspace& ws : workspaces) {
@@ -1235,7 +1256,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
// Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished.
package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
- return PackageMempoolAcceptResult(package_state, std::move(results));
+ return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
}
if (args.m_test_accept) {
// When test_accept=true, transactions that pass PolicyScriptChecks are valid because there are
@@ -1246,14 +1267,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
}
}
- if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, std::move(results));
+ if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
if (!SubmitPackage(args, workspaces, package_state, results)) {
// PackageValidationState filled in by SubmitPackage().
- return PackageMempoolAcceptResult(package_state, std::move(results));
+ return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
}
- return PackageMempoolAcceptResult(package_state, std::move(results));
+ return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results));
}
PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args)
@@ -1320,6 +1341,8 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
// transactions that are already in the mempool, and only call AcceptMultipleTransactions() with
// the new transactions. This ensures we don't double-count transaction counts and sizes when
// checking ancestor/descendant limits, or double-count transaction fees for fee-related policy.
+ ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
+ bool quit_early{false};
std::vector<CTransactionRef> txns_new;
for (const auto& tx : package) {
const auto& wtxid = tx->GetWitnessHash();
@@ -1346,18 +1369,43 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
results.emplace(wtxid, MempoolAcceptResult::MempoolTxDifferentWitness(iter.value()->GetTx().GetWitnessHash()));
} else {
// Transaction does not already exist in the mempool.
- txns_new.push_back(tx);
+ // Try submitting the transaction on its own.
+ const auto single_res = AcceptSingleTransaction(tx, single_args);
+ if (single_res.m_result_type == MempoolAcceptResult::ResultType::VALID) {
+ // The transaction succeeded on its own and is now in the mempool. Don't include it
+ // in package validation, because its fees should only be "used" once.
+ assert(m_pool.exists(GenTxid::Wtxid(wtxid)));
+ results.emplace(wtxid, single_res);
+ } else if (single_res.m_state.GetResult() != TxValidationResult::TX_MEMPOOL_POLICY &&
+ single_res.m_state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) {
+ // Package validation policy only differs from individual policy in its evaluation
+ // of feerate. For example, if a transaction fails here due to violation of a
+ // consensus rule, the result will not change when it is submitted as part of a
+ // package. To minimize the amount of repeated work, unless the transaction fails
+ // due to feerate or missing inputs (its parent is a previous transaction in the
+ // package that failed due to feerate), don't run package validation. Note that this
+ // decision might not make sense if different types of packages are allowed in the
+ // future. Continue individually validating the rest of the transactions, because
+ // some of them may still be valid.
+ quit_early = true;
+ } else {
+ txns_new.push_back(tx);
+ }
}
}
// Nothing to do if the entire package has already been submitted.
- if (txns_new.empty()) return PackageMempoolAcceptResult(package_state, std::move(results));
+ if (quit_early || txns_new.empty()) {
+ // No package feerate when no package validation was done.
+ return PackageMempoolAcceptResult(package_state, std::move(results));
+ }
// Validate the (deduplicated) transactions as a package.
auto submission_result = AcceptMultipleTransactions(txns_new, args);
// Include already-in-mempool transaction results in the final result.
for (const auto& [wtxid, mempoolaccept_res] : results) {
submission_result.m_tx_results.emplace(wtxid, mempoolaccept_res);
}
+ if (submission_result.m_state.IsValid()) assert(submission_result.m_package_feerate.has_value());
return submission_result;
}
@@ -1871,45 +1919,39 @@ public:
static ThresholdConditionCache warningcache[VERSIONBITS_NUM_BITS] GUARDED_BY(cs_main);
-static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& consensusparams)
+static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Consensus::Params& consensusparams)
{
- unsigned int flags = SCRIPT_VERIFY_NONE;
-
// BIP16 didn't become active until Apr 1 2012 (on mainnet, and
// retroactively applied to testnet)
// However, only one historical block violated the P2SH rules (on both
- // mainnet and testnet), so for simplicity, always leave P2SH
- // on except for the one violating block.
- if (consensusparams.BIP16Exception.IsNull() || // no bip16 exception on this chain
- pindex->phashBlock == nullptr || // this is a new candidate block, eg from TestBlockValidity()
- *pindex->phashBlock != consensusparams.BIP16Exception) // this block isn't the historical exception
- {
- // Enforce WITNESS rules whenever P2SH is in effect
- flags |= SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS;
+ // mainnet and testnet).
+ // Similarly, only one historical block violated the TAPROOT rules on
+ // mainnet.
+ // For simplicity, always leave P2SH+WITNESS+TAPROOT on except for the two
+ // violating blocks.
+ uint32_t flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT};
+ const auto it{consensusparams.script_flag_exceptions.find(*Assert(block_index.phashBlock))};
+ if (it != consensusparams.script_flag_exceptions.end()) {
+ flags = it->second;
}
// Enforce the DERSIG (BIP66) rule
- if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_DERSIG)) {
+ if (DeploymentActiveAt(block_index, consensusparams, Consensus::DEPLOYMENT_DERSIG)) {
flags |= SCRIPT_VERIFY_DERSIG;
}
// Enforce CHECKLOCKTIMEVERIFY (BIP65)
- if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_CLTV)) {
+ if (DeploymentActiveAt(block_index, consensusparams, Consensus::DEPLOYMENT_CLTV)) {
flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
}
// Enforce CHECKSEQUENCEVERIFY (BIP112)
- if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_CSV)) {
+ if (DeploymentActiveAt(block_index, consensusparams, Consensus::DEPLOYMENT_CSV)) {
flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
}
- // Enforce Taproot (BIP340-BIP342)
- if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_TAPROOT)) {
- flags |= SCRIPT_VERIFY_TAPROOT;
- }
-
// Enforce BIP147 NULLDUMMY (activated simultaneously with segwit)
- if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_SEGWIT)) {
+ if (DeploymentActiveAt(block_index, consensusparams, Consensus::DEPLOYMENT_SEGWIT)) {
flags |= SCRIPT_VERIFY_NULLDUMMY;
}
@@ -1917,11 +1959,11 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens
}
-
static int64_t nTimeCheck = 0;
static int64_t nTimeForks = 0;
-static int64_t nTimeVerify = 0;
static int64_t nTimeConnect = 0;
+static int64_t nTimeVerify = 0;
+static int64_t nTimeUndo = 0;
static int64_t nTimeIndex = 0;
static int64_t nTimeTotal = 0;
static int64_t nBlocksTotal = 0;
@@ -2105,7 +2147,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
}
// Get the script flags for this block
- unsigned int flags = GetBlockScriptFlags(pindex, m_params.GetConsensus());
+ unsigned int flags{GetBlockScriptFlags(*pindex, m_params.GetConsensus())};
int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1;
LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime2 - nTime1), nTimeForks * MICRO, nTimeForks * MILLI / nBlocksTotal);
@@ -2215,6 +2257,9 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
return false;
}
+ int64_t nTime5 = GetTimeMicros(); nTimeUndo += nTime5 - nTime4;
+ LogPrint(BCLog::BENCH, " - Write undo data: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5 - nTime4), nTimeUndo * MICRO, nTimeUndo * MILLI / nBlocksTotal);
+
if (!pindex->IsValid(BLOCK_VALID_SCRIPTS)) {
pindex->RaiseValidity(BLOCK_VALID_SCRIPTS);
m_blockman.m_dirty_blockindex.insert(pindex);
@@ -2224,8 +2269,8 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// add this block to the view's block chain
view.SetBestBlock(pindex->GetBlockHash());
- int64_t nTime5 = GetTimeMicros(); nTimeIndex += nTime5 - nTime4;
- LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5 - nTime4), nTimeIndex * MICRO, nTimeIndex * MILLI / nBlocksTotal);
+ int64_t nTime6 = GetTimeMicros(); nTimeIndex += nTime6 - nTime5;
+ LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime6 - nTime5), nTimeIndex * MICRO, nTimeIndex * MILLI / nBlocksTotal);
TRACE6(validation, block_connected,
block_hash.data(),
@@ -2564,7 +2609,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr
return true;
}
-static int64_t nTimeReadFromDisk = 0;
+static int64_t nTimeReadFromDiskTotal = 0;
static int64_t nTimeConnectTotal = 0;
static int64_t nTimeFlush = 0;
static int64_t nTimeChainState = 0;
@@ -2632,13 +2677,14 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew
}
pthisBlock = pblockNew;
} else {
+ LogPrint(BCLog::BENCH, " - Using cached block\n");
pthisBlock = pblock;
}
const CBlock& blockConnecting = *pthisBlock;
// Apply the block atomically to the chain state.
- int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1;
+ int64_t nTime2 = GetTimeMicros(); nTimeReadFromDiskTotal += nTime2 - nTime1;
int64_t nTime3;
- LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO);
+ LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDiskTotal * MICRO, nTimeReadFromDiskTotal * MILLI / nBlocksTotal);
{
CCoinsViewCache view(&CoinsTip());
bool rv = ConnectBlock(blockConnecting, state, pindexNew, view);
@@ -3381,7 +3427,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
// Don't accept any forks from the main chain prior to last checkpoint.
// GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our
// BlockIndex().
- CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(params.Checkpoints());
+ const CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(params.Checkpoints());
if (pcheckpoint && nHeight < pcheckpoint->nHeight) {
LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight);
return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-prior-to-checkpoint");
@@ -4091,8 +4137,74 @@ bool ChainstateManager::LoadBlockIndex()
// Load block index from databases
bool needs_init = fReindex;
if (!fReindex) {
- bool ret = m_blockman.LoadBlockIndexDB(*this);
+ bool ret = m_blockman.LoadBlockIndexDB();
if (!ret) return false;
+
+ std::vector<CBlockIndex*> vSortedByHeight{m_blockman.GetAllBlockIndices()};
+ std::sort(vSortedByHeight.begin(), vSortedByHeight.end(),
+ CBlockIndexHeightOnlyComparator());
+
+ // Find start of assumed-valid region.
+ int first_assumed_valid_height = std::numeric_limits<int>::max();
+
+ for (const CBlockIndex* block : vSortedByHeight) {
+ if (block->IsAssumedValid()) {
+ auto chainstates = GetAll();
+
+ // If we encounter an assumed-valid block index entry, ensure that we have
+ // one chainstate that tolerates assumed-valid entries and another that does
+ // not (i.e. the background validation chainstate), since assumed-valid
+ // entries should always be pending validation by a fully-validated chainstate.
+ auto any_chain = [&](auto fnc) { return std::any_of(chainstates.cbegin(), chainstates.cend(), fnc); };
+ assert(any_chain([](auto chainstate) { return chainstate->reliesOnAssumedValid(); }));
+ assert(any_chain([](auto chainstate) { return !chainstate->reliesOnAssumedValid(); }));
+
+ first_assumed_valid_height = block->nHeight;
+ break;
+ }
+ }
+
+ for (CBlockIndex* pindex : vSortedByHeight) {
+ if (ShutdownRequested()) return false;
+ if (pindex->IsAssumedValid() ||
+ (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) &&
+ (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) {
+
+ // Fill each chainstate's block candidate set. Only add assumed-valid
+ // blocks to the tip candidate set if the chainstate is allowed to rely on
+ // assumed-valid blocks.
+ //
+ // If all setBlockIndexCandidates contained the assumed-valid blocks, the
+ // background chainstate's ActivateBestChain() call would add assumed-valid
+ // blocks to the chain (based on how FindMostWorkChain() works). Obviously
+ // we don't want this since the purpose of the background validation chain
+ // is to validate assued-valid blocks.
+ //
+ // Note: This is considering all blocks whose height is greater or equal to
+ // the first assumed-valid block to be assumed-valid blocks, and excluding
+ // them from the background chainstate's setBlockIndexCandidates set. This
+ // does mean that some blocks which are not technically assumed-valid
+ // (later blocks on a fork beginning before the first assumed-valid block)
+ // might not get added to the background chainstate, but this is ok,
+ // because they will still be attached to the active chainstate if they
+ // actually contain more work.
+ //
+ // Instead of this height-based approach, an earlier attempt was made at
+ // detecting "holistically" whether the block index under consideration
+ // relied on an assumed-valid ancestor, but this proved to be too slow to
+ // be practical.
+ for (CChainState* chainstate : GetAll()) {
+ if (chainstate->reliesOnAssumedValid() ||
+ pindex->nHeight < first_assumed_valid_height) {
+ chainstate->setBlockIndexCandidates.insert(pindex);
+ }
+ }
+ }
+ if (pindex->nStatus & BLOCK_FAILED_MASK && (!m_best_invalid || pindex->nChainWork > m_best_invalid->nChainWork)) {
+ m_best_invalid = pindex;
+ }
+ }
+
needs_init = m_blockman.m_block_index.empty();
}
@@ -4194,7 +4306,7 @@ void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp)
}
// process in case the block isn't known yet
- CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash);
+ const CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash);
if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) {
BlockValidationState state;
if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr)) {
@@ -4790,7 +4902,7 @@ bool ChainstateManager::ActivateSnapshot(
auto snapshot_chainstate = WITH_LOCK(::cs_main,
return std::make_unique<CChainState>(
- /* mempool */ nullptr, m_blockman, *this, base_blockhash));
+ /*mempool=*/nullptr, m_blockman, *this, base_blockhash));
{
LOCK(::cs_main);
diff --git a/src/validation.h b/src/validation.h
index 965ed4225e..53ea2d4aea 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -234,11 +234,19 @@ struct PackageMempoolAcceptResult
* was a package-wide error (see result in m_state), m_tx_results will be empty.
*/
std::map<const uint256, const MempoolAcceptResult> m_tx_results;
+ /** Package feerate, defined as the aggregated modified fees divided by the total virtual size
+ * of all transactions in the package. May be unavailable if some inputs were not available or
+ * a transaction failure caused validation to terminate early. */
+ std::optional<CFeeRate> m_package_feerate;
explicit PackageMempoolAcceptResult(PackageValidationState state,
std::map<const uint256, const MempoolAcceptResult>&& results)
: m_state{state}, m_tx_results(std::move(results)) {}
+ explicit PackageMempoolAcceptResult(PackageValidationState state, CFeeRate feerate,
+ std::map<const uint256, const MempoolAcceptResult>&& results)
+ : m_state{state}, m_tx_results(std::move(results)), m_package_feerate{feerate} {}
+
/** Constructor to create a PackageMempoolAcceptResult from a single MempoolAcceptResult */
explicit PackageMempoolAcceptResult(const uint256& wtxid, const MempoolAcceptResult& result)
: m_tx_results{ {wtxid, result} } {}
@@ -686,7 +694,7 @@ public:
bool IsInitialBlockDownload() const;
/** Find the last common block of this chain and a locator. */
- CBlockIndex* FindForkInGlobalIndex(const CBlockLocator& locator) const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ const CBlockIndex* FindForkInGlobalIndex(const CBlockLocator& locator) const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Make various assertions about the state of the block index.
@@ -829,7 +837,7 @@ private:
bool m_snapshot_validated{false};
CBlockIndex* m_best_invalid;
- friend bool node::BlockManager::LoadBlockIndex(const Consensus::Params&, ChainstateManager&);
+ friend bool node::BlockManager::LoadBlockIndex(const Consensus::Params&);
//! Internal helper for ActivateSnapshot().
[[nodiscard]] bool PopulateAndValidateSnapshot(
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index 49f0abf9e7..0d0456af4b 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -60,12 +60,12 @@ bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
* erases the weak pointer from the g_dbenvs map.
* @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map.
*/
-std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory)
+std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory)
{
LOCK(cs_db);
auto inserted = g_dbenvs.emplace(fs::PathToString(env_directory), std::weak_ptr<BerkeleyEnvironment>());
if (inserted.second) {
- auto env = std::make_shared<BerkeleyEnvironment>(env_directory);
+ auto env = std::make_shared<BerkeleyEnvironment>(env_directory, use_shared_memory);
inserted.first->second = env;
return env;
}
@@ -113,7 +113,7 @@ void BerkeleyEnvironment::Reset()
fMockDb = false;
}
-BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(fs::PathToString(dir_path))
+BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path, bool use_shared_memory) : strPath(fs::PathToString(dir_path)), m_use_shared_memory(use_shared_memory)
{
Reset();
}
@@ -145,8 +145,9 @@ bool BerkeleyEnvironment::Open(bilingual_str& err)
LogPrintf("BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n", fs::PathToString(pathLogDir), fs::PathToString(pathErrorFile));
unsigned int nEnvFlags = 0;
- if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB))
+ if (!m_use_shared_memory) {
nEnvFlags |= DB_PRIVATE;
+ }
dbenv->set_lg_dir(fs::PathToString(pathLogDir).c_str());
dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
@@ -188,7 +189,7 @@ bool BerkeleyEnvironment::Open(bilingual_str& err)
}
//! Construct an in-memory mock Berkeley environment for testing
-BerkeleyEnvironment::BerkeleyEnvironment()
+BerkeleyEnvironment::BerkeleyEnvironment() : m_use_shared_memory(false)
{
Reset();
@@ -377,7 +378,7 @@ void BerkeleyBatch::Flush()
nMinutes = 1;
if (env) { // env is nullptr for dummy databases (i.e. in tests). Don't actually flush if env is nullptr so we don't segfault
- env->dbenv->txn_checkpoint(nMinutes ? gArgs.GetIntArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
+ env->dbenv->txn_checkpoint(nMinutes ? m_database.m_max_log_mb * 1024 : 0, nMinutes, 0);
}
}
@@ -831,13 +832,13 @@ std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, con
{
LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor
std::string data_filename = fs::PathToString(data_file.filename());
- std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path());
+ std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path(), options.use_shared_memory);
if (env->m_databases.count(data_filename)) {
error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", fs::PathToString(env->Directory() / data_filename)));
status = DatabaseStatus::FAILED_ALREADY_LOADED;
return nullptr;
}
- db = std::make_unique<BerkeleyDatabase>(std::move(env), std::move(data_filename));
+ db = std::make_unique<BerkeleyDatabase>(std::move(env), std::move(data_filename), options);
}
if (options.verify && !db->Verify(error)) {
diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h
index b924890d81..fd6c76183e 100644
--- a/src/wallet/bdb.h
+++ b/src/wallet/bdb.h
@@ -32,8 +32,6 @@
struct bilingual_str;
namespace wallet {
-static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
-static const bool DEFAULT_WALLET_PRIVDB = true;
struct WalletDatabaseFileId {
u_int8_t value[DB_FILE_ID_LEN];
@@ -56,8 +54,9 @@ public:
std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
std::condition_variable_any m_db_in_use;
+ bool m_use_shared_memory;
- explicit BerkeleyEnvironment(const fs::path& env_directory);
+ explicit BerkeleyEnvironment(const fs::path& env_directory, bool use_shared_memory);
BerkeleyEnvironment();
~BerkeleyEnvironment();
void Reset();
@@ -85,7 +84,7 @@ public:
};
/** Get BerkeleyEnvironment given a directory path. */
-std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory);
+std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory);
class BerkeleyBatch;
@@ -98,8 +97,8 @@ public:
BerkeleyDatabase() = delete;
/** Create DB handle to real database */
- BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) :
- WalletDatabase(), env(std::move(env)), strFile(std::move(filename))
+ BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename, const DatabaseOptions& options) :
+ WalletDatabase(), env(std::move(env)), strFile(std::move(filename)), m_max_log_mb(options.max_log_mb)
{
auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this));
assert(inserted.second);
@@ -160,6 +159,7 @@ public:
std::unique_ptr<Db> m_db;
std::string strFile;
+ int64_t m_max_log_mb;
/** Make a BerkeleyBatch connected to this database */
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override;
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 513572da45..433759e086 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -50,8 +50,8 @@ struct {
* The Branch and Bound algorithm is described in detail in Murch's Master Thesis:
* https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
*
- * @param const std::vector<CInputCoin>& utxo_pool The set of UTXOs that we are choosing from.
- * These UTXOs will be sorted in descending order by effective value and the CInputCoins'
+ * @param const std::vector<OutputGroup>& utxo_pool The set of UTXO groups that we are choosing from.
+ * These UTXO groups will be sorted in descending order by effective value and the OutputGroups'
* values are their effective values.
* @param const CAmount& selection_target This is the value that we want to select. It is the lower
* bound of the range.
@@ -66,14 +66,13 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
{
SelectionResult result(selection_target);
CAmount curr_value = 0;
-
- std::vector<bool> curr_selection; // select the utxo at this index
- curr_selection.reserve(utxo_pool.size());
+ std::vector<size_t> curr_selection; // selected utxo indexes
// Calculate curr_available_value
CAmount curr_available_value = 0;
for (const OutputGroup& utxo : utxo_pool) {
- // Assert that this utxo is not negative. It should never be negative, effective value calculation should have removed it
+ // Assert that this utxo is not negative. It should never be negative,
+ // effective value calculation should have removed it
assert(utxo.GetSelectionAmount() > 0);
curr_available_value += utxo.GetSelectionAmount();
}
@@ -85,15 +84,15 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
CAmount curr_waste = 0;
- std::vector<bool> best_selection;
+ std::vector<size_t> best_selection;
CAmount best_waste = MAX_MONEY;
// Depth First search loop for choosing the UTXOs
- for (size_t i = 0; i < TOTAL_TRIES; ++i) {
+ for (size_t curr_try = 0, utxo_pool_index = 0; curr_try < TOTAL_TRIES; ++curr_try, ++utxo_pool_index) {
// Conditions for starting a backtrack
bool backtrack = false;
- if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
- curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch
+ if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
+ curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch
(curr_waste > best_waste && (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0)) { // Don't select things which we know will be more wasteful if the waste is increasing
backtrack = true;
} else if (curr_value >= selection_target) { // Selected value is within range
@@ -104,7 +103,6 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
// explore any more UTXOs to avoid burning money like that.
if (curr_waste <= best_waste) {
best_selection = curr_selection;
- best_selection.resize(utxo_pool.size());
best_waste = curr_waste;
if (best_waste == 0) {
break;
@@ -114,38 +112,38 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
backtrack = true;
}
- // Backtracking, moving backwards
- if (backtrack) {
- // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
- while (!curr_selection.empty() && !curr_selection.back()) {
- curr_selection.pop_back();
- curr_available_value += utxo_pool.at(curr_selection.size()).GetSelectionAmount();
- }
-
+ if (backtrack) { // Backtracking, moving backwards
if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched
break;
}
+ // Add omitted UTXOs back to lookahead before traversing the omission branch of last included UTXO.
+ for (--utxo_pool_index; utxo_pool_index > curr_selection.back(); --utxo_pool_index) {
+ curr_available_value += utxo_pool.at(utxo_pool_index).GetSelectionAmount();
+ }
+
// Output was included on previous iterations, try excluding now.
- curr_selection.back() = false;
- OutputGroup& utxo = utxo_pool.at(curr_selection.size() - 1);
+ assert(utxo_pool_index == curr_selection.back());
+ OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
curr_value -= utxo.GetSelectionAmount();
curr_waste -= utxo.fee - utxo.long_term_fee;
+ curr_selection.pop_back();
} else { // Moving forwards, continuing down this branch
- OutputGroup& utxo = utxo_pool.at(curr_selection.size());
+ OutputGroup& utxo = utxo_pool.at(utxo_pool_index);
// Remove this utxo from the curr_available_value utxo amount
curr_available_value -= utxo.GetSelectionAmount();
- // Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded. Since the ratio of fee to
- // long term fee is the same, we only need to check if one of those values match in order to know that the waste is the same.
- if (!curr_selection.empty() && !curr_selection.back() &&
- utxo.GetSelectionAmount() == utxo_pool.at(curr_selection.size() - 1).GetSelectionAmount() &&
- utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) {
- curr_selection.push_back(false);
- } else {
+ if (curr_selection.empty() ||
+ // The previous index is included and therefore not relevant for exclusion shortcut
+ (utxo_pool_index - 1) == curr_selection.back() ||
+ // Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded.
+ // Since the ratio of fee to long term fee is the same, we only need to check if one of those values match in order to know that the waste is the same.
+ utxo.GetSelectionAmount() != utxo_pool.at(utxo_pool_index - 1).GetSelectionAmount() ||
+ utxo.fee != utxo_pool.at(utxo_pool_index - 1).fee)
+ {
// Inclusion branch first (Largest First Exploration)
- curr_selection.push_back(true);
+ curr_selection.push_back(utxo_pool_index);
curr_value += utxo.GetSelectionAmount();
curr_waste += utxo.fee - utxo.long_term_fee;
}
@@ -158,10 +156,8 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
}
// Set output set
- for (size_t i = 0; i < best_selection.size(); ++i) {
- if (best_selection.at(i)) {
- result.AddInput(utxo_pool.at(i));
- }
+ for (const size_t& i : best_selection) {
+ result.AddInput(utxo_pool.at(i));
}
result.ComputeAndSetWaste(CAmount{0});
assert(best_waste == result.GetWaste());
@@ -169,14 +165,14 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
return result;
}
-std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value)
+std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng)
{
SelectionResult result(target_value);
std::vector<size_t> indexes;
indexes.resize(utxo_pool.size());
std::iota(indexes.begin(), indexes.end(), 0);
- Shuffle(indexes.begin(), indexes.end(), FastRandomContext());
+ Shuffle(indexes.begin(), indexes.end(), rng);
CAmount selected_eff_value = 0;
for (const size_t i : indexes) {
@@ -191,16 +187,27 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut
return std::nullopt;
}
-static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
+/** Find a subset of the OutputGroups that is at least as large as, but as close as possible to, the
+ * target amount; solve subset sum.
+ * param@[in] groups OutputGroups to choose from, sorted by value in descending order.
+ * param@[in] nTotalLower Total (effective) value of the UTXOs in groups.
+ * param@[in] nTargetValue Subset sum target, not including change.
+ * param@[out] vfBest Boolean vector representing the subset chosen that is closest to
+ * nTargetValue, with indices corresponding to groups. If the ith
+ * entry is true, that means the ith group in groups was selected.
+ * param@[out] nBest Total amount of subset chosen that is closest to nTargetValue.
+ * param@[in] iterations Maximum number of tries.
+ */
+static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::vector<OutputGroup>& groups,
+ const CAmount& nTotalLower, const CAmount& nTargetValue,
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
std::vector<char> vfIncluded;
+ // Worst case "best" approximation is just all of the groups.
vfBest.assign(groups.size(), true);
nBest = nTotalLower;
- FastRandomContext insecure_rand;
-
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
{
vfIncluded.assign(groups.size(), false);
@@ -223,6 +230,8 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
if (nTotal >= nTargetValue)
{
fReachedTarget = true;
+ // If the total is between nTargetValue and nBest, it's our new best
+ // approximation.
if (nTotal < nBest)
{
nBest = nTotal;
@@ -237,22 +246,25 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
}
}
-std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue)
+std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
+ CAmount change_target, FastRandomContext& rng)
{
SelectionResult result(nTargetValue);
// List of values less than target
std::optional<OutputGroup> lowest_larger;
+ // Groups with selection amount smaller than the target and any change we might produce.
+ // Don't include groups larger than this, because they will only cause us to overshoot.
std::vector<OutputGroup> applicable_groups;
CAmount nTotalLower = 0;
- Shuffle(groups.begin(), groups.end(), FastRandomContext());
+ Shuffle(groups.begin(), groups.end(), rng);
for (const OutputGroup& group : groups) {
if (group.GetSelectionAmount() == nTargetValue) {
result.AddInput(group);
return result;
- } else if (group.GetSelectionAmount() < nTargetValue + MIN_CHANGE) {
+ } else if (group.GetSelectionAmount() < nTargetValue + change_target) {
applicable_groups.push_back(group);
nTotalLower += group.GetSelectionAmount();
} else if (!lowest_larger || group.GetSelectionAmount() < lowest_larger->GetSelectionAmount()) {
@@ -278,15 +290,15 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
std::vector<char> vfBest;
CAmount nBest;
- ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
- if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) {
- ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
+ ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
+ if (nBest != nTargetValue && nTotalLower >= nTargetValue + change_target) {
+ ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue + change_target, vfBest, nBest);
}
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
// or the next bigger coin is closer), return the bigger coin
if (lowest_larger &&
- ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || lowest_larger->GetSelectionAmount() <= nBest)) {
+ ((nBest != nTargetValue && nBest < nTargetValue + change_target) || lowest_larger->GetSelectionAmount() <= nBest)) {
result.AddInput(*lowest_larger);
} else {
for (unsigned int i = 0; i < applicable_groups.size(); i++) {
@@ -315,29 +327,29 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
******************************************************************************/
-void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) {
+void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) {
// Compute the effective value first
- const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes);
+ const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes);
const CAmount ev = output.txout.nValue - coin_fee;
// Filter for positive only here before adding the coin
if (positive_only && ev <= 0) return;
m_outputs.push_back(output);
- CInputCoin& coin = m_outputs.back();
+ COutput& coin = m_outputs.back();
- coin.m_fee = coin_fee;
- fee += coin.m_fee;
+ coin.fee = coin_fee;
+ fee += coin.fee;
- coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
- long_term_fee += coin.m_long_term_fee;
+ coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
+ long_term_fee += coin.long_term_fee;
coin.effective_value = ev;
effective_value += coin.effective_value;
- m_from_me &= from_me;
- m_value += output.txout.nValue;
- m_depth = std::min(m_depth, depth);
+ m_from_me &= coin.from_me;
+ m_value += coin.txout.nValue;
+ m_depth = std::min(m_depth, coin.depth);
// ancestors here express the number of ancestors the new coin will end up having, which is
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
// have common ancestors
@@ -359,7 +371,7 @@ CAmount OutputGroup::GetSelectionAmount() const
return m_subtract_fee_outputs ? m_value : effective_value;
}
-CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
+CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
{
// This function should not be called with empty inputs as that would mean the selection failed
assert(!inputs.empty());
@@ -367,8 +379,8 @@ CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cos
// Always consider the cost of spending an input now vs in the future.
CAmount waste = 0;
CAmount selected_effective_value = 0;
- for (const CInputCoin& coin : inputs) {
- waste += coin.m_fee - coin.m_long_term_fee;
+ for (const COutput& coin : inputs) {
+ waste += coin.fee - coin.long_term_fee;
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
}
@@ -386,6 +398,17 @@ CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cos
return waste;
}
+CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng)
+{
+ if (payment_value <= CHANGE_LOWER / 2) {
+ return CHANGE_LOWER;
+ } else {
+ // random value between 50ksat and min (payment_value * 2, 1milsat)
+ const auto upper_bound = std::min(payment_value * 2, CHANGE_UPPER);
+ return rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER;
+ }
+}
+
void SelectionResult::ComputeAndSetWaste(CAmount change_cost)
{
m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective);
@@ -413,14 +436,14 @@ void SelectionResult::AddInput(const OutputGroup& group)
m_use_effective = !group.m_subtract_fee_outputs;
}
-const std::set<CInputCoin>& SelectionResult::GetInputSet() const
+const std::set<COutput>& SelectionResult::GetInputSet() const
{
return m_selected_inputs;
}
-std::vector<CInputCoin> SelectionResult::GetShuffledInputVector() const
+std::vector<COutput> SelectionResult::GetShuffledInputVector() const
{
- std::vector<CInputCoin> coins(m_selected_inputs.begin(), m_selected_inputs.end());
+ std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end());
Shuffle(coins.begin(), coins.end(), FastRandomContext());
return coins;
}
@@ -432,4 +455,9 @@ bool SelectionResult::operator<(SelectionResult other) const
// As this operator is only used in std::min_element, we want the result that has more inputs when waste are equal.
return *m_waste < *other.m_waste || (*m_waste == *other.m_waste && m_selected_inputs.size() > other.m_selected_inputs.size());
}
+
+std::string COutput::ToString() const
+{
+ return strprintf("COutput(%s, %d, %d) [%s]", outpoint.hash.ToString(), outpoint.n, depth, FormatMoney(txout.nValue));
+}
} // namespace wallet
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 496a026999..c1484c0a57 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -13,74 +13,93 @@
#include <optional>
namespace wallet {
-//! target minimum change amount
-static constexpr CAmount MIN_CHANGE{COIN / 100};
-//! final minimum change amount after paying for fees
-static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
+//! lower bound for randomly-chosen target change amount
+static constexpr CAmount CHANGE_LOWER{50000};
+//! upper bound for randomly-chosen target change amount
+static constexpr CAmount CHANGE_UPPER{1000000};
/** A UTXO under consideration for use in funding a new transaction. */
-class CInputCoin {
-public:
- CInputCoin(const CTransactionRef& tx, unsigned int i)
- {
- if (!tx)
- throw std::invalid_argument("tx should not be null");
- if (i >= tx->vout.size())
- throw std::out_of_range("The output index is out of range");
-
- outpoint = COutPoint(tx->GetHash(), i);
- txout = tx->vout[i];
- effective_value = txout.nValue;
- }
+struct COutput {
+ /** The outpoint identifying this UTXO */
+ COutPoint outpoint;
- CInputCoin(const CTransactionRef& tx, unsigned int i, int input_bytes) : CInputCoin(tx, i)
- {
- m_input_bytes = input_bytes;
- }
+ /** The output itself */
+ CTxOut txout;
- CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in)
- {
- outpoint = outpoint_in;
- txout = txout_in;
- effective_value = txout.nValue;
- }
+ /**
+ * Depth in block chain.
+ * If > 0: the tx is on chain and has this many confirmations.
+ * If = 0: the tx is waiting confirmation.
+ * If < 0: a conflicting tx is on chain and has this many confirmations. */
+ int depth;
- CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in)
- {
- m_input_bytes = input_bytes;
- }
+ /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
+ int input_bytes;
- COutPoint outpoint;
- CTxOut txout;
+ /** Whether we have the private keys to spend this output */
+ bool spendable;
+
+ /** Whether we know how to spend this output, ignoring the lack of keys */
+ bool solvable;
+
+ /**
+ * Whether this output is considered safe to spend. Unconfirmed transactions
+ * from outside keys and unconfirmed replacement transactions are considered
+ * unsafe and will not be used to fund new spending transactions.
+ */
+ bool safe;
+
+ /** The time of the transaction containing this output as determined by CWalletTx::nTimeSmart */
+ int64_t time;
+
+ /** Whether the transaction containing this output is sent from the owning wallet */
+ bool from_me;
+
+ /** The output's value minus fees required to spend it. Initialized as the output's absolute value. */
CAmount effective_value;
- CAmount m_fee{0};
- CAmount m_long_term_fee{0};
- /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
- int m_input_bytes{-1};
+ /** The fee required to spend this output at the transaction's target feerate. */
+ CAmount fee{0};
- bool operator<(const CInputCoin& rhs) const {
- return outpoint < rhs.outpoint;
- }
+ /** The fee required to spend this output at the consolidation feerate. */
+ CAmount long_term_fee{0};
- bool operator!=(const CInputCoin& rhs) const {
- return outpoint != rhs.outpoint;
- }
+ COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me)
+ : outpoint{outpoint},
+ txout{txout},
+ depth{depth},
+ input_bytes{input_bytes},
+ spendable{spendable},
+ solvable{solvable},
+ safe{safe},
+ time{time},
+ from_me{from_me},
+ effective_value{txout.nValue}
+ {}
+
+ std::string ToString() const;
- bool operator==(const CInputCoin& rhs) const {
- return outpoint == rhs.outpoint;
+ bool operator<(const COutput& rhs) const
+ {
+ return outpoint < rhs.outpoint;
}
};
/** Parameters for one iteration of Coin Selection. */
-struct CoinSelectionParams
-{
+struct CoinSelectionParams {
+ /** Randomness to use in the context of coin selection. */
+ FastRandomContext& rng_fast;
/** Size of a change output in bytes, determined by the output type. */
size_t change_output_size = 0;
/** Size of the input to spend a change output in virtual bytes. */
size_t change_spend_size = 0;
+ /** Mininmum change to target in Knapsack solver: select coins to cover the payment and
+ * at least this value of change. */
+ CAmount m_min_change_target{0};
/** Cost of creating the change output. */
CAmount m_change_fee{0};
+ /** The pre-determined minimum value to target when funding a change output. */
+ CAmount m_change_target{0};
/** Cost of creating the change output + cost of spending the change output in the future. */
CAmount m_cost_of_change{0};
/** The targeted feerate of the transaction being built. */
@@ -100,17 +119,22 @@ struct CoinSelectionParams
* reuse. Dust outputs are not eligible to be added to output groups and thus not considered. */
bool m_avoid_partial_spends = false;
- CoinSelectionParams(size_t change_output_size, size_t change_spend_size, CFeeRate effective_feerate,
- CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial) :
- change_output_size(change_output_size),
- change_spend_size(change_spend_size),
- m_effective_feerate(effective_feerate),
- m_long_term_feerate(long_term_feerate),
- m_discard_feerate(discard_feerate),
- tx_noinputs_size(tx_noinputs_size),
- m_avoid_partial_spends(avoid_partial)
- {}
- CoinSelectionParams() {}
+ CoinSelectionParams(FastRandomContext& rng_fast, size_t change_output_size, size_t change_spend_size,
+ CAmount min_change_target, CFeeRate effective_feerate,
+ CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial)
+ : rng_fast{rng_fast},
+ change_output_size(change_output_size),
+ change_spend_size(change_spend_size),
+ m_min_change_target(min_change_target),
+ m_effective_feerate(effective_feerate),
+ m_long_term_feerate(long_term_feerate),
+ m_discard_feerate(discard_feerate),
+ tx_noinputs_size(tx_noinputs_size),
+ m_avoid_partial_spends(avoid_partial)
+ {
+ }
+ CoinSelectionParams(FastRandomContext& rng_fast)
+ : rng_fast{rng_fast} {}
};
/** Parameters for filtering which OutputGroups we may use in coin selection.
@@ -139,7 +163,7 @@ struct CoinEligibilityFilter
struct OutputGroup
{
/** The list of UTXOs contained in this output group. */
- std::vector<CInputCoin> m_outputs;
+ std::vector<COutput> m_outputs;
/** Whether the UTXOs were sent by the wallet to itself. This is relevant because we may want at
* least a certain number of confirmations on UTXOs received from outside wallets while trusting
* our own UTXOs more. */
@@ -176,7 +200,7 @@ struct OutputGroup
m_subtract_fee_outputs(params.m_subtract_fee_outputs)
{}
- void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only);
+ void Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only);
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
CAmount GetSelectionAmount() const;
};
@@ -198,13 +222,28 @@ struct OutputGroup
* @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false).
* @return The waste
*/
-[[nodiscard]] CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
+[[nodiscard]] CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
+
+
+/** Choose a random change target for each transaction to make it harder to fingerprint the Core
+ * wallet based on the change output values of transactions it creates.
+ * The random value is between 50ksat and min(2 * payment_value, 1milsat)
+ * When payment_value <= 25ksat, the value is just 50ksat.
+ *
+ * Making change amounts similar to the payment value may help disguise which output(s) are payments
+ * are which ones are change. Using double the payment value may increase the number of inputs
+ * needed (and thus be more expensive in fees), but breaks analysis techniques which assume the
+ * coins selected are just sufficient to cover the payment amount ("unnecessary input" heuristic).
+ *
+ * @param[in] payment_value Average payment value of the transaction output(s).
+ */
+[[nodiscard]] CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng);
struct SelectionResult
{
private:
/** Set of inputs selected by the algorithm to use in the transaction */
- std::set<CInputCoin> m_selected_inputs;
+ std::set<COutput> m_selected_inputs;
/** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */
const CAmount m_target;
/** Whether the input values for calculations should be the effective value (true) or normal value (false) */
@@ -230,9 +269,9 @@ public:
[[nodiscard]] CAmount GetWaste() const;
/** Get m_selected_inputs */
- const std::set<CInputCoin>& GetInputSet() const;
- /** Get the vector of CInputCoins that will be used to fill in a CTransaction's vin */
- std::vector<CInputCoin> GetShuffledInputVector() const;
+ const std::set<COutput>& GetInputSet() const;
+ /** Get the vector of COutputs that will be used to fill in a CTransaction's vin */
+ std::vector<COutput> GetShuffledInputVector() const;
bool operator<(SelectionResult other) const;
};
@@ -246,10 +285,11 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
* @param[in] target_value The target value to select for
* @returns If successful, a SelectionResult, otherwise, std::nullopt
*/
-std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value);
+std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng);
// Original coin selection algorithm as a fallback
-std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue);
+std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
+ CAmount change_target, FastRandomContext& rng);
} // namespace wallet
#endif // BITCOIN_WALLET_COINSELECTION_H
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 0ed2658129..8e79ee678e 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -6,6 +6,7 @@
#include <chainparams.h>
#include <fs.h>
#include <logging.h>
+#include <util/system.h>
#include <wallet/db.h>
#include <exception>
@@ -137,4 +138,13 @@ bool IsSQLiteFile(const fs::path& path)
// Check the application id matches our network magic
return memcmp(Params().MessageStart(), app_id, 4) == 0;
}
+
+void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options)
+{
+ // Override current options with args values, if any were specified
+ options.use_unsafe_sync = args.GetBoolArg("-unsafesqlitesync", options.use_unsafe_sync);
+ options.use_shared_memory = !args.GetBoolArg("-privdb", !options.use_shared_memory);
+ options.max_log_mb = args.GetIntArg("-dblogsize", options.max_log_mb);
+}
+
} // namespace wallet
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 5825b00e3a..f09844c37e 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -16,6 +16,7 @@
#include <optional>
#include <string>
+class ArgsManager;
struct bilingual_str;
namespace wallet {
@@ -207,7 +208,12 @@ struct DatabaseOptions {
std::optional<DatabaseFormat> require_format;
uint64_t create_flags = 0;
SecureString create_passphrase;
- bool verify = true;
+
+ // Specialized options. Not every option is supported by every backend.
+ bool verify = true; //!< Check data integrity on load.
+ bool use_unsafe_sync = false; //!< Disable file sync for faster performance.
+ bool use_shared_memory = false; //!< Let other processes access the database.
+ int64_t max_log_mb = 100; //!< Max log size to allow before consolidating.
};
enum class DatabaseStatus {
@@ -227,6 +233,7 @@ enum class DatabaseStatus {
/** Recursively list database paths in directory. */
std::vector<fs::path> ListDatabases(const fs::path& path);
+void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options);
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
fs::path BDBDataFile(const fs::path& path);
diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp
index 6d8508fc72..d80c3e25b0 100644
--- a/src/wallet/dump.cpp
+++ b/src/wallet/dump.cpp
@@ -19,10 +19,10 @@ namespace wallet {
static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP";
uint32_t DUMP_VERSION = 1;
-bool DumpWallet(CWallet& wallet, bilingual_str& error)
+bool DumpWallet(const ArgsManager& args, CWallet& wallet, bilingual_str& error)
{
// Get the dumpfile
- std::string dump_filename = gArgs.GetArg("-dumpfile", "");
+ std::string dump_filename = args.GetArg("-dumpfile", "");
if (dump_filename.empty()) {
error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided.");
return false;
@@ -114,10 +114,10 @@ static void WalletToolReleaseWallet(CWallet* wallet)
delete wallet;
}
-bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
+bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
// Get the dumpfile
- std::string dump_filename = gArgs.GetArg("-dumpfile", "");
+ std::string dump_filename = args.GetArg("-dumpfile", "");
if (dump_filename.empty()) {
error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
return false;
@@ -171,7 +171,7 @@ bool CreateFromDump(const std::string& name, const fs::path& wallet_path, biling
return false;
}
// Get the data file format with format_value as the default
- std::string file_format = gArgs.GetArg("-format", format_value);
+ std::string file_format = args.GetArg("-format", format_value);
if (file_format.empty()) {
error = _("No wallet file format provided. To use createfromdump, -format=<format> must be provided.");
return false;
@@ -193,6 +193,7 @@ bool CreateFromDump(const std::string& name, const fs::path& wallet_path, biling
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(args, options);
options.require_create = true;
options.require_format = data_format;
std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error);
diff --git a/src/wallet/dump.h b/src/wallet/dump.h
index a879c4db35..bf683e9843 100644
--- a/src/wallet/dump.h
+++ b/src/wallet/dump.h
@@ -11,11 +11,12 @@
#include <vector>
struct bilingual_str;
+class ArgsManager;
namespace wallet {
class CWallet;
-bool DumpWallet(CWallet& wallet, bilingual_str& error);
-bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
+bool DumpWallet(const ArgsManager& args, CWallet& wallet, bilingual_str& error);
+bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
} // namespace wallet
#endif // BITCOIN_WALLET_DUMP_H
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 3552c14160..73042424ad 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -135,7 +135,7 @@ static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, con
feerate += std::max(node_incremental_relay_fee, wallet_incremental_relay_fee);
// Fee rate must also be at least the wallet's GetMinimumFeeRate
- CFeeRate min_feerate(GetMinimumFeeRate(wallet, coin_control, /* feeCalc */ nullptr));
+ CFeeRate min_feerate(GetMinimumFeeRate(wallet, coin_control, /*feeCalc=*/nullptr));
// Set the required fee rate for the replacement transaction in coin control.
return std::max(feerate, min_feerate);
@@ -233,12 +233,6 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
// Write back transaction
mtx = CMutableTransaction(*tx_new);
- // Mark new tx not replaceable, if requested.
- if (!coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf)) {
- for (auto& input : mtx.vin) {
- if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
- }
- }
return Result::OK;
}
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 7a83dbc35d..7e21126298 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -80,9 +80,9 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
#ifdef USE_BDB
- argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
+ argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DatabaseOptions().max_log_mb), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
- argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
+ argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", !DatabaseOptions().use_shared_memory), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
#else
argsman.AddHiddenArgs({"-dblogsize", "-flushwallet", "-privdb"});
#endif
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 9083c304b2..98e843385c 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -111,6 +111,17 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet,
return result;
}
+WalletTxOut MakeWalletTxOut(const CWallet& wallet,
+ const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+{
+ WalletTxOut result;
+ result.txout = output.txout;
+ result.time = output.time;
+ result.depth_in_main_chain = output.depth;
+ result.is_spent = wallet.IsSpent(output.outpoint.hash, output.outpoint.n);
+ return result;
+}
+
class WalletImpl : public Wallet
{
public:
@@ -419,8 +430,8 @@ public:
for (const auto& entry : ListCoins(*m_wallet)) {
auto& group = result[entry.first];
for (const auto& coin : entry.second) {
- group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
- MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth));
+ group.emplace_back(coin.outpoint,
+ MakeWalletTxOut(*m_wallet, coin));
}
}
return result;
@@ -544,6 +555,7 @@ public:
std::shared_ptr<CWallet> wallet;
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*m_context.args, options);
options.require_create = true;
options.create_flags = wallet_creation_flags;
options.create_passphrase = passphrase;
@@ -553,6 +565,7 @@ public:
{
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*m_context.args, options);
options.require_existing = true;
return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
}
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 633d8c5450..c06513588b 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -57,6 +57,7 @@ bool VerifyWallets(WalletContext& context)
if (!args.IsArgSet("wallet")) {
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(args, options);
bilingual_str error_string;
options.require_existing = true;
options.verify = false;
@@ -84,6 +85,7 @@ bool VerifyWallets(WalletContext& context)
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(args, options);
options.require_existing = true;
options.verify = true;
bilingual_str error_string;
@@ -112,6 +114,7 @@ bool LoadWallets(WalletContext& context)
}
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*context.args, options);
options.require_existing = true;
options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
bilingual_str error;
@@ -127,6 +130,8 @@ bool LoadWallets(WalletContext& context)
chain.initError(error);
return false;
}
+
+ NotifyWalletLoaded(context, pwallet);
AddWallet(context, pwallet);
}
return true;
diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp
index 1a6f06213c..cddf94aab2 100644
--- a/src/wallet/receive.cpp
+++ b/src/wallet/receive.cpp
@@ -325,8 +325,8 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse)
const CWalletTx& wtx = entry.second;
const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)};
const int tx_depth{wallet.GetTxDepthInMainChain(wtx)};
- const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
- const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
+ const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, /*fUseCache=*/true, ISMINE_SPENDABLE | reuse_filter)};
+ const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, /*fUseCache=*/true, ISMINE_WATCH_ONLY | reuse_filter)};
if (is_trusted && tx_depth >= min_depth) {
ret.m_mine_trusted += tx_credit_mine;
ret.m_watchonly_trusted += tx_credit_watchonly;
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index 51587a64a3..d4f6c9d805 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -239,7 +239,7 @@ RPCHelpMan addmultisigaddress()
{RPCResult::Type::STR, "address", "The value of the new multisig address"},
{RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script"},
{RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
- {RPCResult::Type::ARR, "warnings", /* optional */ true, "Any warnings resulting from the creation of this multisig",
+ {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig",
{
{RPCResult::Type::STR, "", ""},
}},
@@ -597,7 +597,7 @@ RPCHelpMan getaddressinfo()
DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
if (desc_spk_man) {
std::string desc_str;
- if (desc_spk_man->GetDescriptorString(desc_str, /* priv */ false)) {
+ if (desc_spk_man->GetDescriptorString(desc_str, /*priv=*/false)) {
ret.pushKV("parent_desc", desc_str);
}
}
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index 228564fae4..b048ddfc6e 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -1260,7 +1260,7 @@ RPCHelpMan importmulti()
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"},
{"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor",
- /* oneline_description */ "", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}
+ /*oneline_description=*/"", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}
},
{"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n"
" or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
@@ -1268,7 +1268,7 @@ RPCHelpMan importmulti()
" \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
" 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
" creation time of all keys being imported by the importmulti call will be scanned.",
- /* oneline_description */ "", {"timestamp | \"now\"", "integer / string"}
+ /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"}
},
{"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"},
{"witnessscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"},
@@ -1596,7 +1596,7 @@ RPCHelpMan importdescriptors()
" \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
" 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
" of all descriptors being imported will be scanned.",
- /* oneline_description */ "", {"timestamp | \"now\"", "integer / string"}
+ /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"}
},
{"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
{"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index 035541babd..4eccff3969 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -112,7 +112,7 @@ RPCHelpMan getreceivedbyaddress()
LOCK(pwallet->cs_wallet);
- return ValueFromAmount(GetReceived(*pwallet, request.params, /* by_label */ false));
+ return ValueFromAmount(GetReceived(*pwallet, request.params, /*by_label=*/false));
},
};
}
@@ -153,7 +153,7 @@ RPCHelpMan getreceivedbylabel()
LOCK(pwallet->cs_wallet);
- return ValueFromAmount(GetReceived(*pwallet, request.params, /* by_label */ true));
+ return ValueFromAmount(GetReceived(*pwallet, request.params, /*by_label=*/true));
},
};
}
@@ -648,16 +648,16 @@ RPCHelpMan listunspent()
for (const COutput& out : vecOutputs) {
CTxDestination address;
- const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
+ const CScript& scriptPubKey = out.txout.scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address);
- bool reused = avoid_reuse && pwallet->IsSpentKey(out.tx->GetHash(), out.i);
+ bool reused = avoid_reuse && pwallet->IsSpentKey(out.outpoint.hash, out.outpoint.n);
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
continue;
UniValue entry(UniValue::VOBJ);
- entry.pushKV("txid", out.tx->GetHash().GetHex());
- entry.pushKV("vout", out.i);
+ entry.pushKV("txid", out.outpoint.hash.GetHex());
+ entry.pushKV("vout", (int)out.outpoint.n);
if (fValidAddress) {
entry.pushKV("address", EncodeDestination(address));
@@ -702,21 +702,21 @@ RPCHelpMan listunspent()
}
entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
- entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
- entry.pushKV("confirmations", out.nDepth);
- if (!out.nDepth) {
+ entry.pushKV("amount", ValueFromAmount(out.txout.nValue));
+ entry.pushKV("confirmations", out.depth);
+ if (!out.depth) {
size_t ancestor_count, descendant_count, ancestor_size;
CAmount ancestor_fees;
- pwallet->chain().getTransactionAncestry(out.tx->GetHash(), ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
+ pwallet->chain().getTransactionAncestry(out.outpoint.hash, ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
if (ancestor_count) {
entry.pushKV("ancestorcount", uint64_t(ancestor_count));
entry.pushKV("ancestorsize", uint64_t(ancestor_size));
entry.pushKV("ancestorfees", uint64_t(ancestor_fees));
}
}
- entry.pushKV("spendable", out.fSpendable);
- entry.pushKV("solvable", out.fSolvable);
- if (out.fSolvable) {
+ entry.pushKV("spendable", out.spendable);
+ entry.pushKV("solvable", out.solvable);
+ if (out.solvable) {
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
if (provider) {
auto descriptor = InferDescriptor(scriptPubKey, *provider);
@@ -724,7 +724,7 @@ RPCHelpMan listunspent()
}
}
if (avoid_reuse) entry.pushKV("reused", reused);
- entry.pushKV("safe", out.fSafe);
+ entry.pushKV("safe", out.safe);
results.push_back(entry);
}
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 072879a42a..07119133b7 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -9,10 +9,12 @@
#include <rpc/rawtransaction_util.h>
#include <rpc/util.h>
#include <util/fees.h>
+#include <util/rbf.h>
#include <util/translation.h>
#include <util/vector.h>
#include <wallet/coincontrol.h>
#include <wallet/feebumper.h>
+#include <wallet/fees.h>
#include <wallet/rpc/util.h>
#include <wallet/spend.h>
#include <wallet/wallet.h>
@@ -21,7 +23,8 @@
namespace wallet {
-static void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient> &recipients) {
+static void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient>& recipients)
+{
std::set<CTxDestination> destinations;
int i = 0;
for (const std::string& address: address_amounts.getKeys()) {
@@ -51,6 +54,93 @@ static void ParseRecipients(const UniValue& address_amounts, const UniValue& sub
}
}
+static void InterpretFeeEstimationInstructions(const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, UniValue& options)
+{
+ if (options.exists("conf_target") || options.exists("estimate_mode")) {
+ if (!conf_target.isNull() || !estimate_mode.isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both");
+ }
+ } else {
+ options.pushKV("conf_target", conf_target);
+ options.pushKV("estimate_mode", estimate_mode);
+ }
+ if (options.exists("fee_rate")) {
+ if (!fee_rate.isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass the fee_rate either as an argument, or in the options object, but not both");
+ }
+ } else {
+ options.pushKV("fee_rate", fee_rate);
+ }
+ if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");
+ }
+}
+
+static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const UniValue& options, const CMutableTransaction& rawTx)
+{
+ // Make a blank psbt
+ PartiallySignedTransaction psbtx(rawTx);
+
+ // First fill transaction with our data without signing,
+ // so external signers are not asked sign more than once.
+ bool complete;
+ pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true);
+ const TransactionError err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false)};
+ if (err != TransactionError::OK) {
+ throw JSONRPCTransactionError(err);
+ }
+
+ CMutableTransaction mtx;
+ complete = FinalizeAndExtractPSBT(psbtx, mtx);
+
+ UniValue result(UniValue::VOBJ);
+
+ const bool psbt_opt_in{options.exists("psbt") && options["psbt"].get_bool()};
+ bool add_to_wallet{options.exists("add_to_wallet") ? options["add_to_wallet"].get_bool() : true};
+ if (psbt_opt_in || !complete || !add_to_wallet) {
+ // Serialize the PSBT
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+ result.pushKV("psbt", EncodeBase64(ssTx.str()));
+ }
+
+ if (complete) {
+ std::string hex{EncodeHexTx(CTransaction(mtx))};
+ CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
+ result.pushKV("txid", tx->GetHash().GetHex());
+ if (add_to_wallet && !psbt_opt_in) {
+ pwallet->CommitTransaction(tx, {}, /*orderForm=*/{});
+ } else {
+ result.pushKV("hex", hex);
+ }
+ }
+ result.pushKV("complete", complete);
+
+ return result;
+}
+
+static void PreventOutdatedOptions(const UniValue& options)
+{
+ if (options.exists("feeRate")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate (" + CURRENCY_ATOM + "/vB) instead of feeRate");
+ }
+ if (options.exists("changeAddress")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address instead of changeAddress");
+ }
+ if (options.exists("changePosition")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_position instead of changePosition");
+ }
+ if (options.exists("includeWatching")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Use include_watching instead of includeWatching");
+ }
+ if (options.exists("lockUnspents")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Use lock_unspents instead of lockUnspents");
+ }
+ if (options.exists("subtractFeeFromOutputs")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Use subtract_fee_from_outputs instead of subtractFeeFromOutputs");
+ }
+}
+
UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose)
{
EnsureWalletIsUnlocked(wallet);
@@ -108,7 +198,7 @@ static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const Un
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");
}
// Fee rates in sat/vB cannot represent more than 3 significant digits.
- cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /* decimals */ 3)};
+ cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /*decimals=*/3)};
if (override_min_fee) cc.fOverrideFeeRate = true;
// Default RBF to true for explicit fee_rate, if unset.
if (!cc.m_signal_bip125_rbf) cc.m_signal_bip125_rbf = true;
@@ -203,7 +293,7 @@ RPCHelpMan sendtoaddress()
// We also enable partial spend avoidance if reuse avoidance is set.
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
- SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[9], /* override_min_fee */ false);
+ SetFeeEstimateMode(*pwallet, coin_control, /*conf_target=*/request.params[6], /*estimate_mode=*/request.params[7], /*fee_rate=*/request.params[9], /*override_min_fee=*/false);
EnsureWalletIsUnlocked(*pwallet);
@@ -306,7 +396,7 @@ RPCHelpMan sendmany()
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
}
- SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[8], /* override_min_fee */ false);
+ SetFeeEstimateMode(*pwallet, coin_control, /*conf_target=*/request.params[6], /*estimate_mode=*/request.params[7], /*fee_rate=*/request.params[8], /*override_min_fee=*/false);
std::vector<CRecipient> recipients;
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
@@ -360,31 +450,43 @@ RPCHelpMan settxfee()
// Only includes key documentation where the key is snake_case in all RPC methods. MixedCase keys can be added later.
-static std::vector<RPCArg> FundTxDoc()
+static std::vector<RPCArg> FundTxDoc(bool solving_data = true)
{
- return {
+ std::vector<RPCArg> args = {
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
- {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Marks this transaction as BIP125-replaceable.\n"
- "Allows this transaction to be replaced by a transaction with higher fees"},
- {"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n"
- "Used for fee estimation during coin selection.",
- {
- {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.",
- {
- {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
- }},
- {"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.",
- {
- {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
- }},
- {"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.",
- {
- {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"},
- }},
- }},
+ {
+ "replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Marks this transaction as BIP125-replaceable.\n"
+ "Allows this transaction to be replaced by a transaction with higher fees"
+ },
};
+ if (solving_data) {
+ args.push_back({"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n"
+ "Used for fee estimation during coin selection.",
+ {
+ {
+ "pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.",
+ {
+ {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
+ }
+ },
+ {
+ "scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.",
+ {
+ {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
+ }
+ },
+ {
+ "descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.",
+ {
+ {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"},
+ }
+ },
+ }
+ });
+ }
+ return args;
}
void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl, bool override_min_fee)
@@ -736,7 +838,7 @@ RPCHelpMan fundrawtransaction()
CCoinControl coin_control;
// Automatically select (additional) coins. Can be overridden by options.add_inputs.
coin_control.m_add_inputs = true;
- FundTransaction(*pwallet, tx, fee, change_position, request.params[1], coin_control, /* override_min_fee */ true);
+ FundTransaction(*pwallet, tx, fee, change_position, request.params[1], coin_control, /*override_min_fee=*/true);
UniValue result(UniValue::VOBJ);
result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
@@ -941,7 +1043,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
if (options.exists("replaceable")) {
coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
}
- SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /* override_min_fee */ false);
+ SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /*override_min_fee=*/false);
}
// Make sure the results are valid at least up to the most recent block
@@ -1126,102 +1228,249 @@ RPCHelpMan send()
if (!pwallet) return NullUniValue;
UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]};
- if (options.exists("conf_target") || options.exists("estimate_mode")) {
- if (!request.params[1].isNull() || !request.params[2].isNull()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both");
- }
- } else {
- options.pushKV("conf_target", request.params[1]);
- options.pushKV("estimate_mode", request.params[2]);
- }
- if (options.exists("fee_rate")) {
- if (!request.params[3].isNull()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass the fee_rate either as an argument, or in the options object, but not both");
- }
- } else {
- options.pushKV("fee_rate", request.params[3]);
- }
- if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");
- }
- if (options.exists("feeRate")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate (" + CURRENCY_ATOM + "/vB) instead of feeRate");
- }
- if (options.exists("changeAddress")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address");
- }
- if (options.exists("changePosition")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_position");
- }
- if (options.exists("includeWatching")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Use include_watching");
- }
- if (options.exists("lockUnspents")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Use lock_unspents");
- }
- if (options.exists("subtractFeeFromOutputs")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Use subtract_fee_from_outputs");
- }
+ InterpretFeeEstimationInstructions(/*conf_target=*/request.params[1], /*estimate_mode=*/request.params[2], /*fee_rate=*/request.params[3], options);
+ PreventOutdatedOptions(options);
- const bool psbt_opt_in = options.exists("psbt") && options["psbt"].get_bool();
CAmount fee;
int change_position;
- bool rbf = pwallet->m_signal_rbf;
- if (options.exists("replaceable")) {
- rbf = options["replaceable"].get_bool();
- }
+ bool rbf{options.exists("replaceable") ? options["replaceable"].get_bool() : pwallet->m_signal_rbf};
CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf);
CCoinControl coin_control;
// Automatically select coins, unless at least one is manually selected. Can
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
SetOptionsInputWeights(options["inputs"], options);
- FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false);
+ FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /*override_min_fee=*/false);
- bool add_to_wallet = true;
- if (options.exists("add_to_wallet")) {
- add_to_wallet = options["add_to_wallet"].get_bool();
+ return FinishTransaction(pwallet, options, rawTx);
+ }
+ };
+}
+
+RPCHelpMan sendall()
+{
+ return RPCHelpMan{"sendall",
+ "EXPERIMENTAL warning: this call may be changed in future releases.\n"
+ "\nSpend the value of all (or specific) confirmed UTXOs in the wallet to one or more recipients.\n"
+ "Unconfirmed inbound UTXOs and locked UTXOs will not be spent. Sendall will respect the avoid_reuse wallet flag.\n"
+ "If your wallet contains many small inputs, either because it received tiny payments or as a result of accumulating change, consider using `send_max` to exclude inputs that are worth less than the fees needed to spend them.\n",
+ {
+ {"recipients", RPCArg::Type::ARR, RPCArg::Optional::NO, "The sendall destinations. Each address may only appear once.\n"
+ "Optionally some recipients can be specified with an amount to perform payments, but at least one address must appear without a specified amount.\n",
+ {
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "A bitcoin address which receives an equal share of the unspecified amount."},
+ {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "",
+ {
+ {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""},
+ },
+ },
+ },
+ },
+ {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
+ " \"" + FeeModes("\"\n\"") + "\""},
+ {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
+ {
+ "options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
+ Cat<std::vector<RPCArg>>(
+ {
+ {"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns the serialized transaction without broadcasting or adding it to the wallet"},
+ {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
+ {"include_watching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch-only.\n"
+ "Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
+ "e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
+ {"inputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Use exactly the specified inputs to build the transaction. Specifying inputs is incompatible with send_max. A JSON array of JSON objects",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
+ {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
+ {"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"},
+ },
+ },
+ {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
+ {"lock_unspents", RPCArg::Type::BOOL, RPCArg::Default{false}, "Lock selected unspent outputs"},
+ {"psbt", RPCArg::Type::BOOL, RPCArg::DefaultHint{"automatic"}, "Always return a PSBT, implies add_to_wallet=false."},
+ {"send_max", RPCArg::Type::BOOL, RPCArg::Default{false}, "When true, only use UTXOs that can pay for their own fees to maximize the output amount. When 'false' (default), no UTXO is left behind. send_max is incompatible with providing specific inputs."},
+ },
+ FundTxDoc()
+ ),
+ "options"
+ },
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
+ {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id for the send. Only 1 transaction is created regardless of the number of addresses."},
+ {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "If add_to_wallet is false, the hex-encoded raw transaction with signature(s)"},
+ {RPCResult::Type::STR, "psbt", /*optional=*/true, "If more signatures are needed, or if add_to_wallet is false, the base64-encoded (partially) signed transaction"}
+ }
+ },
+ RPCExamples{""
+ "\nSpend all UTXOs from the wallet with a fee rate of 1 " + CURRENCY_ATOM + "/vB using named arguments\n"
+ + HelpExampleCli("-named sendall", "recipients='[\"" + EXAMPLE_ADDRESS[0] + "\"]' fee_rate=1\n") +
+ "Spend all UTXOs with a fee rate of 1.1 " + CURRENCY_ATOM + "/vB using positional arguments\n"
+ + HelpExampleCli("sendall", "'[\"" + EXAMPLE_ADDRESS[0] + "\"]' null \"unset\" 1.1\n") +
+ "Spend all UTXOs split into equal amounts to two addresses with a fee rate of 1.5 " + CURRENCY_ATOM + "/vB using the options argument\n"
+ + HelpExampleCli("sendall", "'[\"" + EXAMPLE_ADDRESS[0] + "\", \"" + EXAMPLE_ADDRESS[1] + "\"]' null \"unset\" null '{\"fee_rate\": 1.5}'\n") +
+ "Leave dust UTXOs in wallet, spend only UTXOs with positive effective value with a fee rate of 10 " + CURRENCY_ATOM + "/vB using the options argument\n"
+ + HelpExampleCli("sendall", "'[\"" + EXAMPLE_ADDRESS[0] + "\"]' null \"unset\" null '{\"fee_rate\": 10, \"send_max\": true}'\n") +
+ "Spend all UTXOs with a fee rate of 1.3 " + CURRENCY_ATOM + "/vB using named arguments and sending a 0.25 " + CURRENCY_UNIT + " to another recipient\n"
+ + HelpExampleCli("-named sendall", "recipients='[{\"" + EXAMPLE_ADDRESS[1] + "\": 0.25}, \""+ EXAMPLE_ADDRESS[0] + "\"]' fee_rate=1.3\n")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {
+ UniValue::VARR, // recipients
+ UniValue::VNUM, // conf_target
+ UniValue::VSTR, // estimate_mode
+ UniValueType(), // fee_rate, will be checked by AmountFromValue() in SetFeeEstimateMode()
+ UniValue::VOBJ, // options
+ }, true
+ );
+
+ std::shared_ptr<CWallet> const pwallet{GetWalletForJSONRPCRequest(request)};
+ if (!pwallet) return NullUniValue;
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]};
+ InterpretFeeEstimationInstructions(/*conf_target=*/request.params[1], /*estimate_mode=*/request.params[2], /*fee_rate=*/request.params[3], options);
+ PreventOutdatedOptions(options);
+
+
+ std::set<std::string> addresses_without_amount;
+ UniValue recipient_key_value_pairs(UniValue::VARR);
+ const UniValue& recipients{request.params[0]};
+ for (unsigned int i = 0; i < recipients.size(); ++i) {
+ const UniValue& recipient{recipients[i]};
+ if (recipient.isStr()) {
+ UniValue rkvp(UniValue::VOBJ);
+ rkvp.pushKV(recipient.get_str(), 0);
+ recipient_key_value_pairs.push_back(rkvp);
+ addresses_without_amount.insert(recipient.get_str());
+ } else {
+ recipient_key_value_pairs.push_back(recipient);
+ }
+ }
+
+ if (addresses_without_amount.size() == 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Must provide at least one address without a specified amount");
}
- // Make a blank psbt
- PartiallySignedTransaction psbtx(rawTx);
+ CCoinControl coin_control;
+
+ SetFeeEstimateMode(*pwallet, coin_control, options["conf_target"], options["estimate_mode"], options["fee_rate"], /*override_min_fee=*/false);
- // First fill transaction with our data without signing,
- // so external signers are not asked sign more than once.
- bool complete;
- pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true);
- const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false);
- if (err != TransactionError::OK) {
- throw JSONRPCTransactionError(err);
+ coin_control.fAllowWatchOnly = ParseIncludeWatchonly(options["include_watching"], *pwallet);
+
+ const bool rbf{options.exists("replaceable") ? options["replaceable"].get_bool() : pwallet->m_signal_rbf};
+
+ FeeCalculation fee_calc_out;
+ CFeeRate fee_rate{GetMinimumFeeRate(*pwallet, coin_control, &fee_calc_out)};
+ // Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
+ // provided one
+ if (coin_control.m_feerate && fee_rate > *coin_control.m_feerate) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee rate (%s) is lower than the minimum fee rate setting (%s)", coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), fee_rate.ToString(FeeEstimateMode::SAT_VB)));
+ }
+ if (fee_calc_out.reason == FeeReason::FALLBACK && !pwallet->m_allow_fallback_fee) {
+ // eventually allow a fallback fee
+ throw JSONRPCError(RPC_WALLET_ERROR, "Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
}
- CMutableTransaction mtx;
- complete = FinalizeAndExtractPSBT(psbtx, mtx);
+ CMutableTransaction rawTx{ConstructTransaction(options["inputs"], recipient_key_value_pairs, options["locktime"], rbf)};
+ LOCK(pwallet->cs_wallet);
+ std::vector<COutput> all_the_utxos;
+
+ CAmount total_input_value(0);
+ bool send_max{options.exists("send_max") ? options["send_max"].get_bool() : false};
+ if (options.exists("inputs") && options.exists("send_max")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot combine send_max with specific inputs.");
+ } else if (options.exists("inputs")) {
+ for (const CTxIn& input : rawTx.vin) {
+ if (pwallet->IsSpent(input.prevout.hash, input.prevout.n)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not available. UTXO (%s:%d) was already spent.", input.prevout.hash.ToString(), input.prevout.n));
+ }
+ const CWalletTx* tx{pwallet->GetWalletTx(input.prevout.hash)};
+ if (!tx || pwallet->IsMine(tx->tx->vout[input.prevout.n]) != (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not found. UTXO (%s:%d) is not part of wallet.", input.prevout.hash.ToString(), input.prevout.n));
+ }
+ total_input_value += tx->tx->vout[input.prevout.n].nValue;
+ }
+ } else {
+ AvailableCoins(*pwallet, all_the_utxos, &coin_control, /*nMinimumAmount=*/0);
+ for (const COutput& output : all_the_utxos) {
+ CHECK_NONFATAL(output.input_bytes > 0);
+ if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
+ continue;
+ }
+ CTxIn input(output.outpoint.hash, output.outpoint.n, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL);
+ rawTx.vin.push_back(input);
+ total_input_value += output.txout.nValue;
+ }
+ }
- UniValue result(UniValue::VOBJ);
+ // estimate final size of tx
+ const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())};
+ const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)};
+ const CAmount effective_value{total_input_value - fee_from_size};
- if (psbt_opt_in || !complete || !add_to_wallet) {
- // Serialize the PSBT
- CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
- ssTx << psbtx;
- result.pushKV("psbt", EncodeBase64(ssTx.str()));
+ if (effective_value <= 0) {
+ if (send_max) {
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Total value of UTXO pool too low to pay for transaction, try using lower feerate.");
+ } else {
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Total value of UTXO pool too low to pay for transaction. Try using lower feerate or excluding uneconomic UTXOs with 'send_max' option.");
+ }
}
- if (complete) {
- std::string err_string;
- std::string hex = EncodeHexTx(CTransaction(mtx));
- CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
- result.pushKV("txid", tx->GetHash().GetHex());
- if (add_to_wallet && !psbt_opt_in) {
- pwallet->CommitTransaction(tx, {}, {} /* orderForm */);
+ CAmount output_amounts_claimed{0};
+ for (CTxOut out : rawTx.vout) {
+ output_amounts_claimed += out.nValue;
+ }
+
+ if (output_amounts_claimed > total_input_value) {
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Assigned more value to outputs than available funds.");
+ }
+
+ const CAmount remainder{effective_value - output_amounts_claimed};
+ if (remainder < 0) {
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds for fees after creating specified outputs.");
+ }
+
+ const CAmount per_output_without_amount{remainder / (long)addresses_without_amount.size()};
+
+ bool gave_remaining_to_first{false};
+ for (CTxOut& out : rawTx.vout) {
+ CTxDestination dest;
+ ExtractDestination(out.scriptPubKey, dest);
+ std::string addr{EncodeDestination(dest)};
+ if (addresses_without_amount.count(addr) > 0) {
+ out.nValue = per_output_without_amount;
+ if (!gave_remaining_to_first) {
+ out.nValue += remainder % addresses_without_amount.size();
+ gave_remaining_to_first = true;
+ }
+ if (IsDust(out, pwallet->chain().relayDustFee())) {
+ // Dynamically generated output amount is dust
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Dynamically assigned remainder results in dust output.");
+ }
} else {
- result.pushKV("hex", hex);
+ if (IsDust(out, pwallet->chain().relayDustFee())) {
+ // Specified output amount is dust
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Specified output amount to %s is below dust threshold.", addr));
+ }
+ }
+ }
+
+ const bool lock_unspents{options.exists("lock_unspents") ? options["lock_unspents"].get_bool() : false};
+ if (lock_unspents) {
+ for (const CTxIn& txin : rawTx.vin) {
+ pwallet->LockCoin(txin.prevout);
}
}
- result.pushKV("complete", complete);
- return result;
+ return FinishTransaction(pwallet, options, rawTx);
}
};
}
@@ -1418,7 +1667,7 @@ RPCHelpMan walletcreatefundedpsbt()
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
SetOptionsInputWeights(request.params[0], options);
- FundTransaction(wallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ true);
+ FundTransaction(wallet, rawTx, fee, change_position, options, coin_control, /*override_min_fee=*/true);
// Make a blank psbt
PartiallySignedTransaction psbtx(rawTx);
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index ad94ce4b32..c87af2ea30 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -800,7 +800,7 @@ RPCHelpMan gettransaction()
if (verbose) {
UniValue decoded(UniValue::VOBJ);
- TxToUniv(*wtx.tx, uint256(), decoded, false);
+ TxToUniv(*wtx.tx, /*block_hash=*/uint256(), /*entry=*/decoded, /*include_hex=*/false);
entry.pushKV("decoded", decoded);
}
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 1380f1a77a..4baf16fdcb 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -8,6 +8,7 @@
#include <rpc/server.h>
#include <rpc/util.h>
#include <util/translation.h>
+#include <wallet/context.h>
#include <wallet/receive.h>
#include <wallet/rpc/wallet.h>
#include <wallet/rpc/util.h>
@@ -55,7 +56,7 @@ static RPCHelpMan getwalletinfo()
{
{RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
{RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
- }},
+ }, /*skip_type_check=*/true},
{RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
{RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
}},
@@ -220,6 +221,7 @@ static RPCHelpMan loadwallet()
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*context.args, options);
options.require_existing = true;
bilingual_str error;
std::vector<bilingual_str> warnings;
@@ -381,6 +383,7 @@ static RPCHelpMan createwallet()
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(*context.args, options);
options.require_create = true;
options.create_flags = flags;
options.create_passphrase = passphrase;
@@ -641,6 +644,7 @@ RPCHelpMan fundrawtransaction();
RPCHelpMan bumpfee();
RPCHelpMan psbtbumpfee();
RPCHelpMan send();
+RPCHelpMan sendall();
RPCHelpMan walletprocesspsbt();
RPCHelpMan walletcreatefundedpsbt();
RPCHelpMan signrawtransactionwithwallet();
@@ -720,6 +724,7 @@ static const CRPCCommand commands[] =
{ "wallet", &setwalletflag, },
{ "wallet", &signmessage, },
{ "wallet", &signrawtransactionwithwallet, },
+ { "wallet", &sendall, },
{ "wallet", &unloadwallet, },
{ "wallet", &upgradewallet, },
{ "wallet", &walletcreatefundedpsbt, },
diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp
index 1ecc96fe0e..9ba3c7fd2c 100644
--- a/src/wallet/salvage.cpp
+++ b/src/wallet/salvage.cpp
@@ -23,10 +23,11 @@ static bool KeyFilter(const std::string& type)
return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN;
}
-bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
+bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
DatabaseOptions options;
DatabaseStatus status;
+ ReadDatabaseArgs(args, options);
options.require_existing = true;
options.verify = false;
options.require_format = DatabaseFormat::BERKELEY;
diff --git a/src/wallet/salvage.h b/src/wallet/salvage.h
index 332aceb262..e4822c3c75 100644
--- a/src/wallet/salvage.h
+++ b/src/wallet/salvage.h
@@ -9,10 +9,11 @@
#include <fs.h>
#include <streams.h>
+class ArgsManager;
struct bilingual_str;
namespace wallet {
-bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
+bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
} // namespace wallet
#endif // BITCOIN_WALLET_SALVAGE_H
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index a707ef89d2..9e508f3a32 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -19,6 +19,8 @@
#include <wallet/transaction.h>
#include <wallet/wallet.h>
+#include <cmath>
+
using interfaces::FoundBlock;
namespace wallet {
@@ -29,11 +31,6 @@ int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out
return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig);
}
-std::string COutput::ToString() const
-{
- return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
-}
-
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
{
CMutableTransaction txn;
@@ -158,6 +155,8 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
continue;
}
+ bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL);
+
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
// Only consider selected coins if add_inputs is false
if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) {
@@ -190,8 +189,9 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
+ int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
- vCoins.push_back(COutput(wallet, wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
+ vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me);
// Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) {
@@ -218,8 +218,8 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr
std::vector<COutput> vCoins;
AvailableCoins(wallet, vCoins, coinControl);
for (const COutput& out : vCoins) {
- if (out.fSpendable) {
- balance += out.tx->tx->vout[out.i].nValue;
+ if (out.spendable) {
+ balance += out.txout.nValue;
}
}
return balance;
@@ -243,6 +243,12 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransactio
return ptx->vout[n];
}
+const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint)
+{
+ AssertLockHeld(wallet.cs_wallet);
+ return FindNonChangeParentOutput(wallet, *wallet.GetWalletTx(outpoint.hash)->tx, outpoint.n);
+}
+
std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
{
AssertLockHeld(wallet.cs_wallet);
@@ -254,8 +260,8 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
for (const COutput& coin : availableCoins) {
CTxDestination address;
- if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
- ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) {
+ if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
+ ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
result[address].emplace_back(std::move(coin));
}
}
@@ -268,14 +274,15 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
for (const COutPoint& output : lockedCoins) {
auto it = wallet.mapWallet.find(output.hash);
if (it != wallet.mapWallet.end()) {
- int depth = wallet.GetTxDepthInMainChain(it->second);
- if (depth >= 0 && output.n < it->second.tx->vout.size() &&
- wallet.IsMine(it->second.tx->vout[output.n]) == is_mine_filter
+ const auto& wtx = it->second;
+ int depth = wallet.GetTxDepthInMainChain(wtx);
+ if (depth >= 0 && output.n < wtx.tx->vout.size() &&
+ wallet.IsMine(wtx.tx->vout[output.n]) == is_mine_filter
) {
CTxDestination address;
- if (ExtractDestination(FindNonChangeParentOutput(wallet, *it->second.tx, output.n).scriptPubKey, address)) {
+ if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) {
result[address].emplace_back(
- wallet, it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */);
+ COutPoint(wtx.GetHash(), output.n), wtx.tx->vout.at(output.n), depth, GetTxSpendSize(wallet, wtx, output.n), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL));
}
}
}
@@ -292,15 +299,14 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
// Allowing partial spends means no grouping. Each COutput gets its own OutputGroup.
for (const COutput& output : outputs) {
// Skip outputs we cannot spend
- if (!output.fSpendable) continue;
+ if (!output.spendable) continue;
size_t ancestors, descendants;
- wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
- CInputCoin input_coin = output.GetInputCoin();
+ wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
// Make an OutputGroup containing just this output
OutputGroup group{coin_sel_params};
- group.Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
+ group.Insert(output, ancestors, descendants, positive_only);
// Check the OutputGroup's eligibility. Only add the eligible ones.
if (positive_only && group.GetSelectionAmount() <= 0) continue;
@@ -312,18 +318,17 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
// We want to combine COutputs that have the same scriptPubKey into single OutputGroups
// except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
// To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
- // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
+ // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added
// to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
- // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
+ // OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector.
std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
for (const auto& output : outputs) {
// Skip outputs we cannot spend
- if (!output.fSpendable) continue;
+ if (!output.spendable) continue;
size_t ancestors, descendants;
- wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
- CInputCoin input_coin = output.GetInputCoin();
- CScript spk = input_coin.txout.scriptPubKey;
+ wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
+ CScript spk = output.txout.scriptPubKey;
std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
@@ -332,7 +337,7 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
groups.emplace_back(coin_sel_params);
}
- // Get the last OutputGroup in the vector so that we can add the CInputCoin to it
+ // Get the last OutputGroup in the vector so that we can add the COutput to it
// A pointer is used here so that group can be reassigned later if it is full.
OutputGroup* group = &groups.back();
@@ -344,8 +349,8 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
group = &groups.back();
}
- // Add the input_coin to group
- group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
+ // Add the output to group
+ group->Insert(output, ancestors, descendants, positive_only);
}
// Now we go through the entire map and pull out the OutputGroups
@@ -386,15 +391,18 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
std::vector<OutputGroup> all_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */);
// While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
// So we need to include that for KnapsackSolver as well, as we are expecting to create a change output.
- if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee)}) {
+ if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee,
+ coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) {
knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
results.push_back(*knapsack_result);
}
- // We include the minimum final change for SRD as we do want to avoid making really small change.
- // KnapsackSolver does not need this because it includes MIN_CHANGE internally.
- const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + MIN_FINAL_CHANGE;
- if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target)}) {
+ // Include change for SRD as we want to avoid making really small change if the selection just
+ // barely meets the target. Just use the lower bound change target instead of the randomly
+ // generated one, since SRD will result in a random change amount anyway; avoid making the
+ // target needlessly large.
+ const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + CHANGE_LOWER;
+ if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target, coin_selection_params.rng_fast)}) {
srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
results.push_back(*srd_result);
}
@@ -421,11 +429,11 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
{
for (const COutput& out : vCoins) {
- if (!out.fSpendable) continue;
- /* Set depth, from_me, ancestors, and descendants to 0 or false as these don't matter for preset inputs as no actual selection is being done.
+ if (!out.spendable) continue;
+ /* Set ancestors and descendants to 0 as these don't matter for preset inputs as no actual selection is being done.
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
*/
- preset_inputs.Insert(out.GetInputCoin(), 0, false, 0, 0, false);
+ preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
SelectionResult result(nTargetValue);
result.AddInput(preset_inputs);
@@ -434,7 +442,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
}
// calculate value from preset inputs and store them
- std::set<CInputCoin> setPresetCoins;
+ std::set<COutPoint> preset_coins;
std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
@@ -455,34 +463,36 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
if (!coin_control.GetExternalOutput(outpoint, txout)) {
return std::nullopt;
}
- input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true);
+ input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /*use_max_sig=*/true);
}
// If available, override calculated size with coin control specified size
if (coin_control.HasInputWeight(outpoint)) {
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
}
- CInputCoin coin(outpoint, txout, input_bytes);
- if (coin.m_input_bytes == -1) {
+ if (input_bytes == -1) {
return std::nullopt; // Not solvable, can't estimate size for fee
}
- coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
+
+ /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
+ COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
+ output.effective_value = output.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(output.input_bytes);
if (coin_selection_params.m_subtract_fee_outputs) {
- value_to_select -= coin.txout.nValue;
+ value_to_select -= output.txout.nValue;
} else {
- value_to_select -= coin.effective_value;
+ value_to_select -= output.effective_value;
}
- setPresetCoins.insert(coin);
- /* Set depth, from_me, ancestors, and descendants to 0 or false as don't matter for preset inputs as no actual selection is being done.
+ preset_coins.insert(outpoint);
+ /* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
*/
- preset_inputs.Insert(coin, 0, false, 0, 0, false);
+ preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
// remove preset inputs from vCoins so that Coin Selection doesn't pick them.
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
{
- if (setPresetCoins.count(it->GetInputCoin()))
+ if (preset_coins.count(it->outpoint))
it = vCoins.erase(it);
else
++it;
@@ -501,7 +511,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
// Cases where we have 101+ outputs all pointing to the same destination may result in
// privacy leaks as they will potentially be deterministically sorted. We solve that by
// explicitly shuffling the outputs before processing
- Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
+ Shuffle(vCoins.begin(), vCoins.end(), coin_selection_params.rng_fast);
}
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
@@ -585,7 +595,8 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256&
* Set a height-based locktime for new transactions (uses the height of the
* current chain tip unless we are not synced with the current chain
*/
-static void DiscourageFeeSniping(CMutableTransaction& tx, interfaces::Chain& chain, const uint256& block_hash, int block_height)
+static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
+ interfaces::Chain& chain, const uint256& block_hash, int block_height)
{
// All inputs must be added by now
assert(!tx.vin.empty());
@@ -616,8 +627,8 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, interfaces::Chain& cha
// that transactions that are delayed after signing for whatever reason,
// e.g. high-latency mix networks and some CoinJoin implementations, have
// better privacy.
- if (GetRandInt(10) == 0) {
- tx.nLockTime = std::max(0, int(tx.nLockTime) - GetRandInt(100));
+ if (rng_fast.randrange(10) == 0) {
+ tx.nLockTime = std::max(0, int(tx.nLockTime) - int(rng_fast.randrange(100)));
}
} else {
// If our chain is lagging behind, we can't discourage fee sniping nor help
@@ -653,9 +664,10 @@ static bool CreateTransactionInternal(
{
AssertLockHeld(wallet.cs_wallet);
+ FastRandomContext rng_fast;
CMutableTransaction txNew; // The resulting transaction that we make
- CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
+ CoinSelectionParams coin_selection_params{rng_fast}; // Parameters for coin selection, init with dummy
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
// Set the long term feerate estimate to the wallet's consolidate feerate
@@ -673,6 +685,7 @@ static bool CreateTransactionInternal(
coin_selection_params.m_subtract_fee_outputs = true;
}
}
+ coin_selection_params.m_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), rng_fast);
// Create change script that will be used if we need change
CScript scriptChange;
@@ -770,7 +783,7 @@ static bool CreateTransactionInternal(
AvailableCoins(wallet, vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
// Choose coins to use
- std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /* nTargetValue */ selection_target, coin_control, coin_selection_params);
+ std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
if (!result) {
error = _("Insufficient funds");
return false;
@@ -782,10 +795,9 @@ static bool CreateTransactionInternal(
assert(change_and_fee >= 0);
CTxOut newTxOut(change_and_fee, scriptChange);
- if (nChangePosInOut == -1)
- {
+ if (nChangePosInOut == -1) {
// Insert change txn at random position:
- nChangePosInOut = GetRandInt(txNew.vout.size()+1);
+ nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1);
}
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
{
@@ -797,7 +809,7 @@ static bool CreateTransactionInternal(
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
// Shuffle selected coins and fill in final vin
- std::vector<CInputCoin> selected_coins = result->GetShuffledInputVector();
+ std::vector<COutput> selected_coins = result->GetShuffledInputVector();
// The sequence number is set to non-maxint so that DiscourageFeeSniping
// works.
@@ -811,7 +823,7 @@ static bool CreateTransactionInternal(
for (const auto& coin : selected_coins) {
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
}
- DiscourageFeeSniping(txNew, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight());
+ DiscourageFeeSniping(txNew, rng_fast, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight());
// Calculate the transaction fee
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index 4453fb2762..e43aac5273 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -11,61 +11,11 @@
#include <wallet/wallet.h>
namespace wallet {
-/** Get the marginal bytes if spending the specified output from this transaction */
+/** Get the marginal bytes if spending the specified output from this transaction.
+ * use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the
+ * size of the input spend. This should only be set when watch-only outputs are allowed */
int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false);
-class COutput
-{
-public:
- const CWalletTx *tx;
-
- /** Index in tx->vout. */
- int i;
-
- /**
- * Depth in block chain.
- * If > 0: the tx is on chain and has this many confirmations.
- * If = 0: the tx is waiting confirmation.
- * If < 0: a conflicting tx is on chain and has this many confirmations. */
- int nDepth;
-
- /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
- int nInputBytes;
-
- /** Whether we have the private keys to spend this output */
- bool fSpendable;
-
- /** Whether we know how to spend this output, ignoring the lack of keys */
- bool fSolvable;
-
- /** Whether to use the maximum sized, 72 byte signature when calculating the size of the input spend. This should only be set when watch-only outputs are allowed */
- bool use_max_sig;
-
- /**
- * Whether this output is considered safe to spend. Unconfirmed transactions
- * from outside keys and unconfirmed replacement transactions are considered
- * unsafe and will not be used to fund new spending transactions.
- */
- bool fSafe;
-
- COutput(const CWallet& wallet, const CWalletTx& wtx, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false)
- {
- tx = &wtx; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in;
- // If known and signable by the given wallet, compute nInputBytes
- // Failure will keep this value -1
- if (fSpendable) {
- nInputBytes = GetTxSpendSize(wallet, wtx, i, use_max_sig);
- }
- }
-
- std::string ToString() const;
-
- inline CInputCoin GetInputCoin() const
- {
- return CInputCoin(tx->tx, i, nInputBytes);
- }
-};
-
//Get the marginal bytes of spending the specified output
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false);
@@ -93,6 +43,7 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr
* Find non-change parent output.
*/
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
/**
* Return list of available coins and locked coins grouped by non-change output address.
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index 55949e6e7a..3f860289f9 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -83,8 +83,8 @@ static void SetPragma(sqlite3* db, const std::string& key, const std::string& va
}
}
-SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock)
- : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path))
+SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock)
+ : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path)), m_use_unsafe_sync(options.use_unsafe_sync)
{
{
LOCK(g_sqlite_mutex);
@@ -255,7 +255,7 @@ void SQLiteDatabase::Open()
// Enable fullfsync for the platforms that use it
SetPragma(m_db, "fullfsync", "true", "Failed to enable fullfsync");
- if (gArgs.GetBoolArg("-unsafesqlitesync", false)) {
+ if (m_use_unsafe_sync) {
// Use normal synchronous mode for the journal
LogPrintf("WARNING SQLite is configured to not wait for data to be flushed to disk. Data loss and corruption may occur.\n");
SetPragma(m_db, "synchronous", "OFF", "Failed to set synchronous mode to OFF");
@@ -546,7 +546,7 @@ std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const D
{
try {
fs::path data_file = SQLiteDataFile(path);
- auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file);
+ auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file, options);
if (options.verify && !db->Verify(error)) {
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h
index 3ed598d0d2..47b7ebb0ec 100644
--- a/src/wallet/sqlite.h
+++ b/src/wallet/sqlite.h
@@ -69,7 +69,7 @@ public:
SQLiteDatabase() = delete;
/** Create DB handle to real database */
- SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock = false);
+ SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock = false);
~SQLiteDatabase();
@@ -113,6 +113,7 @@ public:
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override;
sqlite3* m_db{nullptr};
+ bool m_use_unsafe_sync;
};
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index b9f12158ca..2a08c8ab57 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -28,20 +28,20 @@ BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
// we repeat those tests this many times and only complain if all iterations of the test fail
#define RANDOM_REPEATS 5
-typedef std::set<CInputCoin> CoinSet;
+typedef std::set<COutput> CoinSet;
static const CoinEligibilityFilter filter_standard(1, 6, 0);
static const CoinEligibilityFilter filter_confirmed(1, 1, 0);
static const CoinEligibilityFilter filter_standard_extra(6, 6, 0);
static int nextLockTime = 0;
-static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
+static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& set)
{
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
- set.emplace_back(MakeTransactionRef(tx), nInput);
+ set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
}
static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
@@ -50,9 +50,9 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
- CInputCoin coin(MakeTransactionRef(tx), nInput);
+ COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
OutputGroup group;
- group.Insert(coin, 1, false, 0, 0, true);
+ group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true);
result.AddInput(group);
}
@@ -62,10 +62,10 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
- CInputCoin coin(MakeTransactionRef(tx), nInput);
+ COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
coin.effective_value = nValue - fee;
- coin.m_fee = fee;
- coin.m_long_term_fee = long_term_fee;
+ coin.fee = fee;
+ coin.long_term_fee = long_term_fee;
set.insert(coin);
}
@@ -82,24 +82,13 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
assert(destination_ok);
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
}
- if (fIsFromMe) {
- // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
- // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
- tx.vin.resize(1);
- }
uint256 txid = tx.GetHash();
LOCK(wallet.cs_wallet);
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
assert(ret.second);
CWalletTx& wtx = (*ret.first).second;
- if (fIsFromMe)
- {
- wtx.m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1);
- wtx.m_is_cache_empty = false;
- }
- COutput output(wallet, wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
- coins.push_back(output);
+ coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe);
}
/** Check if SelectionResult a is equivalent to SelectionResult b.
@@ -124,11 +113,14 @@ static bool EquivalentResult(const SelectionResult& a, const SelectionResult& b)
/** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */
static bool EqualResult(const SelectionResult& a, const SelectionResult& b)
{
- std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin());
+ std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin(),
+ [](const COutput& a, const COutput& b) {
+ return a.outpoint == b.outpoint;
+ });
return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end();
}
-static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
+static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
{
utxo_pool.clear();
CAmount target = 0;
@@ -140,34 +132,31 @@ static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
return target;
}
-inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins)
-{
- static std::vector<OutputGroup> static_groups;
- static_groups.clear();
- for (auto& coin : coins) {
- static_groups.emplace_back();
- static_groups.back().Insert(coin, 0, true, 0, 0, false);
- }
- return static_groups;
-}
-
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
for (auto& coin : coins) {
static_groups.emplace_back();
- static_groups.back().Insert(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false);
+ static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
return static_groups;
}
inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& coins, CWallet& wallet, const CoinEligibilityFilter& filter)
{
- CoinSelectionParams coin_selection_params(/* change_output_size= */ 0,
- /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0),
- /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
+ FastRandomContext rand{};
+ CoinSelectionParams coin_selection_params{
+ rand,
+ /*change_output_size=*/ 0,
+ /*change_spend_size=*/ 0,
+ /*min_change_target=*/ CENT,
+ /*effective_feerate=*/ CFeeRate(0),
+ /*long_term_feerate=*/ CFeeRate(0),
+ /*discard_feerate=*/ CFeeRate(0),
+ /*tx_noinputs_size=*/ 0,
+ /*avoid_partial=*/ false,
+ };
static std::vector<OutputGroup> static_groups;
static_groups = GroupOutputs(wallet, coins, coin_selection_params, filter, /*positive_only=*/false);
return static_groups;
@@ -176,8 +165,9 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
// Branch and bound coin selection tests
BOOST_AUTO_TEST_CASE(bnb_search_test)
{
+ FastRandomContext rand{};
// Setup
- std::vector<CInputCoin> utxo_pool;
+ std::vector<COutput> utxo_pool;
SelectionResult expected_result(CAmount(0));
/////////////////////////
@@ -301,10 +291,17 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
}
// Make sure that effective value is working in AttemptSelection when BnB is used
- CoinSelectionParams coin_selection_params_bnb(/* change_output_size= */ 0,
- /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000),
- /* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000),
- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
+ CoinSelectionParams coin_selection_params_bnb{
+ rand,
+ /*change_output_size=*/ 0,
+ /*change_spend_size=*/ 0,
+ /*min_change_target=*/ 0,
+ /*effective_feerate=*/ CFeeRate(3000),
+ /*long_term_feerate=*/ CFeeRate(1000),
+ /*discard_feerate=*/ CFeeRate(1000),
+ /*tx_noinputs_size=*/ 0,
+ /*avoid_partial=*/ false,
+ };
{
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet();
@@ -315,13 +312,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
std::vector<COutput> coins;
add_coin(coins, *wallet, 1);
- coins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
+ coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
// Test fees subtracted from output:
coins.clear();
add_coin(coins, *wallet, 1 * CENT);
- coins.at(0).nInputBytes = 40;
+ coins.at(0).input_bytes = 40;
coin_selection_params_bnb.m_subtract_fee_outputs = true;
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
BOOST_CHECK(result9);
@@ -342,7 +339,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
add_coin(coins, *wallet, 2 * CENT, 6 * 24, false, 0, true);
CCoinControl coin_control;
coin_control.fAllowOtherInputs = true;
- coin_control.Select(COutPoint(coins.at(0).tx->GetHash(), coins.at(0).i));
+ coin_control.Select(coins.at(0).outpoint);
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(result10);
@@ -351,6 +348,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
BOOST_AUTO_TEST_CASE(knapsack_solver_test)
{
+ FastRandomContext rand{};
+ const auto temp1{[&rand](std::vector<OutputGroup>& g, const CAmount& v, CAmount c) { return KnapsackSolver(g, v, c, rand); }};
+ const auto KnapsackSolver{temp1};
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet();
LOCK(wallet->cs_wallet);
@@ -365,25 +365,25 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
coins.clear();
// with an empty wallet we can't even pay one cent
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
add_coin(coins, *wallet, 1*CENT, 4); // add a new 1 cent coin
// with a new 1 cent coin, we still can't find a mature 1 cent
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
// but we can find a new 1 cent
- const auto result1 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT);
+ const auto result1 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result1);
BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
add_coin(coins, *wallet, 2*CENT); // add a mature 2 cent coin
// we can't make 3 cents of mature coins
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 3 * CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 3 * CENT, CENT));
// we can make 3 cents of new coins
- const auto result2 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 3 * CENT);
+ const auto result2 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 3 * CENT, CENT);
BOOST_CHECK(result2);
BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT);
@@ -394,38 +394,38 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 38 * CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 38 * CENT, CENT));
// we can't even make 37 cents if we don't allow new coins even if they're from us
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard_extra), 38 * CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard_extra), 38 * CENT, CENT));
// but we can make 37 cents if we accept new coins from ourself
- const auto result3 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 37 * CENT);
+ const auto result3 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 37 * CENT, CENT);
BOOST_CHECK(result3);
BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 37 * CENT);
// and we can make 38 cents if we accept all new coins
- const auto result4 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 38 * CENT);
+ const auto result4 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 38 * CENT, CENT);
BOOST_CHECK(result4);
BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 38 * CENT);
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
- const auto result5 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 34 * CENT);
+ const auto result5 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 34 * CENT, CENT);
BOOST_CHECK(result5);
BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 35 * CENT); // but 35 cents is closest
BOOST_CHECK_EQUAL(result5->GetInputSet().size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
- const auto result6 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 7 * CENT);
+ const auto result6 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 7 * CENT, CENT);
BOOST_CHECK(result6);
BOOST_CHECK_EQUAL(result6->GetSelectedValue(), 7 * CENT);
BOOST_CHECK_EQUAL(result6->GetInputSet().size(), 2U);
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
- const auto result7 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 8 * CENT);
+ const auto result7 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 8 * CENT, CENT);
BOOST_CHECK(result7);
BOOST_CHECK(result7->GetSelectedValue() == 8 * CENT);
BOOST_CHECK_EQUAL(result7->GetInputSet().size(), 3U);
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
- const auto result8 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 9 * CENT);
+ const auto result8 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 9 * CENT, CENT);
BOOST_CHECK(result8);
BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 10 * CENT);
BOOST_CHECK_EQUAL(result8->GetInputSet().size(), 1U);
@@ -440,12 +440,12 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total
// check that we have 71 and not 72
- const auto result9 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 71 * CENT);
+ const auto result9 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 71 * CENT, CENT);
BOOST_CHECK(result9);
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 72 * CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 72 * CENT, CENT));
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
- const auto result10 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT);
+ const auto result10 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result10);
BOOST_CHECK_EQUAL(result10->GetSelectedValue(), 20 * CENT); // we should get 20 in one coin
BOOST_CHECK_EQUAL(result10->GetInputSet().size(), 1U);
@@ -453,7 +453,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(coins, *wallet, 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
- const auto result11 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT);
+ const auto result11 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result11);
BOOST_CHECK_EQUAL(result11->GetSelectedValue(), 18 * CENT); // we should get 18 in 3 coins
BOOST_CHECK_EQUAL(result11->GetInputSet().size(), 3U);
@@ -461,13 +461,13 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(coins, *wallet, 18*CENT); // now we have 5+6+7+8+18+20+30
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
- const auto result12 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT);
+ const auto result12 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result12);
BOOST_CHECK_EQUAL(result12->GetSelectedValue(), 18 * CENT); // we should get 18 in 1 coin
BOOST_CHECK_EQUAL(result12->GetInputSet().size(), 1U); // because in the event of a tie, the biggest coin wins
// now try making 11 cents. we should get 5+6
- const auto result13 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 11 * CENT);
+ const auto result13 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 11 * CENT, CENT);
BOOST_CHECK(result13);
BOOST_CHECK_EQUAL(result13->GetSelectedValue(), 11 * CENT);
BOOST_CHECK_EQUAL(result13->GetInputSet().size(), 2U);
@@ -477,12 +477,12 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(coins, *wallet, 2*COIN);
add_coin(coins, *wallet, 3*COIN);
add_coin(coins, *wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
- const auto result14 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 95 * CENT);
+ const auto result14 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 95 * CENT, CENT);
BOOST_CHECK(result14);
BOOST_CHECK_EQUAL(result14->GetSelectedValue(), 1 * COIN); // we should get 1 BTC in 1 coin
BOOST_CHECK_EQUAL(result14->GetInputSet().size(), 1U);
- const auto result15 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 195 * CENT);
+ const auto result15 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 195 * CENT, CENT);
BOOST_CHECK(result15);
BOOST_CHECK_EQUAL(result15->GetSelectedValue(), 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(result15->GetInputSet().size(), 1U);
@@ -490,34 +490,34 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// empty the wallet and start again, now with fractions of a cent, to test small change avoidance
coins.clear();
- add_coin(coins, *wallet, MIN_CHANGE * 1 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 2 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 3 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 4 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 5 / 10);
-
- // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
- // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
- const auto result16 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), MIN_CHANGE);
+ add_coin(coins, *wallet, CENT * 1 / 10);
+ add_coin(coins, *wallet, CENT * 2 / 10);
+ add_coin(coins, *wallet, CENT * 3 / 10);
+ add_coin(coins, *wallet, CENT * 4 / 10);
+ add_coin(coins, *wallet, CENT * 5 / 10);
+
+ // try making 1 * CENT from the 1.5 * CENT
+ // we'll get change smaller than CENT whatever happens, so can expect CENT exactly
+ const auto result16 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT, CENT);
BOOST_CHECK(result16);
- BOOST_CHECK_EQUAL(result16->GetSelectedValue(), MIN_CHANGE);
+ BOOST_CHECK_EQUAL(result16->GetSelectedValue(), CENT);
// but if we add a bigger coin, small change is avoided
- add_coin(coins, *wallet, 1111*MIN_CHANGE);
+ add_coin(coins, *wallet, 1111*CENT);
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
- const auto result17 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * MIN_CHANGE);
+ const auto result17 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result17);
- BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * MIN_CHANGE); // we should get the exact amount
+ BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * CENT); // we should get the exact amount
// if we add more small coins:
- add_coin(coins, *wallet, MIN_CHANGE * 6 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 7 / 10);
+ add_coin(coins, *wallet, CENT * 6 / 10);
+ add_coin(coins, *wallet, CENT * 7 / 10);
- // and try again to make 1.0 * MIN_CHANGE
- const auto result18 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * MIN_CHANGE);
+ // and try again to make 1.0 * CENT
+ const auto result18 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result18);
- BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * MIN_CHANGE); // we should get the exact amount
+ BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * CENT); // we should get the exact amount
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
// they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
@@ -525,52 +525,52 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
for (int j = 0; j < 20; j++)
add_coin(coins, *wallet, 50000 * COIN);
- const auto result19 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 500000 * COIN);
+ const auto result19 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 500000 * COIN, CENT);
BOOST_CHECK(result19);
BOOST_CHECK_EQUAL(result19->GetSelectedValue(), 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(result19->GetInputSet().size(), 10U); // in ten coins
- // if there's not enough in the smaller coins to make at least 1 * MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0),
+ // if there's not enough in the smaller coins to make at least 1 * CENT change (0.5+0.6+0.7 < 1.0+1.0),
// we need to try finding an exact subset anyway
// sometimes it will fail, and so we use the next biggest coin:
coins.clear();
- add_coin(coins, *wallet, MIN_CHANGE * 5 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 6 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 7 / 10);
- add_coin(coins, *wallet, 1111 * MIN_CHANGE);
- const auto result20 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * MIN_CHANGE);
+ add_coin(coins, *wallet, CENT * 5 / 10);
+ add_coin(coins, *wallet, CENT * 6 / 10);
+ add_coin(coins, *wallet, CENT * 7 / 10);
+ add_coin(coins, *wallet, 1111 * CENT);
+ const auto result20 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result20);
- BOOST_CHECK_EQUAL(result20->GetSelectedValue(), 1111 * MIN_CHANGE); // we get the bigger coin
+ BOOST_CHECK_EQUAL(result20->GetSelectedValue(), 1111 * CENT); // we get the bigger coin
BOOST_CHECK_EQUAL(result20->GetInputSet().size(), 1U);
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
coins.clear();
- add_coin(coins, *wallet, MIN_CHANGE * 4 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 6 / 10);
- add_coin(coins, *wallet, MIN_CHANGE * 8 / 10);
- add_coin(coins, *wallet, 1111 * MIN_CHANGE);
- const auto result21 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), MIN_CHANGE);
+ add_coin(coins, *wallet, CENT * 4 / 10);
+ add_coin(coins, *wallet, CENT * 6 / 10);
+ add_coin(coins, *wallet, CENT * 8 / 10);
+ add_coin(coins, *wallet, 1111 * CENT);
+ const auto result21 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT, CENT);
BOOST_CHECK(result21);
- BOOST_CHECK_EQUAL(result21->GetSelectedValue(), MIN_CHANGE); // we should get the exact amount
+ BOOST_CHECK_EQUAL(result21->GetSelectedValue(), CENT); // we should get the exact amount
BOOST_CHECK_EQUAL(result21->GetInputSet().size(), 2U); // in two coins 0.4+0.6
// test avoiding small change
coins.clear();
- add_coin(coins, *wallet, MIN_CHANGE * 5 / 100);
- add_coin(coins, *wallet, MIN_CHANGE * 1);
- add_coin(coins, *wallet, MIN_CHANGE * 100);
+ add_coin(coins, *wallet, CENT * 5 / 100);
+ add_coin(coins, *wallet, CENT * 1);
+ add_coin(coins, *wallet, CENT * 100);
// trying to make 100.01 from these three coins
- const auto result22 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), MIN_CHANGE * 10001 / 100);
+ const auto result22 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
BOOST_CHECK(result22);
- BOOST_CHECK_EQUAL(result22->GetSelectedValue(), MIN_CHANGE * 10105 / 100); // we should get all coins
+ BOOST_CHECK_EQUAL(result22->GetSelectedValue(), CENT * 10105 / 100); // we should get all coins
BOOST_CHECK_EQUAL(result22->GetInputSet().size(), 3U);
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
- const auto result23 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), MIN_CHANGE * 9990 / 100);
+ const auto result23 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
BOOST_CHECK(result23);
- BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * MIN_CHANGE);
+ BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * CENT);
BOOST_CHECK_EQUAL(result23->GetInputSet().size(), 2U);
}
@@ -583,12 +583,12 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
- const auto result24 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 2000);
+ const auto result24 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 2000, CENT);
BOOST_CHECK(result24);
- if (amt - 2000 < MIN_CHANGE) {
+ if (amt - 2000 < CENT) {
// needs more than one input:
- uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt);
+ uint16_t returnSize = std::ceil((2000.0 + CENT)/amt);
CAmount returnValue = amt * returnSize;
BOOST_CHECK_EQUAL(result24->GetSelectedValue(), returnValue);
BOOST_CHECK_EQUAL(result24->GetInputSet().size(), returnSize);
@@ -610,9 +610,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
for (int i = 0; i < RUN_TESTS; i++) {
// picking 50 from 100 coins doesn't depend on the shuffle,
// but does depend on randomness in the stochastic approximation code
- const auto result25 = KnapsackSolver(GroupCoins(coins), 50 * COIN);
+ const auto result25 = KnapsackSolver(GroupCoins(coins), 50 * COIN, CENT);
BOOST_CHECK(result25);
- const auto result26 = KnapsackSolver(GroupCoins(coins), 50 * COIN);
+ const auto result26 = KnapsackSolver(GroupCoins(coins), 50 * COIN, CENT);
BOOST_CHECK(result26);
BOOST_CHECK(!EqualResult(*result25, *result26));
@@ -623,9 +623,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice
// which will cause it to fail.
// To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail
- const auto result27 = KnapsackSolver(GroupCoins(coins), COIN);
+ const auto result27 = KnapsackSolver(GroupCoins(coins), COIN, CENT);
BOOST_CHECK(result27);
- const auto result28 = KnapsackSolver(GroupCoins(coins), COIN);
+ const auto result28 = KnapsackSolver(GroupCoins(coins), COIN, CENT);
BOOST_CHECK(result28);
if (EqualResult(*result27, *result28))
fails++;
@@ -646,9 +646,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
int fails = 0;
for (int j = 0; j < RANDOM_REPEATS; j++)
{
- const auto result29 = KnapsackSolver(GroupCoins(coins), 90 * CENT);
+ const auto result29 = KnapsackSolver(GroupCoins(coins), 90 * CENT, CENT);
BOOST_CHECK(result29);
- const auto result30 = KnapsackSolver(GroupCoins(coins), 90 * CENT);
+ const auto result30 = KnapsackSolver(GroupCoins(coins), 90 * CENT, CENT);
BOOST_CHECK(result30);
if (EqualResult(*result29, *result30))
fails++;
@@ -660,6 +660,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
{
+ FastRandomContext rand{};
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet();
LOCK(wallet->cs_wallet);
@@ -673,7 +674,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
add_coin(coins, *wallet, 1000 * COIN);
add_coin(coins, *wallet, 3 * COIN);
- const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN);
+ const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN, CENT, rand);
BOOST_CHECK(result);
BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
@@ -714,10 +715,17 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
CAmount target = rand.randrange(balance - 1000) + 1000;
// Perform selection
- CoinSelectionParams cs_params(/* change_output_size= */ 34,
- /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
- /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
- /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
+ CoinSelectionParams cs_params{
+ rand,
+ /*change_output_size=*/ 34,
+ /*change_spend_size=*/ 148,
+ /*min_change_target=*/ CENT,
+ /*effective_feerate=*/ CFeeRate(0),
+ /*long_term_feerate=*/ CFeeRate(0),
+ /*discard_feerate=*/ CFeeRate(0),
+ /*tx_noinputs_size=*/ 0,
+ /*avoid_partial=*/ false,
+ };
CCoinControl cc;
const auto result = SelectCoins(*wallet, coins, target, cc, cs_params);
BOOST_CHECK(result);
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index 35ae3707f8..fbf1e0efd3 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -19,13 +19,13 @@ static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, s
{
fs::path data_file = BDBDataFile(path);
database_filename = fs::PathToString(data_file.filename());
- return GetBerkeleyEnv(data_file.parent_path());
+ return GetBerkeleyEnv(data_file.parent_path(), false);
}
BOOST_AUTO_TEST_CASE(getwalletenv_file)
{
std::string test_name = "test_name.dat";
- const fs::path datadir = gArgs.GetDataDirNet();
+ const fs::path datadir = m_args.GetDataDirNet();
fs::path file_path = datadir / test_name;
std::ofstream f{file_path};
f.close();
@@ -39,7 +39,7 @@ BOOST_AUTO_TEST_CASE(getwalletenv_file)
BOOST_AUTO_TEST_CASE(getwalletenv_directory)
{
std::string expected_name = "wallet.dat";
- const fs::path datadir = gArgs.GetDataDirNet();
+ const fs::path datadir = m_args.GetDataDirNet();
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename);
@@ -49,8 +49,8 @@ BOOST_AUTO_TEST_CASE(getwalletenv_directory)
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple)
{
- fs::path datadir = gArgs.GetDataDirNet() / "1";
- fs::path datadir_2 = gArgs.GetDataDirNet() / "2";
+ fs::path datadir = m_args.GetDataDirNet() / "1";
+ fs::path datadir_2 = m_args.GetDataDirNet() / "2";
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename);
diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp
new file mode 100644
index 0000000000..2693b68cca
--- /dev/null
+++ b/src/wallet/test/fuzz/coinselection.cpp
@@ -0,0 +1,99 @@
+// 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.
+
+#include <policy/feerate.h>
+#include <primitives/transaction.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+#include <test/util/setup_common.h>
+#include <wallet/coinselection.h>
+
+#include <vector>
+
+namespace wallet {
+
+static void AddCoin(const CAmount& value, int n_input, int n_input_bytes, int locktime, std::vector<COutput>& coins)
+{
+ CMutableTransaction tx;
+ tx.vout.resize(n_input + 1);
+ tx.vout[n_input].nValue = value;
+ tx.nLockTime = locktime; // all transactions get different hashes
+ coins.emplace_back(COutPoint(tx.GetHash(), n_input), tx.vout.at(n_input), /*depth=*/0, n_input_bytes, /*spendable=*/true, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/true);
+}
+
+// Randomly distribute coins to instances of OutputGroup
+static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vector<COutput>& coins, const CoinSelectionParams& coin_params, bool positive_only, std::vector<OutputGroup>& output_groups)
+{
+ auto output_group = OutputGroup(coin_params);
+ bool valid_outputgroup{false};
+ for (auto& coin : coins) {
+ output_group.Insert(coin, /*ancestors=*/0, /*descendants=*/0, positive_only);
+ // If positive_only was specified, nothing may have been inserted, leading to an empty outpout group
+ // that would be invalid for the BnB algorithm
+ valid_outputgroup = !positive_only || output_group.GetSelectionAmount() > 0;
+ if (valid_outputgroup && fuzzed_data_provider.ConsumeBool()) {
+ output_groups.push_back(output_group);
+ output_group = OutputGroup(coin_params);
+ valid_outputgroup = false;
+ }
+ }
+ if (valid_outputgroup) output_groups.push_back(output_group);
+}
+
+FUZZ_TARGET(coinselection)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ std::vector<COutput> utxo_pool;
+
+ const CFeeRate long_term_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
+ const CFeeRate effective_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
+ const CAmount cost_of_change{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
+ const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};
+ const bool subtract_fee_outputs{fuzzed_data_provider.ConsumeBool()};
+
+ FastRandomContext fast_random_context{ConsumeUInt256(fuzzed_data_provider)};
+ CoinSelectionParams coin_params{fast_random_context};
+ coin_params.m_subtract_fee_outputs = subtract_fee_outputs;
+ coin_params.m_long_term_feerate = long_term_fee_rate;
+ coin_params.m_effective_feerate = effective_fee_rate;
+
+ // Create some coins
+ CAmount total_balance{0};
+ int next_locktime{0};
+ LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
+ {
+ const int n_input{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 10)};
+ const int n_input_bytes{fuzzed_data_provider.ConsumeIntegralInRange<int>(100, 10000)};
+ const CAmount amount{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};
+ if (total_balance + amount >= MAX_MONEY) {
+ break;
+ }
+ AddCoin(amount, n_input, n_input_bytes, ++next_locktime, utxo_pool);
+ total_balance += amount;
+ }
+
+ std::vector<OutputGroup> group_pos;
+ GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/true, group_pos);
+ std::vector<OutputGroup> group_all;
+ GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/false, group_all);
+
+ // Run coinselection algorithms
+ const auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change);
+
+ auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context);
+ if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change);
+
+ CAmount change_target{GenerateChangeTarget(target, fast_random_context)};
+ auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context);
+ if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change);
+
+ // If the total balance is sufficient for the target and we are not using
+ // effective values, Knapsack should always find a solution.
+ if (total_balance >= target && subtract_fee_outputs) {
+ assert(result_knapsack);
+ }
+}
+
+} // namespace wallet
diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp
index be38cebafd..34c22a9c0d 100644
--- a/src/wallet/test/init_test_fixture.cpp
+++ b/src/wallet/test/init_test_fixture.cpp
@@ -15,12 +15,12 @@
namespace wallet {
InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName) : BasicTestingSetup(chainName)
{
- m_wallet_loader = MakeWalletLoader(*m_node.chain, *Assert(m_node.args));
+ m_wallet_loader = MakeWalletLoader(*m_node.chain, m_args);
std::string sep;
sep += fs::path::preferred_separator;
- m_datadir = gArgs.GetDataDirNet();
+ m_datadir = m_args.GetDataDirNet();
m_cwd = fs::current_path();
m_walletdir_path_cases["default"] = m_datadir / "wallets";
@@ -42,14 +42,11 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainNam
InitWalletDirTestingSetup::~InitWalletDirTestingSetup()
{
- gArgs.LockSettings([&](util::Settings& settings) {
- settings.forced_settings.erase("walletdir");
- });
fs::current_path(m_cwd);
}
void InitWalletDirTestingSetup::SetWalletDir(const fs::path& walletdir_path)
{
- gArgs.ForceSetArg("-walletdir", fs::PathToString(walletdir_path));
+ m_args.ForceSetArg("-walletdir", fs::PathToString(walletdir_path));
}
} // namespace wallet
diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp
index 7fdecc5642..fb0a07e181 100644
--- a/src/wallet/test/init_tests.cpp
+++ b/src/wallet/test/init_tests.cpp
@@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default)
SetWalletDir(m_walletdir_path_cases["default"]);
bool result = m_wallet_loader->verify();
BOOST_CHECK(result == true);
- fs::path walletdir = gArgs.GetPathArg("-walletdir");
+ fs::path walletdir = m_args.GetPathArg("-walletdir");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
BOOST_CHECK_EQUAL(walletdir, expected_path);
}
@@ -28,7 +28,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom)
SetWalletDir(m_walletdir_path_cases["custom"]);
bool result = m_wallet_loader->verify();
BOOST_CHECK(result == true);
- fs::path walletdir = gArgs.GetPathArg("-walletdir");
+ fs::path walletdir = m_args.GetPathArg("-walletdir");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["custom"]);
BOOST_CHECK_EQUAL(walletdir, expected_path);
}
@@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing)
SetWalletDir(m_walletdir_path_cases["trailing"]);
bool result = m_wallet_loader->verify();
BOOST_CHECK(result == true);
- fs::path walletdir = gArgs.GetPathArg("-walletdir");
+ fs::path walletdir = m_args.GetPathArg("-walletdir");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
BOOST_CHECK_EQUAL(walletdir, expected_path);
}
@@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2)
SetWalletDir(m_walletdir_path_cases["trailing2"]);
bool result = m_wallet_loader->verify();
BOOST_CHECK(result == true);
- fs::path walletdir = gArgs.GetPathArg("-walletdir");
+ fs::path walletdir = m_args.GetPathArg("-walletdir");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
BOOST_CHECK_EQUAL(walletdir, expected_path);
}
diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp
index b953f402a2..62053ae8d2 100644
--- a/src/wallet/test/psbt_wallet_tests.cpp
+++ b/src/wallet/test/psbt_wallet_tests.cpp
@@ -68,7 +68,6 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input
SignatureData sigdata;
BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true, true) != TransactionError::OK);
- //BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK);
}
BOOST_AUTO_TEST_CASE(parse_hd_keypath)
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index cb006dea3a..38d23b6f91 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -9,6 +9,7 @@
namespace wallet {
WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
: TestingSetup(chainName),
+ m_wallet_loader{interfaces::MakeWalletLoader(*m_node.chain, *Assert(m_node.args))},
m_wallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase())
{
m_wallet.LoadWallet();
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index d4b855b145..e0b38a40e7 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.h
@@ -22,7 +22,7 @@ struct WalletTestingSetup : public TestingSetup {
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
~WalletTestingSetup();
- std::unique_ptr<interfaces::WalletLoader> m_wallet_loader = interfaces::MakeWalletLoader(*m_node.chain, *Assert(m_node.args));
+ std::unique_ptr<interfaces::WalletLoader> m_wallet_loader;
CWallet m_wallet;
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
};
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index c59f7e6f05..683f0eb327 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -54,6 +54,7 @@ static const std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context)
std::vector<bilingual_str> warnings;
auto database = MakeWalletDatabase("", options, status, error);
auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings);
+ NotifyWalletLoaded(context, wallet);
if (context.chain) {
wallet->postInitProcess();
}
@@ -221,7 +222,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
wallet->SetupLegacyScriptPubKeyMan();
WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
AddWallet(context, wallet);
UniValue keys;
keys.setArray();
@@ -277,12 +278,12 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
SetMockTime(KEY_TIME);
m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
- std::string backup_file = fs::PathToString(gArgs.GetDataDirNet() / "wallet.backup");
+ std::string backup_file = fs::PathToString(m_args.GetDataDirNet() / "wallet.backup");
// Import key into wallet and call dumpwallet to create backup file.
{
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
{
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
@@ -310,7 +311,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
wallet->SetupLegacyScriptPubKeyMan();
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
JSONRPCRequest request;
request.context = &context;
request.params.setArray();
@@ -339,7 +340,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{
CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
- CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*position_in_block=*/0}};
+ CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/0}};
LOCK(wallet.cs_wallet);
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
@@ -373,7 +374,7 @@ static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lock
block = &inserted.first->second;
block->nTime = blockTime;
block->phashBlock = &hash;
- state = TxStateConfirmed{hash, block->nHeight, /*position_in_block=*/0};
+ state = TxStateConfirmed{hash, block->nHeight, /*index=*/0};
}
return wallet.AddToWallet(MakeTransactionRef(tx), state, [&](CWalletTx& wtx, bool /* new_tx */) {
// Assign wtx.m_state to simplify test and avoid the need to simulate
@@ -540,7 +541,7 @@ public:
wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
- it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*position_in_block=*/1};
+ it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/1};
return it->second;
}
@@ -588,7 +589,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
for (const auto& group : list) {
for (const auto& coin : group.second) {
LOCK(wallet->cs_wallet);
- wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
+ wallet->LockCoin(coin.outpoint);
}
}
{
@@ -716,10 +717,10 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
//! rescanning where new transactions in new blocks could be lost.
BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
{
- gArgs.ForceSetArg("-unsafesqlitesync", "1");
+ m_args.ForceSetArg("-unsafesqlitesync", "1");
// Create new wallet with known key and unload it.
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
context.chain = m_node.chain.get();
auto wallet = TestLoadWallet(context);
CKey key;
@@ -812,7 +813,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
{
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
auto wallet = TestLoadWallet(context);
BOOST_CHECK(wallet);
UnloadWallet(std::move(wallet));
@@ -820,9 +821,9 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
{
- gArgs.ForceSetArg("-unsafesqlitesync", "1");
+ m_args.ForceSetArg("-unsafesqlitesync", "1");
WalletContext context;
- context.args = &gArgs;
+ context.args = &m_args;
context.chain = m_node.chain.get();
auto wallet = TestLoadWallet(context);
CKey key;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 261d042529..2a0653c719 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -167,6 +167,14 @@ std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, Lo
return interfaces::MakeHandler([&context, it] { LOCK(context.wallets_mutex); context.wallet_load_fns.erase(it); });
}
+void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& wallet)
+{
+ LOCK(context.wallets_mutex);
+ for (auto& load_wallet : context.wallet_load_fns) {
+ load_wallet(interfaces::MakeWallet(context, wallet));
+ }
+}
+
static Mutex g_loading_wallet_mutex;
static Mutex g_wallet_release_mutex;
static std::condition_variable g_wallet_release_cv;
@@ -232,6 +240,8 @@ std::shared_ptr<CWallet> LoadWalletInternal(WalletContext& context, const std::s
status = DatabaseStatus::FAILED_LOAD;
return nullptr;
}
+
+ NotifyWalletLoaded(context, wallet);
AddWallet(context, wallet);
wallet->postInitProcess();
@@ -348,6 +358,8 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
wallet->Lock();
}
}
+
+ NotifyWalletLoaded(context, wallet);
AddWallet(context, wallet);
wallet->postInitProcess();
@@ -366,6 +378,7 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& backup_file, const std::string& wallet_name, std::optional<bool> load_on_start, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
DatabaseOptions options;
+ ReadDatabaseArgs(*context.args, options);
options.require_existing = true;
if (!fs::exists(backup_file)) {
@@ -2904,13 +2917,6 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
}
{
- LOCK(context.wallets_mutex);
- for (auto& load_wallet : context.wallet_load_fns) {
- load_wallet(interfaces::MakeWallet(context, walletInstance));
- }
- }
-
- {
LOCK(walletInstance->cs_wallet);
walletInstance->SetBroadcastTransactions(args.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index e2c5c69c91..26b7f97b5f 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -20,7 +20,6 @@
#include <util/system.h>
#include <util/ui_change_type.h>
#include <validationinterface.h>
-#include <wallet/coinselection.h>
#include <wallet/crypter.h>
#include <wallet/scriptpubkeyman.h>
#include <wallet/transaction.h>
@@ -68,6 +67,7 @@ std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& n
std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& backup_file, const std::string& wallet_name, std::optional<bool> load_on_start, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, LoadWalletFn load_wallet);
+void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
//! -paytxfee default
@@ -95,7 +95,7 @@ static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000;
//! Default for -spendzeroconfchange
static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true;
//! Default for -walletrejectlongchains
-static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false;
+static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS{true};
//! -txconfirmtarget default
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6;
//! -walletrbf default
@@ -112,7 +112,6 @@ constexpr CAmount HIGH_MAX_TX_FEE{100 * HIGH_TX_FEE_PER_KB};
static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91;
class CCoinControl;
-class COutput;
class CWalletTx;
class ReserveDestination;
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 6e100524a4..7bbed7973f 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -850,10 +850,10 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
// Set the active ScriptPubKeyMans
for (auto spk_man_pair : wss.m_active_external_spks) {
- pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ false);
+ pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /*internal=*/false);
}
for (auto spk_man_pair : wss.m_active_internal_spks) {
- pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ true);
+ pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /*internal=*/true);
}
// Set the descriptor caches
@@ -1189,10 +1189,11 @@ std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase()
/** Return object for accessing temporary in-memory database. */
std::unique_ptr<WalletDatabase> CreateMockWalletDatabase()
{
+ DatabaseOptions options;
#ifdef USE_SQLITE
- return std::make_unique<SQLiteDatabase>("", "", true);
+ return std::make_unique<SQLiteDatabase>("", "", options, true);
#elif USE_BDB
- return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");
+ return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "", options);
#endif
}
} // namespace wallet
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 9cd18dd0a5..769175b5a8 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -140,6 +140,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
if (command == "create") {
DatabaseOptions options;
+ ReadDatabaseArgs(args, options);
options.require_create = true;
// If -legacy is set, use it. Otherwise default to false.
bool make_legacy = args.GetBoolArg("-legacy", false);
@@ -165,6 +166,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
}
} else if (command == "info") {
DatabaseOptions options;
+ ReadDatabaseArgs(args, options);
options.require_existing = true;
const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options);
if (!wallet_instance) return false;
@@ -174,7 +176,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
#ifdef USE_BDB
bilingual_str error;
std::vector<bilingual_str> warnings;
- bool ret = RecoverDatabaseFile(path, error, warnings);
+ bool ret = RecoverDatabaseFile(args, path, error, warnings);
if (!ret) {
for (const auto& warning : warnings) {
tfm::format(std::cerr, "%s\n", warning.original);
@@ -190,11 +192,12 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
#endif
} else if (command == "dump") {
DatabaseOptions options;
+ ReadDatabaseArgs(args, options);
options.require_existing = true;
const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options);
if (!wallet_instance) return false;
bilingual_str error;
- bool ret = DumpWallet(*wallet_instance, error);
+ bool ret = DumpWallet(args, *wallet_instance, error);
if (!ret && !error.empty()) {
tfm::format(std::cerr, "%s\n", error.original);
return ret;
@@ -204,7 +207,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
} else if (command == "createfromdump") {
bilingual_str error;
std::vector<bilingual_str> warnings;
- bool ret = CreateFromDump(name, path, error, warnings);
+ bool ret = CreateFromDump(args, name, path, error, warnings);
for (const auto& warning : warnings) {
tfm::format(std::cout, "%s\n", warning.original);
}
diff --git a/test/README.md b/test/README.md
index 8fffde888d..7ff2d6d9f2 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
+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
```
By default, up to 4 tests will be run in parallel by test_runner. To specify
@@ -107,6 +107,34 @@ how many jobs to run, append `--jobs=n`
The individual tests and the test_runner harness have many command-line
options. Run `test/functional/test_runner.py -h` to see them all.
+#### Speed up test runs with a ramdisk
+
+If you have available RAM on your system you can create a ramdisk to use as the `cache` and `tmp` directories for the functional tests in order to speed them up.
+Speed-up amount varies on each system (and according to your ram speed and other variables), but a 2-3x speed-up is not uncommon.
+
+To create a 4GB ramdisk on Linux at `/mnt/tmp/`:
+
+```bash
+sudo mkdir -p /mnt/tmp
+sudo mount -t tmpfs -o size=4g tmpfs /mnt/tmp/
+```
+
+Configure the size of the ramdisk using the `size=` option.
+The size of the ramdisk needed is relative to the number of concurrent jobs the test suite runs.
+For example running the test suite with `--jobs=100` might need a 4GB ramdisk, but running with `--jobs=32` will only need a 2.5GB ramdisk.
+
+To use, run the test suite specifying the ramdisk as the `cachedir` and `tmpdir`:
+
+```bash
+test/functional/test_runner.py --cachedir=/mnt/tmp/cache --tmpdir=/mnt/tmp
+```
+
+Once finished with the tests and the disk, and to free the ram, simply unmount the disk:
+
+```bash
+sudo umount /mnt/tmp
+```
+
#### Troubleshooting and debugging test failures
##### Resource contention
@@ -280,8 +308,9 @@ Use the `-v` option for verbose output.
| [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8)
| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy)
| [`lint-python.sh`](lint/lint-python.sh) | [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-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell)
+| [`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).
@@ -292,7 +321,7 @@ Please be aware that on Linux distributions all dependencies are usually availab
Individual tests can be run by directly calling the test script, e.g.:
```
-test/lint/lint-files.sh
+test/lint/lint-files.py
```
You can run all the shell-based lint tests by running:
diff --git a/test/config.ini.in b/test/config.ini.in
index 8bcba1b39c..d7105c419b 100644
--- a/test/config.ini.in
+++ b/test/config.ini.in
@@ -25,3 +25,4 @@ RPCAUTH=@abs_top_srcdir@/share/rpcauth/rpcauth.py
@ENABLE_ZMQ_TRUE@ENABLE_ZMQ=true
@ENABLE_EXTERNAL_SIGNER_TRUE@ENABLE_EXTERNAL_SIGNER=true
@ENABLE_SYSCALL_SANDBOX_TRUE@ENABLE_SYSCALL_SANDBOX=true
+@ENABLE_USDT_TRACEPOINTS_TRUE@ENABLE_USDT_TRACEPOINTS=true
diff --git a/test/functional/feature_blockfilterindex_prune.py b/test/functional/feature_blockfilterindex_prune.py
index 2451988135..c983ceda6f 100755
--- a/test/functional/feature_blockfilterindex_prune.py
+++ b/test/functional/feature_blockfilterindex_prune.py
@@ -31,7 +31,7 @@ class FeatureBlockfilterindexPruneTest(BitcoinTestFramework):
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, 248)
+ 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)
@@ -40,19 +40,19 @@ class FeatureBlockfilterindexPruneTest(BitcoinTestFramework):
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], 298)
- self.sync_index(height=998)
+ 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], 502)
+ 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, 998)
+ 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)
diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py
index c70f8a83db..f865661894 100755
--- a/test/functional/feature_coinstatsindex.py
+++ b/test/functional/feature_coinstatsindex.py
@@ -18,9 +18,6 @@ from test_framework.blocktools import (
)
from test_framework.messages import (
COIN,
- COutPoint,
- CTransaction,
- CTxIn,
CTxOut,
)
from test_framework.script import (
@@ -33,6 +30,11 @@ from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
+from test_framework.wallet import (
+ MiniWallet,
+ getnewdestination,
+)
+
class CoinStatsIndexTest(BitcoinTestFramework):
def set_test_params(self):
@@ -40,16 +42,12 @@ class CoinStatsIndexTest(BitcoinTestFramework):
self.num_nodes = 2
self.supports_cli = False
self.extra_args = [
- # Explicitly set the output type in order to have consistent tx vsize / fees
- # for both legacy and descriptor wallets (disables the change address type detection algorithm)
- ["-addresstype=bech32", "-changetype=bech32"],
+ [],
["-coinstatsindex"]
]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
self._test_coin_stats_index()
self._test_use_index_option()
self._test_reorg_index()
@@ -69,9 +67,8 @@ class CoinStatsIndexTest(BitcoinTestFramework):
index_hash_options = ['none', 'muhash']
# Generate a normal transaction and mine it
- self.generate(node, COINBASE_MATURITY + 1)
- address = self.nodes[0].get_deterministic_priv_key().address
- node.sendtoaddress(address=address, amount=10, subtractfeefromamount=True)
+ self.generate(self.wallet, COINBASE_MATURITY + 1)
+ self.wallet.send_self_transfer(from_node=node)
self.generate(node, 1)
self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")
@@ -136,36 +133,31 @@ class CoinStatsIndexTest(BitcoinTestFramework):
assert_equal(res5['block_info'], {
'unspendable': 0,
'prevout_spent': 50,
- 'new_outputs_ex_coinbase': Decimal('49.99995560'),
- 'coinbase': Decimal('50.00004440'),
+ 'new_outputs_ex_coinbase': Decimal('49.99968800'),
+ 'coinbase': Decimal('50.00031200'),
'unspendables': {
'genesis_block': 0,
'bip30': 0,
'scripts': 0,
- 'unclaimed_rewards': 0
+ 'unclaimed_rewards': 0,
}
})
self.block_sanity_check(res5['block_info'])
# Generate and send a normal tx with two outputs
- tx1_inputs = []
- tx1_outputs = {self.nodes[0].getnewaddress(): 21, self.nodes[0].getnewaddress(): 42}
- raw_tx1 = self.nodes[0].createrawtransaction(tx1_inputs, tx1_outputs)
- funded_tx1 = self.nodes[0].fundrawtransaction(raw_tx1)
- signed_tx1 = self.nodes[0].signrawtransactionwithwallet(funded_tx1['hex'])
- tx1_txid = self.nodes[0].sendrawtransaction(signed_tx1['hex'])
+ tx1_txid, tx1_vout = self.wallet.send_to(
+ from_node=node,
+ scriptPubKey=self.wallet.get_scriptPubKey(),
+ amount=21 * COIN,
+ )
# Find the right position of the 21 BTC output
- tx1_final = self.nodes[0].gettransaction(tx1_txid)
- for output in tx1_final['details']:
- if output['amount'] == Decimal('21.00000000') and output['category'] == 'receive':
- n = output['vout']
+ tx1_out_21 = self.wallet.get_utxo(txid=tx1_txid, vout=tx1_vout)
# Generate and send another tx with an OP_RETURN output (which is unspendable)
- tx2 = CTransaction()
- tx2.vin.append(CTxIn(COutPoint(int(tx1_txid, 16), n), b''))
- tx2.vout.append(CTxOut(int(Decimal('20.99') * COIN), CScript([OP_RETURN] + [OP_FALSE]*30)))
- tx2_hex = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())['hex']
+ tx2 = self.wallet.create_self_transfer(utxo_to_spend=tx1_out_21)['tx']
+ tx2.vout = [CTxOut(int(Decimal('20.99') * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))]
+ tx2_hex = tx2.serialize().hex()
self.nodes[0].sendrawtransaction(tx2_hex)
# Include both txs in a block
@@ -177,14 +169,14 @@ class CoinStatsIndexTest(BitcoinTestFramework):
assert_equal(res6['total_unspendable_amount'], Decimal('70.99000000'))
assert_equal(res6['block_info'], {
'unspendable': Decimal('20.99000000'),
- 'prevout_spent': 111,
- 'new_outputs_ex_coinbase': Decimal('89.99993620'),
- 'coinbase': Decimal('50.01006380'),
+ 'prevout_spent': 71,
+ 'new_outputs_ex_coinbase': Decimal('49.99999000'),
+ 'coinbase': Decimal('50.01001000'),
'unspendables': {
'genesis_block': 0,
'bip30': 0,
'scripts': Decimal('20.99000000'),
- 'unclaimed_rewards': 0
+ 'unclaimed_rewards': 0,
}
})
self.block_sanity_check(res6['block_info'])
@@ -246,7 +238,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
# Generate two block, let the index catch up, then invalidate the blocks
index_node = self.nodes[1]
- reorg_blocks = self.generatetoaddress(index_node, 2, index_node.getnewaddress())
+ reorg_blocks = self.generatetoaddress(index_node, 2, getnewdestination()[2])
reorg_block = reorg_blocks[1]
res_invalid = index_node.gettxoutsetinfo('muhash')
index_node.invalidateblock(reorg_blocks[0])
diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py
index 233ffd60da..422612a78e 100755
--- a/test/functional/feature_fee_estimation.py
+++ b/test/functional/feature_fee_estimation.py
@@ -3,25 +3,13 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test fee estimation code."""
+from copy import deepcopy
from decimal import Decimal
import os
import random
from test_framework.messages import (
COIN,
- COutPoint,
- CTransaction,
- CTxIn,
- CTxOut,
-)
-from test_framework.script import (
- CScript,
- OP_1,
- OP_DROP,
- OP_TRUE,
-)
-from test_framework.script_util import (
- script_to_p2sh_script,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -31,22 +19,14 @@ from test_framework.util import (
assert_raises_rpc_error,
satoshi_round,
)
-
-# Construct 2 trivial P2SH's and the ScriptSigs that spend them
-# So we can create many transactions without needing to spend
-# time signing.
-SCRIPT = CScript([OP_1, OP_DROP])
-P2SH = script_to_p2sh_script(SCRIPT)
-REDEEM_SCRIPT = CScript([OP_TRUE, SCRIPT])
+from test_framework.wallet import MiniWallet
def small_txpuzzle_randfee(
- from_node, conflist, unconflist, amount, min_fee, fee_increment
+ wallet, from_node, conflist, unconflist, amount, min_fee, fee_increment
):
- """Create and send a transaction with a random fee.
+ """Create and send a transaction with a random fee using MiniWallet.
- The transaction pays to a trivial P2SH script, and assumes that its inputs
- are of the same form.
The function takes a list of confirmed outputs and unconfirmed outputs
and attempts to use the confirmed list first for its inputs.
It adds the newly created outputs to the unconfirmed list.
@@ -58,23 +38,29 @@ def small_txpuzzle_randfee(
rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28))
# Total fee ranges from min_fee to min_fee + 127*fee_increment
fee = min_fee - fee_increment + satoshi_round(rand_fee)
- tx = CTransaction()
+ utxos_to_spend = []
total_in = Decimal("0.00000000")
while total_in <= (amount + fee) and len(conflist) > 0:
t = conflist.pop(0)
- total_in += t["amount"]
- tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT))
+ total_in += t["value"]
+ utxos_to_spend.append(t)
while total_in <= (amount + fee) and len(unconflist) > 0:
t = unconflist.pop(0)
- total_in += t["amount"]
- tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT))
+ total_in += t["value"]
+ utxos_to_spend.append(t)
if total_in <= amount + fee:
raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}")
- tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH))
- tx.vout.append(CTxOut(int(amount * COIN), P2SH))
+ tx = wallet.create_self_transfer_multi(
+ from_node=from_node,
+ utxos_to_spend=utxos_to_spend,
+ fee_per_output=0)
+ tx.vout[0].nValue = int((total_in - amount - fee) * COIN)
+ tx.vout.append(deepcopy(tx.vout[0]))
+ tx.vout[1].nValue = int(amount * COIN)
+
txid = from_node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
- unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee})
- unconflist.append({"txid": txid, "vout": 1, "amount": amount})
+ unconflist.append({"txid": txid, "vout": 0, "value": total_in - amount - fee})
+ unconflist.append({"txid": txid, "vout": 1, "value": amount})
return (tx.serialize().hex(), fee)
@@ -129,17 +115,13 @@ def check_estimates(node, fees_seen):
check_smart_estimates(node, fees_seen)
-def send_tx(node, utxo, feerate):
+def send_tx(wallet, node, utxo, feerate):
"""Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb)."""
- tx = CTransaction()
- tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), REDEEM_SCRIPT)]
- tx.vout = [CTxOut(int(utxo["amount"] * COIN), P2SH)]
-
- # vbytes == bytes as we are using legacy transactions
- fee = tx.get_vsize() * feerate
- tx.vout[0].nValue -= fee
-
- return node.sendrawtransaction(tx.serialize().hex())
+ return wallet.send_self_transfer(
+ from_node=node,
+ utxo_to_spend=utxo,
+ fee_rate=Decimal(feerate * 1000) / COIN,
+ )['txid']
class EstimateFeeTest(BitcoinTestFramework):
@@ -152,9 +134,6 @@ class EstimateFeeTest(BitcoinTestFramework):
["-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"],
]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
def setup_network(self):
"""
We'll setup the network to have 3 nodes that all mine with different parameters.
@@ -168,9 +147,6 @@ class EstimateFeeTest(BitcoinTestFramework):
# (68k weight is room enough for 120 or so transactions)
# Node2 is a stingy miner, that
# produces too small blocks (room for only 55 or so transactions)
- self.start_nodes()
- self.import_deterministic_coinbase_privkeys()
- self.stop_nodes()
def transact_and_mine(self, numblocks, mining_node):
min_fee = Decimal("0.00001")
@@ -183,6 +159,7 @@ class EstimateFeeTest(BitcoinTestFramework):
for _ in range(random.randrange(100 - 50, 100 + 50)):
from_index = random.randint(1, 2)
(txhex, fee) = small_txpuzzle_randfee(
+ self.wallet,
self.nodes[from_index],
self.confutxo,
self.memutxo,
@@ -205,24 +182,10 @@ class EstimateFeeTest(BitcoinTestFramework):
def initial_split(self, node):
"""Split two coinbase UTxOs into many small coins"""
- utxo_count = 2048
- self.confutxo = []
- splitted_amount = Decimal("0.04")
- fee = Decimal("0.1")
- change = Decimal("100") - splitted_amount * utxo_count - fee
- tx = CTransaction()
- tx.vin = [
- CTxIn(COutPoint(int(cb["txid"], 16), cb["vout"]))
- for cb in node.listunspent()[:2]
- ]
- tx.vout = [CTxOut(int(splitted_amount * COIN), P2SH) for _ in range(utxo_count)]
- tx.vout.append(CTxOut(int(change * COIN), P2SH))
- txhex = node.signrawtransactionwithwallet(tx.serialize().hex())["hex"]
- txid = node.sendrawtransaction(txhex)
- self.confutxo = [
- {"txid": txid, "vout": i, "amount": splitted_amount}
- for i in range(utxo_count)
- ]
+ self.confutxo = self.wallet.send_self_transfer_multi(
+ from_node=node,
+ utxos_to_spend=[self.wallet.get_utxo() for _ in range(2)],
+ num_outputs=2048)['new_utxos']
while len(node.getrawmempool()) > 0:
self.generate(node, 1, sync_fun=self.no_op)
@@ -284,12 +247,12 @@ class EstimateFeeTest(BitcoinTestFramework):
# Broadcast 45 low fee transactions that will need to be RBF'd
for _ in range(45):
u = utxos.pop(0)
- txid = send_tx(node, u, low_feerate)
+ txid = send_tx(self.wallet, node, u, low_feerate)
utxos_to_respend.append(u)
txids_to_replace.append(txid)
# Broadcast 5 low fee transaction which don't need to
for _ in range(5):
- send_tx(node, utxos.pop(0), low_feerate)
+ send_tx(self.wallet, node, utxos.pop(0), low_feerate)
# Mine the transactions on another node
self.sync_mempools(wait=0.1, nodes=[node, miner])
for txid in txids_to_replace:
@@ -298,7 +261,7 @@ class EstimateFeeTest(BitcoinTestFramework):
# RBF the low-fee transactions
while len(utxos_to_respend) > 0:
u = utxos_to_respend.pop(0)
- send_tx(node, u, high_feerate)
+ send_tx(self.wallet, node, u, high_feerate)
# Mine the last replacement txs
self.sync_mempools(wait=0.1, nodes=[node, miner])
@@ -316,6 +279,8 @@ class EstimateFeeTest(BitcoinTestFramework):
# Split two coinbases into many small utxos
self.start_node(0)
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.rescan_utxos()
self.initial_split(self.nodes[0])
self.log.info("Finished splitting")
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index fb0f6d7cb7..8541c3ed88 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -30,6 +30,12 @@ addnode connect to generic DNS name
addnode connect to a CJDNS address
- Test getnetworkinfo for each node
+
+- Test passing invalid -proxy
+- Test passing invalid -onion
+- Test passing invalid -i2psam
+- Test passing -onlynet=onion without -proxy or -onion
+- Test passing -onlynet=onion with -onion=0 and with -noonion
"""
import socket
@@ -234,7 +240,15 @@ class ProxyTest(BitcoinTestFramework):
return r
self.log.info("Test RPC getnetworkinfo")
- n0 = networks_dict(self.nodes[0].getnetworkinfo())
+ nodes_network_info = []
+
+ self.log.debug("Test that setting -proxy disables local address discovery, i.e. -discover=0")
+ for node in self.nodes:
+ network_info = node.getnetworkinfo()
+ assert_equal(network_info["localaddresses"], [])
+ nodes_network_info.append(network_info)
+
+ n0 = networks_dict(nodes_network_info[0])
assert_equal(NETWORKS, n0.keys())
for net in NETWORKS:
if net == NET_I2P:
@@ -249,7 +263,7 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n0['i2p']['reachable'], False)
assert_equal(n0['cjdns']['reachable'], False)
- n1 = networks_dict(self.nodes[1].getnetworkinfo())
+ n1 = networks_dict(nodes_network_info[1])
assert_equal(NETWORKS, n1.keys())
for net in ['ipv4', 'ipv6']:
assert_equal(n1[net]['proxy'], f'{self.conf1.addr[0]}:{self.conf1.addr[1]}')
@@ -261,14 +275,15 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n1['i2p']['proxy_randomize_credentials'], False)
assert_equal(n1['i2p']['reachable'], True)
- n2 = networks_dict(self.nodes[2].getnetworkinfo())
+ n2 = networks_dict(nodes_network_info[2])
assert_equal(NETWORKS, n2.keys())
+ proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}'
for net in NETWORKS:
if net == NET_I2P:
expected_proxy = ''
expected_randomize = False
else:
- expected_proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}'
+ expected_proxy = proxy
expected_randomize = True
assert_equal(n2[net]['proxy'], expected_proxy)
assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize)
@@ -277,20 +292,18 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n2['cjdns']['reachable'], False)
if self.have_ipv6:
- n3 = networks_dict(self.nodes[3].getnetworkinfo())
+ n3 = networks_dict(nodes_network_info[3])
assert_equal(NETWORKS, n3.keys())
+ proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}'
for net in NETWORKS:
- if net == NET_I2P or net == NET_ONION:
- expected_proxy = ''
- else:
- expected_proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}'
+ expected_proxy = '' if net == NET_I2P or net == NET_ONION else proxy
assert_equal(n3[net]['proxy'], expected_proxy)
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
assert_equal(n3['i2p']['reachable'], False)
assert_equal(n3['cjdns']['reachable'], False)
- n4 = networks_dict(self.nodes[4].getnetworkinfo())
+ n4 = networks_dict(nodes_network_info[4])
assert_equal(NETWORKS, n4.keys())
for net in NETWORKS:
if net == NET_I2P:
@@ -305,6 +318,37 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n4['i2p']['reachable'], False)
assert_equal(n4['cjdns']['reachable'], True)
+ self.stop_node(1)
+
+ self.log.info("Test passing invalid -proxy raises expected init error")
+ self.nodes[1].extra_args = ["-proxy=abc:def"]
+ msg = "Error: Invalid -proxy address or hostname: 'abc:def'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing invalid -onion raises expected init error")
+ self.nodes[1].extra_args = ["-onion=xyz:abc"]
+ msg = "Error: Invalid -onion address or hostname: 'xyz:abc'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing invalid -i2psam raises expected init error")
+ self.nodes[1].extra_args = ["-i2psam=def:xyz"]
+ msg = "Error: Invalid -i2psam address or hostname: 'def:xyz'"
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ msg = (
+ "Error: Outbound connections restricted to Tor (-onlynet=onion) but "
+ "the proxy for reaching the Tor network is not provided (no -proxy= "
+ "and no -onion= given) or it is explicitly forbidden (-onion=0)"
+ )
+ self.log.info("Test passing -onlynet=onion without -proxy or -onion raises expected init error")
+ self.nodes[1].extra_args = ["-onlynet=onion"]
+ self.nodes[1].assert_start_raises_init_error(expected_msg=msg)
+
+ self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error")
+ for arg in ["-onion=0", "-noonion"]:
+ self.nodes[1].extra_args = ["-onlynet=onion", arg]
+ 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 ba3c5053cb..bf19384279 100755
--- a/test/functional/feature_pruning.py
+++ b/test/functional/feature_pruning.py
@@ -141,6 +141,10 @@ class PruneTest(BitcoinTestFramework):
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'],
+ )
def test_height_min(self):
assert os.path.isfile(os.path.join(self.prunedir, "blk00000.dat")), "blk00000.dat is missing, pruning too early"
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 50adc08d9a..f0faf1421b 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -86,18 +86,18 @@ class SegWitTest(BitcoinTestFramework):
[
"-acceptnonstdtxn=1",
"-rpcserialversion=0",
- "-testactivationheight=segwit@432",
+ "-testactivationheight=segwit@165",
"-addresstype=legacy",
],
[
"-acceptnonstdtxn=1",
"-rpcserialversion=1",
- "-testactivationheight=segwit@432",
+ "-testactivationheight=segwit@165",
"-addresstype=legacy",
],
[
"-acceptnonstdtxn=1",
- "-testactivationheight=segwit@432",
+ "-testactivationheight=segwit@165",
"-addresstype=legacy",
],
]
@@ -117,10 +117,6 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(len(node.getblock(block[0])["tx"]), 2)
self.sync_blocks()
- def fail_mine(self, node, txid, sign, redeem_script=""):
- send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
- assert_raises_rpc_error(-1, "unexpected witness data found", self.generate, node, 1)
-
def fail_accept(self, node, error_msg, txid, sign, redeem_script=""):
assert_raises_rpc_error(-26, error_msg, send_to_witness, use_p2wsh=1, node=node, utxo=getutxo(txid), pubkey=self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=sign, insert_redeem_script=redeem_script)
@@ -195,23 +191,21 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(self.nodes[1].getbalance(), 20 * Decimal("49.999"))
assert_equal(self.nodes[2].getbalance(), 20 * Decimal("49.999"))
- self.generate(self.nodes[0], 264) # block 427
-
- self.log.info("Verify witness txs cannot be mined before the fork")
- self.fail_mine(self.nodes[2], wit_ids[NODE_2][P2WPKH][0], True)
- self.fail_mine(self.nodes[2], wit_ids[NODE_2][P2WSH][0], True)
- self.fail_mine(self.nodes[2], p2sh_ids[NODE_2][P2WPKH][0], True)
- self.fail_mine(self.nodes[2], p2sh_ids[NODE_2][P2WSH][0], True)
-
self.log.info("Verify unsigned p2sh witness txs without a redeem script are invalid")
self.fail_accept(self.nodes[2], "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", p2sh_ids[NODE_2][P2WPKH][1], sign=False)
self.fail_accept(self.nodes[2], "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", p2sh_ids[NODE_2][P2WSH][1], sign=False)
- self.generate(self.nodes[0], 4) # blocks 428-431
+ self.generate(self.nodes[0], 1) # block 164
+
+ self.log.info("Verify witness txs are mined as soon as segwit activates")
+
+ send_to_witness(1, self.nodes[2], getutxo(wit_ids[NODE_2][P2WPKH][0]), self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=True)
+ send_to_witness(1, self.nodes[2], getutxo(wit_ids[NODE_2][P2WSH][0]), self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=True)
+ send_to_witness(1, self.nodes[2], getutxo(p2sh_ids[NODE_2][P2WPKH][0]), self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=True)
+ send_to_witness(1, self.nodes[2], getutxo(p2sh_ids[NODE_2][P2WSH][0]), self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=True)
- self.log.info("Verify previous witness txs can now be mined")
assert_equal(len(self.nodes[2].getrawmempool()), 4)
- blockhash = self.generate(self.nodes[2], 1)[0] # block 432 (first block with new rules; 432 = 144 * 3)
+ blockhash = self.generate(self.nodes[2], 1)[0] # block 165 (first block with new rules)
assert_equal(len(self.nodes[2].getrawmempool()), 0)
segwit_tx_list = self.nodes[2].getblock(blockhash)["tx"]
assert_equal(len(segwit_tx_list), 5)
@@ -253,10 +247,10 @@ class SegWitTest(BitcoinTestFramework):
self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program was passed an empty witness)', p2sh_ids[NODE_2][P2WSH][2], sign=False, redeem_script=witness_script(True, self.pubkey[2]))
self.log.info("Verify default node can now use witness txs")
- self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WPKH][0], True) # block 432
- self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WSH][0], True) # block 433
- self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WPKH][0], True) # block 434
- self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WSH][0], True) # block 435
+ self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WPKH][0], True)
+ self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WSH][0], True)
+ self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WPKH][0], True)
+ self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WSH][0], True)
self.log.info("Verify sigops are counted in GBT with BIP141 rules after the fork")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index 3e3d4b3c77..c3925dbb00 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -1182,15 +1182,11 @@ def spenders_taproot_inactive():
]
tap = taproot_construct(pub, scripts)
- # Test that keypath spending is valid & non-standard, regardless of validity.
+ # Test that valid spending is standard.
add_spender(spenders, "inactive/keypath_valid", key=sec, tap=tap, standard=Standard.V23)
- add_spender(spenders, "inactive/keypath_invalidsig", key=sec, tap=tap, standard=False, sighash=bitflipper(default_sighash))
- add_spender(spenders, "inactive/keypath_empty", key=sec, tap=tap, standard=False, witness=[])
-
- # Same for scriptpath spending (and features like annex, leaf versions, or OP_SUCCESS don't change this)
add_spender(spenders, "inactive/scriptpath_valid", key=sec, tap=tap, leaf="pk", standard=Standard.V23, inputs=[getter("sign")])
- add_spender(spenders, "inactive/scriptpath_invalidsig", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash))
- add_spender(spenders, "inactive/scriptpath_invalidcb", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], controlblock=bitflipper(default_controlblock))
+
+ # 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))
add_spender(spenders, "inactive/scriptpath_valid_opsuccess", key=sec, tap=tap, leaf="op_success", standard=False, inputs=[getter("sign")])
diff --git a/test/functional/feature_unsupported_utxo_db.py b/test/functional/feature_unsupported_utxo_db.py
new file mode 100755
index 0000000000..1c8c08d1d8
--- /dev/null
+++ b/test/functional/feature_unsupported_utxo_db.py
@@ -0,0 +1,61 @@
+#!/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 that unsupported utxo db causes an init error.
+
+Previous releases are required by this test, see test/README.md.
+"""
+
+import shutil
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+
+class UnsupportedUtxoDbTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_previous_releases()
+
+ def setup_network(self):
+ self.add_nodes(
+ self.num_nodes,
+ versions=[
+ 140300, # Last release with previous utxo db format
+ None, # For MiniWallet, without migration code
+ ],
+ )
+
+ def run_test(self):
+ self.log.info("Create previous version (v0.14.3) utxo db")
+ self.start_node(0)
+ block = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[-1]
+ assert_equal(self.nodes[0].getbestblockhash(), block)
+ assert_equal(self.nodes[0].gettxoutsetinfo()["total_amount"], 50)
+ self.stop_nodes()
+
+ self.log.info("Check init error")
+ legacy_utxos_dir = self.nodes[0].chain_path / "chainstate"
+ legacy_blocks_dir = self.nodes[0].chain_path / "blocks"
+ recent_utxos_dir = self.nodes[1].chain_path / "chainstate"
+ recent_blocks_dir = self.nodes[1].chain_path / "blocks"
+ shutil.copytree(legacy_utxos_dir, recent_utxos_dir)
+ shutil.copytree(legacy_blocks_dir, recent_blocks_dir)
+ self.nodes[1].assert_start_raises_init_error(
+ expected_msg="Error: Unsupported chainstate database format found. "
+ "Please restart with -reindex-chainstate. "
+ "This will rebuild the chainstate database.",
+ )
+
+ self.log.info("Drop legacy utxo db")
+ self.start_node(1, extra_args=["-reindex-chainstate"])
+ assert_equal(self.nodes[1].getbestblockhash(), block)
+ assert_equal(self.nodes[1].gettxoutsetinfo()["total_amount"], 50)
+
+
+if __name__ == "__main__":
+ UnsupportedUtxoDbTest().main()
diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py
index 75180e62a2..4d486bc6f4 100755
--- a/test/functional/feature_utxo_set_hash.py
+++ b/test/functional/feature_utxo_set_hash.py
@@ -69,8 +69,8 @@ class UTXOSetHashTest(BitcoinTestFramework):
assert_equal(finalized[::-1].hex(), node_muhash)
self.log.info("Test deterministic UTXO set hash results")
- assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "3a570529b4c32e77268de1f81b903c75cc2da53c48df0d125c1e697ba7c8c7b7")
- assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "a13e0e70eb8acc786549596e3bc154623f1a5a622ba2f70715f6773ec745f435")
+ assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "f9aa4fb5ffd10489b9a6994e70ccf1de8a8bfa2d5f201d9857332e9954b0855d")
+ assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "d1725b2fe3ef43e55aa4907480aea98d406fc9e0bf8f60169e2305f1fbf5961b")
def run_test(self):
self.test_muhash_implementation()
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index a3d949c6a8..30c9e0c9cd 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -10,6 +10,7 @@ import http.client
from io import BytesIO
import json
from struct import pack, unpack
+import typing
import urllib.parse
@@ -57,14 +58,21 @@ class RESTTest (BitcoinTestFramework):
args.append("-whitelist=noban@127.0.0.1")
self.supports_cli = False
- def test_rest_request(self, uri, http_method='GET', req_type=ReqType.JSON, body='', status=200, ret_type=RetType.JSON):
+ def test_rest_request(
+ self,
+ uri: str,
+ http_method: str = 'GET',
+ req_type: ReqType = ReqType.JSON,
+ body: str = '',
+ status: int = 200,
+ ret_type: RetType = RetType.JSON,
+ query_params: typing.Dict[str, typing.Any] = None,
+ ) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
- if req_type == ReqType.JSON:
- rest_uri += '.json'
- elif req_type == ReqType.BIN:
- rest_uri += '.bin'
- elif req_type == ReqType.HEX:
- rest_uri += '.hex'
+ if req_type in ReqType:
+ rest_uri += f'.{req_type.name.lower()}'
+ if query_params:
+ rest_uri += f'?{urllib.parse.urlencode(query_params)}'
conn = http.client.HTTPConnection(self.url.hostname, self.url.port)
self.log.debug(f'{http_method} {rest_uri} {body}')
@@ -83,6 +91,8 @@ class RESTTest (BitcoinTestFramework):
elif ret_type == RetType.JSON:
return json.loads(resp.read().decode('utf-8'), parse_float=Decimal)
+ return None
+
def run_test(self):
self.url = urllib.parse.urlparse(self.nodes[0].url)
self.wallet = MiniWallet(self.nodes[0])
@@ -213,12 +223,12 @@ class RESTTest (BitcoinTestFramework):
bb_hash = self.nodes[0].getbestblockhash()
# Check result if block does not exists
- assert_equal(self.test_rest_request(f"/headers/1/{UNKNOWN_PARAM}"), [])
+ assert_equal(self.test_rest_request(f"/headers/{UNKNOWN_PARAM}", query_params={"count": 1}), [])
self.test_rest_request(f"/block/{UNKNOWN_PARAM}", status=404, ret_type=RetType.OBJ)
# Check result if block is not in the active chain
self.nodes[0].invalidateblock(bb_hash)
- assert_equal(self.test_rest_request(f'/headers/1/{bb_hash}'), [])
+ assert_equal(self.test_rest_request(f'/headers/{bb_hash}', query_params={'count': 1}), [])
self.test_rest_request(f'/block/{bb_hash}')
self.nodes[0].reconsiderblock(bb_hash)
@@ -228,7 +238,7 @@ class RESTTest (BitcoinTestFramework):
response_bytes = response.read()
# Compare with block header
- response_header = self.test_rest_request(f"/headers/1/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ)
+ response_header = self.test_rest_request(f"/headers/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ, query_params={"count": 1})
assert_equal(int(response_header.getheader('content-length')), BLOCK_HEADER_SIZE)
response_header_bytes = response_header.read()
assert_equal(response_bytes[:BLOCK_HEADER_SIZE], response_header_bytes)
@@ -240,7 +250,7 @@ class RESTTest (BitcoinTestFramework):
assert_equal(response_bytes.hex().encode(), response_hex_bytes)
# Compare with hex block header
- response_header_hex = self.test_rest_request(f"/headers/1/{bb_hash}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
+ response_header_hex = self.test_rest_request(f"/headers/{bb_hash}", req_type=ReqType.HEX, ret_type=RetType.OBJ, query_params={"count": 1})
assert_greater_than(int(response_header_hex.getheader('content-length')), BLOCK_HEADER_SIZE*2)
response_header_hex_bytes = response_header_hex.read(BLOCK_HEADER_SIZE*2)
assert_equal(response_bytes[:BLOCK_HEADER_SIZE].hex().encode(), response_header_hex_bytes)
@@ -267,7 +277,7 @@ class RESTTest (BitcoinTestFramework):
self.test_rest_request("/blockhashbyheight/", ret_type=RetType.OBJ, status=400)
# Compare with json block header
- json_obj = self.test_rest_request(f"/headers/1/{bb_hash}")
+ json_obj = self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 1})
assert_equal(len(json_obj), 1) # ensure that there is one header in the json response
assert_equal(json_obj[0]['hash'], bb_hash) # request/response hash should be the same
@@ -278,9 +288,9 @@ class RESTTest (BitcoinTestFramework):
# See if we can get 5 headers in one response
self.generate(self.nodes[1], 5)
- json_obj = self.test_rest_request(f"/headers/5/{bb_hash}")
+ json_obj = self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 5})
assert_equal(len(json_obj), 5) # now we should have 5 header objects
- json_obj = self.test_rest_request(f"/blockfilterheaders/basic/5/{bb_hash}")
+ json_obj = self.test_rest_request(f"/blockfilterheaders/basic/{bb_hash}", query_params={"count": 5})
first_filter_header = json_obj[0]
assert_equal(len(json_obj), 5) # now we should have 5 filter header objects
json_obj = self.test_rest_request(f"/blockfilter/basic/{bb_hash}")
@@ -294,7 +304,7 @@ class RESTTest (BitcoinTestFramework):
for num in ['5a', '-5', '0', '2001', '99999999999999999999999999999999999']:
assert_equal(
bytes(f'Header count is invalid or out of acceptable range (1-2000): {num}\r\n', 'ascii'),
- self.test_rest_request(f"/headers/{num}/{bb_hash}", ret_type=RetType.BYTES, status=400),
+ self.test_rest_request(f"/headers/{bb_hash}", ret_type=RetType.BYTES, status=400, query_params={"count": num}),
)
self.log.info("Test tx inclusion in the /mempool and /block URIs")
@@ -351,6 +361,15 @@ class RESTTest (BitcoinTestFramework):
json_obj = self.test_rest_request("/chaininfo")
assert_equal(json_obj['bestblockhash'], bb_hash)
+ # Compare with normal RPC getblockchaininfo response
+ blockchain_info = self.nodes[0].getblockchaininfo()
+ assert_equal(blockchain_info, json_obj)
+
+ # Test compatibility of deprecated and newer endpoints
+ self.log.info("Test compatibility of deprecated and newer endpoints")
+ assert_equal(self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 1}), self.test_rest_request(f"/headers/1/{bb_hash}"))
+ assert_equal(self.test_rest_request(f"/blockfilterheaders/basic/{bb_hash}", query_params={"count": 1}), self.test_rest_request(f"/blockfilterheaders/basic/5/{bb_hash}"))
+
if __name__ == '__main__':
RESTTest().main()
diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py
new file mode 100755
index 0000000000..9522cd8c59
--- /dev/null
+++ b/test/functional/interface_usdt_net.py
@@ -0,0 +1,171 @@
+#!/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 net:* tracepoint API interface.
+ See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net
+"""
+
+import ctypes
+from io import BytesIO
+# 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.messages import msg_version
+from test_framework.p2p import P2PInterface
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+# Tor v3 addresses are 62 chars + 6 chars for the port (':12345').
+MAX_PEER_ADDR_LENGTH = 68
+MAX_PEER_CONN_TYPE_LENGTH = 20
+MAX_MSG_TYPE_LENGTH = 20
+# We won't process messages larger than 150 byte in this test. For reading
+# larger messanges see contrib/tracing/log_raw_p2p_msgs.py
+MAX_MSG_DATA_LENGTH = 150
+
+net_tracepoints_program = """
+#include <uapi/linux/ptrace.h>
+
+#define MAX_PEER_ADDR_LENGTH {}
+#define MAX_PEER_CONN_TYPE_LENGTH {}
+#define MAX_MSG_TYPE_LENGTH {}
+#define MAX_MSG_DATA_LENGTH {}
+""".format(
+ MAX_PEER_ADDR_LENGTH,
+ MAX_PEER_CONN_TYPE_LENGTH,
+ MAX_MSG_TYPE_LENGTH,
+ MAX_MSG_DATA_LENGTH
+) + """
+#define MIN(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
+
+struct p2p_message
+{
+ u64 peer_id;
+ char peer_addr[MAX_PEER_ADDR_LENGTH];
+ char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH];
+ char msg_type[MAX_MSG_TYPE_LENGTH];
+ u64 msg_size;
+ u8 msg[MAX_MSG_DATA_LENGTH];
+};
+
+BPF_PERF_OUTPUT(inbound_messages);
+int trace_inbound_message(struct pt_regs *ctx) {
+ struct p2p_message msg = {};
+ bpf_usdt_readarg(1, ctx, &msg.peer_id);
+ bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
+ bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
+ bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
+ bpf_usdt_readarg(5, ctx, &msg.msg_size);
+ bpf_usdt_readarg_p(6, ctx, &msg.msg, MIN(msg.msg_size, MAX_MSG_DATA_LENGTH));
+ inbound_messages.perf_submit(ctx, &msg, sizeof(msg));
+ return 0;
+}
+
+BPF_PERF_OUTPUT(outbound_messages);
+int trace_outbound_message(struct pt_regs *ctx) {
+ struct p2p_message msg = {};
+ bpf_usdt_readarg(1, ctx, &msg.peer_id);
+ bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
+ bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
+ bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
+ bpf_usdt_readarg(5, ctx, &msg.msg_size);
+ bpf_usdt_readarg_p(6, ctx, &msg.msg, MIN(msg.msg_size, MAX_MSG_DATA_LENGTH));
+ outbound_messages.perf_submit(ctx, &msg, sizeof(msg));
+ return 0;
+};
+"""
+
+
+class NetTracepointTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_platform_not_linux()
+ self.skip_if_no_bitcoind_tracepoints()
+ self.skip_if_no_python_bcc()
+ self.skip_if_no_bpf_permissions()
+
+ def run_test(self):
+ # Tests the net:inbound_message and net:outbound_message tracepoints
+ # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net
+
+ class P2PMessage(ctypes.Structure):
+ _fields_ = [
+ ("peer_id", ctypes.c_uint64),
+ ("peer_addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH),
+ ("peer_conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH),
+ ("msg_type", ctypes.c_char * MAX_MSG_TYPE_LENGTH),
+ ("msg_size", ctypes.c_uint64),
+ ("msg", ctypes.c_ubyte * MAX_MSG_DATA_LENGTH),
+ ]
+
+ def __repr__(self):
+ return f"P2PMessage(peer={self.peer_id}, addr={self.peer_addr.decode('utf-8')}, conn_type={self.peer_conn_type.decode('utf-8')}, msg_type={self.msg_type.decode('utf-8')}, msg_size={self.msg_size})"
+
+ self.log.info(
+ "hook into the net:inbound_message and net:outbound_message tracepoints")
+ ctx = USDT(path=str(self.options.bitcoind))
+ ctx.enable_probe(probe="net:inbound_message",
+ fn_name="trace_inbound_message")
+ ctx.enable_probe(probe="net:outbound_message",
+ fn_name="trace_outbound_message")
+ bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0)
+
+ # The handle_* function is a ctypes callback function called from C. When
+ # we assert in the handle_* function, the AssertError doesn't propagate
+ # back to Python. The exception is ignored. We manually count and assert
+ # that the handle_* functions succeeded.
+ EXPECTED_INOUTBOUND_VERSION_MSG = 1
+ checked_inbound_version_msg = 0
+ checked_outbound_version_msg = 0
+
+ def check_p2p_message(event, inbound):
+ nonlocal checked_inbound_version_msg, checked_outbound_version_msg
+ if event.msg_type.decode("utf-8") == "version":
+ self.log.info(
+ f"check_p2p_message(): {'inbound' if inbound else 'outbound'} {event}")
+ peer = self.nodes[0].getpeerinfo()[0]
+ msg = msg_version()
+ msg.deserialize(BytesIO(bytes(event.msg[:event.msg_size])))
+ assert_equal(peer["id"], event.peer_id, peer["id"])
+ assert_equal(peer["addr"], event.peer_addr.decode("utf-8"))
+ assert_equal(peer["connection_type"],
+ event.peer_conn_type.decode("utf-8"))
+ if inbound:
+ checked_inbound_version_msg += 1
+ else:
+ checked_outbound_version_msg += 1
+
+ def handle_inbound(_, data, __):
+ event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
+ check_p2p_message(event, True)
+
+ def handle_outbound(_, data, __):
+ event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents
+ check_p2p_message(event, False)
+
+ bpf["inbound_messages"].open_perf_buffer(handle_inbound)
+ bpf["outbound_messages"].open_perf_buffer(handle_outbound)
+
+ self.log.info("connect a P2P test node to our bitcoind node")
+ test_node = P2PInterface()
+ self.nodes[0].add_p2p_connection(test_node)
+ bpf.perf_buffer_poll(timeout=200)
+
+ self.log.info(
+ "check that we got both an inbound and outbound version message")
+ assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
+ checked_inbound_version_msg)
+ assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG,
+ checked_outbound_version_msg)
+
+ bpf.cleanup()
+
+
+if __name__ == '__main__':
+ NetTracepointTest().main()
diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py
new file mode 100755
index 0000000000..0c7f351e66
--- /dev/null
+++ b/test/functional/interface_usdt_utxocache.py
@@ -0,0 +1,407 @@
+#!/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 utxocache:* tracepoint API interface.
+ See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-utxocache
+"""
+
+import ctypes
+# 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.messages import COIN
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+from test_framework.wallet import MiniWallet
+
+utxocache_changes_program = """
+#include <uapi/linux/ptrace.h>
+
+typedef signed long long i64;
+
+struct utxocache_change
+{
+ char txid[32];
+ u32 index;
+ u32 height;
+ i64 value;
+ bool is_coinbase;
+};
+
+BPF_PERF_OUTPUT(utxocache_add);
+int trace_utxocache_add(struct pt_regs *ctx) {
+ struct utxocache_change add = {};
+ bpf_usdt_readarg_p(1, ctx, &add.txid, 32);
+ bpf_usdt_readarg(2, ctx, &add.index);
+ bpf_usdt_readarg(3, ctx, &add.height);
+ bpf_usdt_readarg(4, ctx, &add.value);
+ bpf_usdt_readarg(5, ctx, &add.is_coinbase);
+ utxocache_add.perf_submit(ctx, &add, sizeof(add));
+ return 0;
+}
+
+BPF_PERF_OUTPUT(utxocache_spent);
+int trace_utxocache_spent(struct pt_regs *ctx) {
+ struct utxocache_change spent = {};
+ bpf_usdt_readarg_p(1, ctx, &spent.txid, 32);
+ bpf_usdt_readarg(2, ctx, &spent.index);
+ bpf_usdt_readarg(3, ctx, &spent.height);
+ bpf_usdt_readarg(4, ctx, &spent.value);
+ bpf_usdt_readarg(5, ctx, &spent.is_coinbase);
+ utxocache_spent.perf_submit(ctx, &spent, sizeof(spent));
+ return 0;
+}
+
+BPF_PERF_OUTPUT(utxocache_uncache);
+int trace_utxocache_uncache(struct pt_regs *ctx) {
+ struct utxocache_change uncache = {};
+ bpf_usdt_readarg_p(1, ctx, &uncache.txid, 32);
+ bpf_usdt_readarg(2, ctx, &uncache.index);
+ bpf_usdt_readarg(3, ctx, &uncache.height);
+ bpf_usdt_readarg(4, ctx, &uncache.value);
+ bpf_usdt_readarg(5, ctx, &uncache.is_coinbase);
+ utxocache_uncache.perf_submit(ctx, &uncache, sizeof(uncache));
+ return 0;
+}
+"""
+
+utxocache_flushes_program = """
+#include <uapi/linux/ptrace.h>
+
+typedef signed long long i64;
+
+struct utxocache_flush
+{
+ i64 duration;
+ u32 mode;
+ u64 size;
+ u64 memory;
+ bool for_prune;
+};
+
+BPF_PERF_OUTPUT(utxocache_flush);
+int trace_utxocache_flush(struct pt_regs *ctx) {
+ struct utxocache_flush flush = {};
+ bpf_usdt_readarg(1, ctx, &flush.duration);
+ bpf_usdt_readarg(2, ctx, &flush.mode);
+ bpf_usdt_readarg(3, ctx, &flush.size);
+ bpf_usdt_readarg(4, ctx, &flush.memory);
+ bpf_usdt_readarg(5, ctx, &flush.for_prune);
+ utxocache_flush.perf_submit(ctx, &flush, sizeof(flush));
+ return 0;
+}
+"""
+
+FLUSHMODE_NAME = {
+ 0: "NONE",
+ 1: "IF_NEEDED",
+ 2: "PERIODIC",
+ 3: "ALWAYS",
+}
+
+
+class UTXOCacheChange(ctypes.Structure):
+ _fields_ = [
+ ("txid", ctypes.c_ubyte * 32),
+ ("index", ctypes.c_uint32),
+ ("height", ctypes.c_uint32),
+ ("value", ctypes.c_uint64),
+ ("is_coinbase", ctypes.c_bool),
+ ]
+
+ def __repr__(self):
+ return f"UTXOCacheChange(outpoint={bytes(self.txid[::-1]).hex()}:{self.index}, height={self.height}, value={self.value}sat, is_coinbase={self.is_coinbase})"
+
+
+class UTXOCacheFlush(ctypes.Structure):
+ _fields_ = [
+ ("duration", ctypes.c_int64),
+ ("mode", ctypes.c_uint32),
+ ("size", ctypes.c_uint64),
+ ("memory", ctypes.c_uint64),
+ ("for_prune", ctypes.c_bool),
+ ]
+
+ def __repr__(self):
+ return f"UTXOCacheFlush(duration={self.duration}, mode={FLUSHMODE_NAME[self.mode]}, size={self.size}, memory={self.memory}, for_prune={self.for_prune})"
+
+
+class UTXOCacheTracepointTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = False
+ self.num_nodes = 1
+ self.extra_args = [["-txindex"]]
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_platform_not_linux()
+ self.skip_if_no_bitcoind_tracepoints()
+ self.skip_if_no_python_bcc()
+ self.skip_if_no_bpf_permissions()
+
+ def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
+ self.generate(self.wallet, 101)
+
+ self.test_uncache()
+ self.test_add_spent()
+ self.test_flush()
+
+ def test_uncache(self):
+ """ Tests the utxocache:uncache tracepoint API.
+ https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheuncache
+ """
+ # To trigger an UTXO uncache from the cache, we create an invalid transaction
+ # spending a not-cached, but existing UTXO. During transaction validation, this
+ # the UTXO is added to the utxo cache, but as the transaction is invalid, it's
+ # uncached again.
+ self.log.info("testing the utxocache:uncache tracepoint API")
+
+ # Retrieve the txid for the UTXO created in the first block. This UTXO is not
+ # in our UTXO cache.
+ EARLY_BLOCK_HEIGHT = 1
+ block_1_hash = self.nodes[0].getblockhash(EARLY_BLOCK_HEIGHT)
+ block_1 = self.nodes[0].getblock(block_1_hash)
+ block_1_coinbase_txid = block_1["tx"][0]
+
+ # 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.vin[0].prevout.hash = int(block_1_coinbase_txid, 16)
+
+ self.log.info("hooking into the utxocache:uncache tracepoint")
+ ctx = USDT(path=str(self.options.bitcoind))
+ ctx.enable_probe(probe="utxocache:uncache",
+ fn_name="trace_utxocache_uncache")
+ bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0)
+
+ # The handle_* function is a ctypes callback function called from C. When
+ # we assert in the handle_* function, the AssertError doesn't propagate
+ # back to Python. The exception is ignored. We manually count and assert
+ # that the handle_* functions succeeded.
+ EXPECTED_HANDLE_UNCACHE_SUCCESS = 1
+ handle_uncache_succeeds = 0
+
+ def handle_utxocache_uncache(_, data, __):
+ nonlocal handle_uncache_succeeds
+ event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
+ self.log.info(f"handle_utxocache_uncache(): {event}")
+ assert_equal(block_1_coinbase_txid, bytes(event.txid[::-1]).hex())
+ assert_equal(0, event.index) # prevout index
+ assert_equal(EARLY_BLOCK_HEIGHT, event.height)
+ assert_equal(50 * COIN, event.value)
+ assert_equal(True, event.is_coinbase)
+
+ handle_uncache_succeeds += 1
+
+ bpf["utxocache_uncache"].open_perf_buffer(handle_utxocache_uncache)
+
+ self.log.info(
+ "testmempoolaccept the invalid transaction to trigger an UTXO-cache uncache")
+ result = self.nodes[0].testmempoolaccept(
+ [invalid_tx.serialize().hex()])[0]
+ assert_equal(result["allowed"], False)
+
+ bpf.perf_buffer_poll(timeout=100)
+ bpf.cleanup()
+
+ self.log.info(
+ f"check that we successfully traced {EXPECTED_HANDLE_UNCACHE_SUCCESS} uncaches")
+ assert_equal(EXPECTED_HANDLE_UNCACHE_SUCCESS, handle_uncache_succeeds)
+
+ def test_add_spent(self):
+ """ Tests the utxocache:add utxocache:spent tracepoint API
+ See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheadd
+ and https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocachespent
+ """
+
+ self.log.info(
+ "test the utxocache:add and utxocache:spent tracepoint API")
+
+ self.log.info("create an unconfirmed transaction")
+ self.wallet.send_self_transfer(from_node=self.nodes[0])
+
+ # We mine a block to trace changes (add/spent) to the active in-memory cache
+ # of the UTXO set (see CoinsTip() of CCoinsViewCache). However, in some cases
+ # temporary clones of the active cache are made. For example, during mining with
+ # the generate RPC call, the block is first tested in TestBlockValidity(). There,
+ # a clone of the active cache is modified during a test ConnectBlock() call.
+ # These are implementation details we don't want to test here. Thus, after
+ # mining, we invalidate the block, start the tracing, and then trace the cache
+ # changes to the active utxo cache.
+ self.log.info("mine and invalidate a block that is later reconsidered")
+ block_hash = self.generate(self.wallet, 1)[0]
+ self.nodes[0].invalidateblock(block_hash)
+
+ self.log.info(
+ "hook into the utxocache:add and utxocache:spent tracepoints")
+ ctx = USDT(path=str(self.options.bitcoind))
+ ctx.enable_probe(probe="utxocache:add", fn_name="trace_utxocache_add")
+ ctx.enable_probe(probe="utxocache:spent",
+ fn_name="trace_utxocache_spent")
+ bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0)
+
+ # The handle_* function is a ctypes callback function called from C. When
+ # we assert in the handle_* function, the AssertError doesn't propagate
+ # back to Python. The exception is ignored. We manually count and assert
+ # that the handle_* functions succeeded.
+ EXPECTED_HANDLE_ADD_SUCCESS = 2
+ EXPECTED_HANDLE_SPENT_SUCCESS = 1
+ handle_add_succeeds = 0
+ handle_spent_succeeds = 0
+
+ expected_utxocache_spents = []
+ expected_utxocache_adds = []
+
+ def handle_utxocache_add(_, data, __):
+ nonlocal handle_add_succeeds
+ event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
+ self.log.info(f"handle_utxocache_add(): {event}")
+ add = expected_utxocache_adds.pop(0)
+ assert_equal(add["txid"], bytes(event.txid[::-1]).hex())
+ assert_equal(add["index"], event.index)
+ assert_equal(add["height"], event.height)
+ assert_equal(add["value"], event.value)
+ assert_equal(add["is_coinbase"], event.is_coinbase)
+ handle_add_succeeds += 1
+
+ def handle_utxocache_spent(_, data, __):
+ nonlocal handle_spent_succeeds
+ event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents
+ self.log.info(f"handle_utxocache_spent(): {event}")
+ spent = expected_utxocache_spents.pop(0)
+ assert_equal(spent["txid"], bytes(event.txid[::-1]).hex())
+ assert_equal(spent["index"], event.index)
+ assert_equal(spent["height"], event.height)
+ assert_equal(spent["value"], event.value)
+ assert_equal(spent["is_coinbase"], event.is_coinbase)
+ handle_spent_succeeds += 1
+
+ bpf["utxocache_add"].open_perf_buffer(handle_utxocache_add)
+ bpf["utxocache_spent"].open_perf_buffer(handle_utxocache_spent)
+
+ # We trigger a block re-connection. This causes changes (add/spent)
+ # to the UTXO-cache which in turn triggers the tracepoints.
+ self.log.info("reconsider the previously invalidated block")
+ self.nodes[0].reconsiderblock(block_hash)
+
+ block = self.nodes[0].getblock(block_hash, 2)
+ for (block_index, tx) in enumerate(block["tx"]):
+ for vin in tx["vin"]:
+ if "coinbase" not in vin:
+ prevout_tx = self.nodes[0].getrawtransaction(
+ vin["txid"], True)
+ prevout_tx_block = self.nodes[0].getblockheader(
+ prevout_tx["blockhash"])
+ spends_coinbase = "coinbase" in prevout_tx["vin"][0]
+ expected_utxocache_spents.append({
+ "txid": vin["txid"],
+ "index": vin["vout"],
+ "height": prevout_tx_block["height"],
+ "value": int(prevout_tx["vout"][vin["vout"]]["value"] * COIN),
+ "is_coinbase": spends_coinbase,
+ })
+ for (i, vout) in enumerate(tx["vout"]):
+ if vout["scriptPubKey"]["type"] != "nulldata":
+ expected_utxocache_adds.append({
+ "txid": tx["txid"],
+ "index": i,
+ "height": block["height"],
+ "value": int(vout["value"] * COIN),
+ "is_coinbase": block_index == 0,
+ })
+
+ assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, len(expected_utxocache_adds))
+ assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS,
+ len(expected_utxocache_spents))
+
+ bpf.perf_buffer_poll(timeout=200)
+ bpf.cleanup()
+
+ self.log.info(
+ f"check that we successfully traced {EXPECTED_HANDLE_ADD_SUCCESS} adds and {EXPECTED_HANDLE_SPENT_SUCCESS} spent")
+ assert_equal(0, len(expected_utxocache_adds))
+ assert_equal(0, len(expected_utxocache_spents))
+ assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, handle_add_succeeds)
+ assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS, handle_spent_succeeds)
+
+ def test_flush(self):
+ """ Tests the utxocache:flush tracepoint API.
+ See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheflush"""
+
+ self.log.info("test the utxocache:flush tracepoint API")
+ self.log.info("hook into the utxocache:flush tracepoint")
+ ctx = USDT(path=str(self.options.bitcoind))
+ ctx.enable_probe(probe="utxocache:flush",
+ fn_name="trace_utxocache_flush")
+ bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0)
+
+ # The handle_* function is a ctypes callback function called from C. When
+ # we assert in the handle_* function, the AssertError doesn't propagate
+ # back to Python. The exception is ignored. We manually count and assert
+ # that the handle_* functions succeeded.
+ EXPECTED_HANDLE_FLUSH_SUCCESS = 3
+ handle_flush_succeeds = 0
+ possible_cache_sizes = set()
+ expected_flushes = []
+
+ def handle_utxocache_flush(_, data, __):
+ nonlocal handle_flush_succeeds
+ event = ctypes.cast(data, ctypes.POINTER(UTXOCacheFlush)).contents
+ self.log.info(f"handle_utxocache_flush(): {event}")
+ expected = expected_flushes.pop(0)
+ assert_equal(expected["mode"], FLUSHMODE_NAME[event.mode])
+ possible_cache_sizes.remove(event.size) # fails if size not in set
+ # sanity checks only
+ assert(event.memory > 0)
+ assert(event.duration > 0)
+ handle_flush_succeeds += 1
+
+ bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)
+
+ self.log.info("stop the node to flush the UTXO cache")
+ UTXOS_IN_CACHE = 104 # might need to be changed if the eariler tests are modified
+ # A node shutdown causes two flushes. One that flushes UTXOS_IN_CACHE
+ # UTXOs and one that flushes 0 UTXOs. Normally the 0-UTXO-flush is the
+ # second flush, however it can happen that the order changes.
+ possible_cache_sizes = {UTXOS_IN_CACHE, 0}
+ flush_for_shutdown = {"mode": "ALWAYS", "for_prune": False}
+ expected_flushes.extend([flush_for_shutdown, flush_for_shutdown])
+ self.stop_node(0)
+
+ bpf.perf_buffer_poll(timeout=200)
+
+ self.log.info("check that we don't expect additional flushes")
+ assert_equal(0, len(expected_flushes))
+ assert_equal(0, len(possible_cache_sizes))
+
+ self.log.info("restart the node with -prune")
+ self.start_node(0, ["-fastprune=1", "-prune=1"])
+
+ BLOCKS_TO_MINE = 350
+ self.log.info(f"mine {BLOCKS_TO_MINE} blocks to be able to prune")
+ self.generate(self.wallet, BLOCKS_TO_MINE)
+ # we added BLOCKS_TO_MINE coinbase UTXOs to the cache
+ possible_cache_sizes = {BLOCKS_TO_MINE}
+ expected_flushes.append(
+ {"mode": "NONE", "for_prune": True, "size_fn": lambda x: x == BLOCKS_TO_MINE})
+
+ self.log.info(f"prune blockchain to trigger a flush for pruning")
+ self.nodes[0].pruneblockchain(315)
+
+ bpf.perf_buffer_poll(timeout=500)
+ bpf.cleanup()
+
+ self.log.info(
+ f"check that we don't expect additional flushes and that the handle_* function succeeded")
+ assert_equal(0, len(expected_flushes))
+ assert_equal(0, len(possible_cache_sizes))
+ assert_equal(EXPECTED_HANDLE_FLUSH_SUCCESS, handle_flush_succeeds)
+
+
+if __name__ == '__main__':
+ UTXOCacheTracepointTest().main()
diff --git a/test/functional/interface_usdt_validation.py b/test/functional/interface_usdt_validation.py
new file mode 100755
index 0000000000..d11809273b
--- /dev/null
+++ b/test/functional/interface_usdt_validation.py
@@ -0,0 +1,136 @@
+#!/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 validation:* tracepoint API interface.
+ See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-validation
+"""
+
+import ctypes
+
+# 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.address import ADDRESS_BCRT1_UNSPENDABLE
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+
+validation_blockconnected_program = """
+#include <uapi/linux/ptrace.h>
+
+typedef signed long long i64;
+
+struct connected_block
+{
+ char hash[32];
+ int height;
+ i64 transactions;
+ int inputs;
+ i64 sigops;
+ u64 duration;
+};
+
+BPF_PERF_OUTPUT(block_connected);
+int trace_block_connected(struct pt_regs *ctx) {
+ struct connected_block block = {};
+ bpf_usdt_readarg_p(1, ctx, &block.hash, 32);
+ bpf_usdt_readarg(2, ctx, &block.height);
+ bpf_usdt_readarg(3, ctx, &block.transactions);
+ bpf_usdt_readarg(4, ctx, &block.inputs);
+ bpf_usdt_readarg(5, ctx, &block.sigops);
+ bpf_usdt_readarg(6, ctx, &block.duration);
+ block_connected.perf_submit(ctx, &block, sizeof(block));
+ return 0;
+}
+"""
+
+
+class ValidationTracepointTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_platform_not_linux()
+ self.skip_if_no_bitcoind_tracepoints()
+ self.skip_if_no_python_bcc()
+ self.skip_if_no_bpf_permissions()
+
+ def run_test(self):
+ # Tests the validation:block_connected tracepoint by generating blocks
+ # and comparing the values passed in the tracepoint arguments with the
+ # blocks.
+ # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-validationblock_connected
+
+ class Block(ctypes.Structure):
+ _fields_ = [
+ ("hash", ctypes.c_ubyte * 32),
+ ("height", ctypes.c_int),
+ ("transactions", ctypes.c_int64),
+ ("inputs", ctypes.c_int),
+ ("sigops", ctypes.c_int64),
+ ("duration", ctypes.c_uint64),
+ ]
+
+ def __repr__(self):
+ return "ConnectedBlock(hash=%s height=%d, transactions=%d, inputs=%d, sigops=%d, duration=%d)" % (
+ bytes(self.hash[::-1]).hex(),
+ self.height,
+ self.transactions,
+ self.inputs,
+ self.sigops,
+ self.duration)
+
+ # The handle_* function is a ctypes callback function called from C. When
+ # we assert in the handle_* function, the AssertError doesn't propagate
+ # back to Python. The exception is ignored. We manually count and assert
+ # that the handle_* functions succeeded.
+ BLOCKS_EXPECTED = 2
+ blocks_checked = 0
+ expected_blocks = list()
+
+ self.log.info("hook into the validation:block_connected tracepoint")
+ ctx = USDT(path=str(self.options.bitcoind))
+ ctx.enable_probe(probe="validation:block_connected",
+ fn_name="trace_block_connected")
+ bpf = BPF(text=validation_blockconnected_program,
+ usdt_contexts=[ctx], debug=0)
+
+ def handle_blockconnected(_, data, __):
+ nonlocal expected_blocks, blocks_checked
+ event = ctypes.cast(data, ctypes.POINTER(Block)).contents
+ self.log.info(f"handle_blockconnected(): {event}")
+ block = expected_blocks.pop(0)
+ assert_equal(block["hash"], bytes(event.hash[::-1]).hex())
+ assert_equal(block["height"], event.height)
+ assert_equal(len(block["tx"]), event.transactions)
+ assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs)
+ assert_equal(0, event.sigops) # no sigops in coinbase tx
+ # only plausibility checks
+ assert(event.duration > 0)
+
+ blocks_checked += 1
+
+ bpf["block_connected"].open_perf_buffer(
+ handle_blockconnected)
+
+ self.log.info(f"mine {BLOCKS_EXPECTED} blocks")
+ block_hashes = self.generatetoaddress(
+ self.nodes[0], BLOCKS_EXPECTED, ADDRESS_BCRT1_UNSPENDABLE)
+ for block_hash in block_hashes:
+ expected_blocks.append(self.nodes[0].getblock(block_hash, 2))
+
+ bpf.perf_buffer_poll(timeout=200)
+ bpf.cleanup()
+
+ self.log.info(f"check that we traced {BLOCKS_EXPECTED} blocks")
+ assert_equal(BLOCKS_EXPECTED, blocks_checked)
+ assert_equal(0, len(expected_blocks))
+
+
+if __name__ == '__main__':
+ ValidationTracepointTest().main()
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 1ee12c0040..7d8d10589b 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -23,6 +23,9 @@ from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
+from test_framework.wallet import (
+ MiniWallet,
+)
from test_framework.netutil import test_ipv6_local
from io import BytesIO
from time import sleep
@@ -100,8 +103,6 @@ class ZMQTestSetupBlock:
class ZMQTest (BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
- if self.is_wallet_compiled():
- self.requires_wallet = True
# This test isn't testing txn relay/timing, so set whitelist on the
# peers for instant txn relay. This speeds up the test run time 2-3x.
self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes
@@ -111,6 +112,7 @@ class ZMQTest (BitcoinTestFramework):
self.skip_if_no_bitcoind_zmq()
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
self.ctx = zmq.Context()
try:
self.test_basic()
@@ -211,25 +213,25 @@ class ZMQTest (BitcoinTestFramework):
assert_equal([txid.hex()], self.nodes[1].getblock(hash)["tx"])
- if self.is_wallet_compiled():
- self.log.info("Wait for tx from second node")
- payment_txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
- self.sync_all()
-
- # Should receive the broadcasted txid.
- txid = hashtx.receive()
- assert_equal(payment_txid, txid.hex())
+ self.wallet.rescan_utxos()
+ self.log.info("Wait for tx from second node")
+ payment_tx = self.wallet.send_self_transfer(from_node=self.nodes[1])
+ payment_txid = payment_tx['txid']
+ self.sync_all()
+ # Should receive the broadcasted txid.
+ txid = hashtx.receive()
+ assert_equal(payment_txid, txid.hex())
- # Should receive the broadcasted raw transaction.
- hex = rawtx.receive()
- assert_equal(payment_txid, hash256_reversed(hex).hex())
+ # Should receive the broadcasted raw transaction.
+ hex = rawtx.receive()
+ assert_equal(payment_tx['wtxid'], hash256_reversed(hex).hex())
- # Mining the block with this tx should result in second notification
- # after coinbase tx notification
- self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
- hashtx.receive()
- txid = hashtx.receive()
- assert_equal(payment_txid, txid.hex())
+ # Mining the block with this tx should result in second notification
+ # after coinbase tx notification
+ self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
+ hashtx.receive()
+ txid = hashtx.receive()
+ assert_equal(payment_txid, txid.hex())
self.log.info("Test the getzmqnotifications RPC")
@@ -243,9 +245,6 @@ class ZMQTest (BitcoinTestFramework):
assert_equal(self.nodes[1].getzmqnotifications(), [])
def test_reorg(self):
- if not self.is_wallet_compiled():
- self.log.info("Skipping reorg test because wallet is disabled")
- return
address = 'tcp://127.0.0.1:28333'
@@ -256,7 +255,7 @@ class ZMQTest (BitcoinTestFramework):
self.disconnect_nodes(0, 1)
# Generate 1 block in nodes[0] with 1 mempool tx and receive all notifications
- payment_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
+ payment_txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
disconnect_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0]
disconnect_cb = self.nodes[0].getblock(disconnect_block)["tx"][0]
assert_equal(self.nodes[0].getbestblockhash(), hashblock.receive().hex())
@@ -325,126 +324,124 @@ class ZMQTest (BitcoinTestFramework):
assert_equal((self.nodes[1].getblockhash(block_count-1), "C", None), seq.receive_sequence())
assert_equal((self.nodes[1].getblockhash(block_count), "C", None), seq.receive_sequence())
- # Rest of test requires wallet functionality
- if self.is_wallet_compiled():
- self.log.info("Wait for tx from second node")
- payment_txid = self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=5.0, replaceable=True)
- self.sync_all()
- self.log.info("Testing sequence notifications with mempool sequence values")
-
- # Should receive the broadcasted txid.
- assert_equal((payment_txid, "A", seq_num), seq.receive_sequence())
- seq_num += 1
-
- self.log.info("Testing RBF notification")
- # Replace it to test eviction/addition notification
- rbf_info = self.nodes[1].bumpfee(payment_txid)
- self.sync_all()
- assert_equal((payment_txid, "R", seq_num), seq.receive_sequence())
- seq_num += 1
- assert_equal((rbf_info["txid"], "A", seq_num), seq.receive_sequence())
- seq_num += 1
-
- # Doesn't get published when mined, make a block and tx to "flush" the possibility
- # though the mempool sequence number does go up by the number of transactions
- # removed from the mempool by the block mining it.
- mempool_size = len(self.nodes[0].getrawmempool())
- c_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0]
- # Make sure the number of mined transactions matches the number of txs out of mempool
- mempool_size_delta = mempool_size - len(self.nodes[0].getrawmempool())
- assert_equal(len(self.nodes[0].getblock(c_block)["tx"])-1, mempool_size_delta)
- seq_num += mempool_size_delta
- payment_txid_2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
- self.sync_all()
- assert_equal((c_block, "C", None), seq.receive_sequence())
- assert_equal((payment_txid_2, "A", seq_num), seq.receive_sequence())
- seq_num += 1
-
- # Spot check getrawmempool results that they only show up when asked for
- assert type(self.nodes[0].getrawmempool()) is list
- assert type(self.nodes[0].getrawmempool(mempool_sequence=False)) is list
- assert "mempool_sequence" not in self.nodes[0].getrawmempool(verbose=True)
- assert_raises_rpc_error(-8, "Verbose results cannot contain mempool sequence values.", self.nodes[0].getrawmempool, True, True)
- assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], seq_num)
-
- self.log.info("Testing reorg notifications")
- # Manually invalidate the last block to test mempool re-entry
- # N.B. This part could be made more lenient in exact ordering
- # since it greatly depends on inner-workings of blocks/mempool
- # during "deep" re-orgs. Probably should "re-construct"
- # blockchain/mempool state from notifications instead.
- block_count = self.nodes[0].getblockcount()
- best_hash = self.nodes[0].getbestblockhash()
- self.nodes[0].invalidateblock(best_hash)
- sleep(2) # Bit of room to make sure transaction things happened
-
- # Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective
- # of the time they were gathered.
- assert self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] > seq_num
-
- assert_equal((best_hash, "D", None), seq.receive_sequence())
- assert_equal((rbf_info["txid"], "A", seq_num), seq.receive_sequence())
- seq_num += 1
-
- # Other things may happen but aren't wallet-deterministic so we don't test for them currently
- self.nodes[0].reconsiderblock(best_hash)
- self.generatetoaddress(self.nodes[1], 1, ADDRESS_BCRT1_UNSPENDABLE)
-
- self.log.info("Evict mempool transaction by block conflict")
- orig_txid = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True)
-
- # More to be simply mined
- more_tx = []
- for _ in range(5):
- more_tx.append(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.1))
-
- raw_tx = self.nodes[0].getrawtransaction(orig_txid)
- bump_info = self.nodes[0].bumpfee(orig_txid)
- # Mine the pre-bump tx
- txs_to_add = [raw_tx] + [self.nodes[0].getrawtransaction(txid) for txid in more_tx]
- block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1), txlist=txs_to_add)
- add_witness_commitment(block)
- block.solve()
- assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None)
- tip = self.nodes[0].getbestblockhash()
- assert_equal(int(tip, 16), block.sha256)
- orig_txid_2 = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True)
-
- # Flush old notifications until evicted tx original entry
+ self.log.info("Wait for tx from second node")
+ payment_tx = self.wallet.send_self_transfer(from_node=self.nodes[1])
+ payment_txid = payment_tx['txid']
+ self.sync_all()
+ self.log.info("Testing sequence notifications with mempool sequence values")
+
+ # Should receive the broadcasted txid.
+ assert_equal((payment_txid, "A", seq_num), seq.receive_sequence())
+ seq_num += 1
+
+ self.log.info("Testing RBF notification")
+ # Replace it to test eviction/addition notification
+ payment_tx['tx'].vout[0].nValue -= 1000
+ rbf_txid = self.nodes[1].sendrawtransaction(payment_tx['tx'].serialize().hex())
+ self.sync_all()
+ assert_equal((payment_txid, "R", seq_num), seq.receive_sequence())
+ seq_num += 1
+ assert_equal((rbf_txid, "A", seq_num), seq.receive_sequence())
+ seq_num += 1
+
+ # Doesn't get published when mined, make a block and tx to "flush" the possibility
+ # though the mempool sequence number does go up by the number of transactions
+ # removed from the mempool by the block mining it.
+ mempool_size = len(self.nodes[0].getrawmempool())
+ c_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0]
+ # Make sure the number of mined transactions matches the number of txs out of mempool
+ mempool_size_delta = mempool_size - len(self.nodes[0].getrawmempool())
+ assert_equal(len(self.nodes[0].getblock(c_block)["tx"])-1, mempool_size_delta)
+ seq_num += mempool_size_delta
+ payment_txid_2 = self.wallet.send_self_transfer(from_node=self.nodes[1])['txid']
+ self.sync_all()
+ assert_equal((c_block, "C", None), seq.receive_sequence())
+ assert_equal((payment_txid_2, "A", seq_num), seq.receive_sequence())
+ seq_num += 1
+
+ # Spot check getrawmempool results that they only show up when asked for
+ assert type(self.nodes[0].getrawmempool()) is list
+ assert type(self.nodes[0].getrawmempool(mempool_sequence=False)) is list
+ assert "mempool_sequence" not in self.nodes[0].getrawmempool(verbose=True)
+ assert_raises_rpc_error(-8, "Verbose results cannot contain mempool sequence values.", self.nodes[0].getrawmempool, True, True)
+ assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], seq_num)
+
+ self.log.info("Testing reorg notifications")
+ # Manually invalidate the last block to test mempool re-entry
+ # N.B. This part could be made more lenient in exact ordering
+ # since it greatly depends on inner-workings of blocks/mempool
+ # during "deep" re-orgs. Probably should "re-construct"
+ # blockchain/mempool state from notifications instead.
+ block_count = self.nodes[0].getblockcount()
+ best_hash = self.nodes[0].getbestblockhash()
+ self.nodes[0].invalidateblock(best_hash)
+ sleep(2) # Bit of room to make sure transaction things happened
+
+ # Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective
+ # of the time they were gathered.
+ assert self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] > seq_num
+
+ assert_equal((best_hash, "D", None), seq.receive_sequence())
+ assert_equal((rbf_txid, "A", seq_num), seq.receive_sequence())
+ seq_num += 1
+
+ # Other things may happen but aren't wallet-deterministic so we don't test for them currently
+ self.nodes[0].reconsiderblock(best_hash)
+ self.generatetoaddress(self.nodes[1], 1, ADDRESS_BCRT1_UNSPENDABLE)
+
+ self.log.info("Evict mempool transaction by block conflict")
+ orig_tx = self.wallet.send_self_transfer(from_node=self.nodes[0])
+ orig_txid = orig_tx['txid']
+
+ # More to be simply mined
+ more_tx = []
+ for _ in range(5):
+ more_tx.append(self.wallet.send_self_transfer(from_node=self.nodes[0]))
+
+ orig_tx['tx'].vout[0].nValue -= 1000
+ bump_txid = self.nodes[0].sendrawtransaction(orig_tx['tx'].serialize().hex())
+ # Mine the pre-bump tx
+ txs_to_add = [orig_tx['hex']] + [tx['hex'] for tx in more_tx]
+ block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1), txlist=txs_to_add)
+ add_witness_commitment(block)
+ block.solve()
+ assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None)
+ tip = self.nodes[0].getbestblockhash()
+ assert_equal(int(tip, 16), block.sha256)
+ orig_txid_2 = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
+
+ # Flush old notifications until evicted tx original entry
+ (hash_str, label, mempool_seq) = seq.receive_sequence()
+ while hash_str != orig_txid:
(hash_str, label, mempool_seq) = seq.receive_sequence()
- while hash_str != orig_txid:
- (hash_str, label, mempool_seq) = seq.receive_sequence()
- mempool_seq += 1
+ mempool_seq += 1
- # Added original tx
- assert_equal(label, "A")
- # More transactions to be simply mined
- for i in range(len(more_tx)):
- assert_equal((more_tx[i], "A", mempool_seq), seq.receive_sequence())
- mempool_seq += 1
- # Bumped by rbf
- assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence())
- mempool_seq += 1
- assert_equal((bump_info["txid"], "A", mempool_seq), seq.receive_sequence())
+ # Added original tx
+ assert_equal(label, "A")
+ # More transactions to be simply mined
+ for i in range(len(more_tx)):
+ assert_equal((more_tx[i]['txid'], "A", mempool_seq), seq.receive_sequence())
mempool_seq += 1
- # Conflict announced first, then block
- assert_equal((bump_info["txid"], "R", mempool_seq), seq.receive_sequence())
- mempool_seq += 1
- assert_equal((tip, "C", None), seq.receive_sequence())
- mempool_seq += len(more_tx)
- # Last tx
- assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence())
- mempool_seq += 1
- self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
- self.sync_all() # want to make sure we didn't break "consensus" for other tests
+ # Bumped by rbf
+ assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence())
+ mempool_seq += 1
+ assert_equal((bump_txid, "A", mempool_seq), seq.receive_sequence())
+ mempool_seq += 1
+ # Conflict announced first, then block
+ assert_equal((bump_txid, "R", mempool_seq), seq.receive_sequence())
+ mempool_seq += 1
+ assert_equal((tip, "C", None), seq.receive_sequence())
+ mempool_seq += len(more_tx)
+ # Last tx
+ assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence())
+ mempool_seq += 1
+ self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
+ self.sync_all() # want to make sure we didn't break "consensus" for other tests
def test_mempool_sync(self):
"""
Use sequence notification plus getrawmempool sequence results to "sync mempool"
"""
- if not self.is_wallet_compiled():
- self.log.info("Skipping mempool sync test")
- return
self.log.info("Testing 'mempool sync' usage of sequence notifier")
[seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")])
@@ -455,10 +452,10 @@ class ZMQTest (BitcoinTestFramework):
# Some transactions have been happening but we aren't consuming zmq notifications yet
# or we lost a ZMQ message somehow and want to start over
- txids = []
+ txs = []
num_txs = 5
for _ in range(num_txs):
- txids.append(self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True))
+ txs.append(self.wallet.send_self_transfer(from_node=self.nodes[1]))
self.sync_all()
# 1) Consume backlog until we get a mempool sequence number
@@ -484,11 +481,12 @@ class ZMQTest (BitcoinTestFramework):
# Things continue to happen in the "interim" while waiting for snapshot results
# We have node 0 do all these to avoid p2p races with RBF announcements
for _ in range(num_txs):
- txids.append(self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=0.1, replaceable=True))
- self.nodes[0].bumpfee(txids[-1])
+ txs.append(self.wallet.send_self_transfer(from_node=self.nodes[0]))
+ txs[-1]['tx'].vout[0].nValue -= 1000
+ self.nodes[0].sendrawtransaction(txs[-1]['tx'].serialize().hex())
self.sync_all()
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
- final_txid = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=0.1, replaceable=True)
+ final_txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid']
# 3) Consume ZMQ backlog until we get to "now" for the mempool snapshot
while True:
diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py
index a6fb1dcf35..423a5bf2ee 100755
--- a/test/functional/mempool_package_onemore.py
+++ b/test/functional/mempool_package_onemore.py
@@ -7,74 +7,68 @@
size.
"""
-from decimal import Decimal
-
-from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- chain_transaction,
)
+from test_framework.wallet import MiniWallet
+
MAX_ANCESTORS = 25
MAX_DESCENDANTS = 25
+
class MempoolPackagesTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [["-maxorphantx=1000"]]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ def chain_tx(self, utxos_to_spend, *, num_outputs=1):
+ return self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ utxos_to_spend=utxos_to_spend,
+ num_outputs=num_outputs)['new_utxos']
def run_test(self):
- # Mine some blocks and have them mature.
- self.generate(self.nodes[0], COINBASE_MATURITY + 1)
- utxo = self.nodes[0].listunspent(10)
- txid = utxo[0]['txid']
- vout = utxo[0]['vout']
- value = utxo[0]['amount']
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.rescan_utxos()
- fee = Decimal("0.0002")
# MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = []
+ utxo = self.wallet.get_utxo()
for _ in range(4):
- (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2)
- vout = 0
- value = sent_value
- chain.append([txid, value])
+ utxo, utxo2 = self.chain_tx([utxo], num_outputs=2)
+ chain.append(utxo2)
for _ in range(MAX_ANCESTORS - 4):
- (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1)
- value = sent_value
- chain.append([txid, value])
- (second_chain, second_chain_value) = chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1)
+ utxo, = self.chain_tx([utxo])
+ chain.append(utxo)
+ second_chain, = self.chain_tx([self.wallet.get_utxo()])
# Check mempool has MAX_ANCESTORS + 1 transactions in it
assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 1)
# Adding one more transaction on to the chain should fail.
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", chain_transaction, self.nodes[0], [txid], [0], value, fee, 1)
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_tx, [utxo])
# ...even if it chains on from some point in the middle of the chain.
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1)
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1)
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[2]])
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[1]])
# ...even if it chains on to two parent transactions with one in the chain.
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1)
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0], second_chain])
# ...especially if its > 40k weight
- assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350)
+ assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0]], num_outputs=350)
# But not if it chains directly off the first transaction
- (replacable_txid, replacable_orig_value) = chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1)
+ replacable_tx = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], utxos_to_spend=[chain[0]])['tx']
# and the second chain should work just fine
- chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1)
+ self.chain_tx([second_chain])
# Make sure we can RBF the chain which used our carve-out rule
- second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['address']: replacable_orig_value - (Decimal(1) / Decimal(100))}
- second_tx = self.nodes[0].createrawtransaction([{'txid': chain[0][0], 'vout': 1}], second_tx_outputs)
- signed_second_tx = self.nodes[0].signrawtransactionwithwallet(second_tx)
- self.nodes[0].sendrawtransaction(signed_second_tx['hex'])
+ replacable_tx.vout[0].nValue -= 1000000
+ self.nodes[0].sendrawtransaction(replacable_tx.serialize().hex())
# Finally, check that we added two transactions
assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 3)
+
if __name__ == '__main__':
MempoolPackagesTest().main()
diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py
index adf7326dac..37ef4a9157 100755
--- a/test/functional/mempool_unbroadcast.py
+++ b/test/functional/mempool_unbroadcast.py
@@ -9,21 +9,20 @@ import time
from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import (
- assert_equal,
- create_confirmed_utxos,
-)
+from test_framework.util import assert_equal
+from test_framework.wallet import MiniWallet
MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # 15 minutes in seconds
class MempoolUnbroadcastTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ if self.is_wallet_compiled():
+ self.requires_wallet = True
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.rescan_utxos()
self.test_broadcast()
self.test_txn_removal()
@@ -31,30 +30,25 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
self.log.info("Test that mempool reattempts delivery of locally submitted transaction")
node = self.nodes[0]
- min_relay_fee = node.getnetworkinfo()["relayfee"]
- utxos = create_confirmed_utxos(self, min_relay_fee, node, 10)
-
self.disconnect_nodes(0, 1)
self.log.info("Generate transactions that only node 0 knows about")
- # generate a wallet txn
- addr = node.getnewaddress()
- wallet_tx_hsh = node.sendtoaddress(addr, 0.0001)
+ if self.is_wallet_compiled():
+ # generate a wallet txn
+ addr = node.getnewaddress()
+ wallet_tx_hsh = node.sendtoaddress(addr, 0.0001)
# generate a txn using sendrawtransaction
- us0 = utxos.pop()
- inputs = [{"txid": us0["txid"], "vout": us0["vout"]}]
- outputs = {addr: 0.0001}
- tx = node.createrawtransaction(inputs, outputs)
- node.settxfee(min_relay_fee)
- txF = node.fundrawtransaction(tx)
- txFS = node.signrawtransactionwithwallet(txF["hex"])
+ txFS = self.wallet.create_self_transfer(from_node=node)
rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
# check transactions are in unbroadcast using rpc
mempoolinfo = self.nodes[0].getmempoolinfo()
- assert_equal(mempoolinfo['unbroadcastcount'], 2)
+ unbroadcast_count = 1
+ if self.is_wallet_compiled():
+ unbroadcast_count += 1
+ assert_equal(mempoolinfo['unbroadcastcount'], unbroadcast_count)
mempool = self.nodes[0].getrawmempool(True)
for tx in mempool:
assert_equal(mempool[tx]['unbroadcast'], True)
@@ -62,7 +56,8 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
# check that second node doesn't have these two txns
mempool = self.nodes[1].getrawmempool()
assert rpc_tx_hsh not in mempool
- assert wallet_tx_hsh not in mempool
+ if self.is_wallet_compiled():
+ assert wallet_tx_hsh not in mempool
# ensure that unbroadcast txs are persisted to mempool.dat
self.restart_node(0)
@@ -75,7 +70,8 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
self.sync_mempools(timeout=30)
mempool = self.nodes[1].getrawmempool()
assert rpc_tx_hsh in mempool
- assert wallet_tx_hsh in mempool
+ if self.is_wallet_compiled():
+ assert wallet_tx_hsh in mempool
# check that transactions are no longer in first node's unbroadcast set
mempool = self.nodes[0].getrawmempool(True)
@@ -102,8 +98,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework):
# since the node doesn't have any connections, it will not receive
# any GETDATAs & thus the transaction will remain in the unbroadcast set.
- addr = node.getnewaddress()
- txhsh = node.sendtoaddress(addr, 0.0001)
+ txhsh = self.wallet.send_self_transfer(from_node=node)["txid"]
# check transaction was removed from unbroadcast set due to presence in
# a block
diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py
index 6f2ac805a0..a15fbe5a24 100755
--- a/test/functional/mining_prioritisetransaction.py
+++ b/test/functional/mining_prioritisetransaction.py
@@ -4,11 +4,15 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the prioritisetransaction mining RPC."""
+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.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.wallet import MiniWallet
+
class PrioritiseTransactionTest(BitcoinTestFramework):
def set_test_params(self):
@@ -23,7 +27,84 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
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)
+
+ # tx_a
+ # / \
+ # / \
+ # tx_b tx_c
+ # \ /
+ # \ /
+ # tx_d
+
+ tx_o_a = self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ num_outputs=2,
+ )
+ txid_a = tx_o_a["txid"]
+
+ tx_o_b, tx_o_c = [self.wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=u,
+ ) for u in tx_o_a["new_utxos"]]
+ txid_b = tx_o_b["txid"]
+ txid_c = tx_o_c["txid"]
+
+ tx_o_d = self.wallet.send_self_transfer_multi(
+ from_node=self.nodes[0],
+ utxos_to_spend=[
+ self.wallet.get_utxo(txid=txid_b),
+ self.wallet.get_utxo(txid=txid_c),
+ ],
+ )
+ txid_d = tx_o_d["txid"]
+
+ self.log.info("Test priority while txs are in mempool")
+ raw_before = self.nodes[0].getrawmempool(verbose=True)
+ fee_delta_b = Decimal(9999) / COIN
+ fee_delta_c_1 = Decimal(-1234) / COIN
+ fee_delta_c_2 = Decimal(8888) / COIN
+ self.nodes[0].prioritisetransaction(txid=txid_b, fee_delta=int(fee_delta_b * COIN))
+ self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_1 * COIN))
+ self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_2 * COIN))
+ raw_before[txid_a]["fees"]["descendant"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2
+ raw_before[txid_b]["fees"]["modified"] += fee_delta_b
+ raw_before[txid_b]["fees"]["ancestor"] += fee_delta_b
+ raw_before[txid_b]["fees"]["descendant"] += fee_delta_b
+ raw_before[txid_c]["fees"]["modified"] += fee_delta_c_1 + fee_delta_c_2
+ raw_before[txid_c]["fees"]["ancestor"] += fee_delta_c_1 + fee_delta_c_2
+ raw_before[txid_c]["fees"]["descendant"] += fee_delta_c_1 + fee_delta_c_2
+ raw_before[txid_d]["fees"]["ancestor"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2
+ raw_after = self.nodes[0].getrawmempool(verbose=True)
+ assert_equal(raw_before[txid_a], raw_after[txid_a])
+ assert_equal(raw_before, raw_after)
+
+ self.log.info("Test priority while txs are not in mempool")
+ self.restart_node(0, extra_args=["-nopersistmempool"])
+ self.nodes[0].setmocktime(mock_time)
+ assert_equal(self.nodes[0].getmempoolinfo()["size"], 0)
+ self.nodes[0].prioritisetransaction(txid=txid_b, fee_delta=int(fee_delta_b * COIN))
+ self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_1 * COIN))
+ self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_2 * COIN))
+ for t in [tx_o_a["hex"], tx_o_b["hex"], tx_o_c["hex"], tx_o_d["hex"]]:
+ self.nodes[0].sendrawtransaction(t)
+ raw_after = self.nodes[0].getrawmempool(verbose=True)
+ assert_equal(raw_before[txid_a], raw_after[txid_a])
+ assert_equal(raw_before, raw_after)
+
+ # Clear mempool
+ self.generate(self.nodes[0], 1)
+
+ # Use default extra_args
+ self.restart_node(0)
+
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
+
# Test `prioritisetransaction` required parameters
assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction)
assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction, '')
@@ -44,6 +125,8 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
# Test `prioritisetransaction` invalid `fee_delta`
assert_raises_rpc_error(-1, "JSON value is not an integer as expected", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo')
+ self.test_diamond()
+
self.txouts = gen_return_txouts()
self.relayfee = self.nodes[0].getnetworkinfo()['relayfee']
diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py
index e73fad439f..e45cef65bd 100755
--- a/test/functional/p2p_blockfilters.py
+++ b/test/functional/p2p_blockfilters.py
@@ -244,6 +244,12 @@ class CompactFiltersTest(BitcoinTestFramework):
peer_0.send_message(request)
peer_0.wait_for_disconnect()
+ self.log.info("Test -peerblockfilters without -blockfilterindex raises an error")
+ self.stop_node(0)
+ self.nodes[0].extra_args = ["-peerblockfilters"]
+ msg = "Error: Cannot set -peerblockfilters without -blockfilterindex."
+ self.nodes[0].assert_start_raises_init_error(expected_msg=msg)
+
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 6f142f23f2..12ee4b3c27 100755
--- a/test/functional/p2p_blocksonly.py
+++ b/test/functional/p2p_blocksonly.py
@@ -94,7 +94,7 @@ class P2PBlocksOnly(BitcoinTestFramework):
self.nodes[0].sendrawtransaction(tx_hex)
- # Bump time forward to ensure nNextInvSend timer pops
+ # Bump time forward to ensure m_next_inv_send_time timer pops
self.nodes[0].setmocktime(int(time.time()) + 60)
conn.sync_send_with_ping()
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 1a3d14100f..1695acaaa8 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -18,15 +18,18 @@ from test_framework.util import (
assert_equal,
)
from test_framework.wallet_util import bytes_to_wif
+from test_framework.wallet import (
+ MiniWallet,
+ getnewdestination,
+)
class RpcCreateMultiSigTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.supports_cli = False
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ if self.is_bdb_compiled():
+ self.requires_wallet = True
def get_keys(self):
self.pub = []
@@ -37,15 +40,20 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
k.generate()
self.pub.append(k.get_pubkey().get_bytes().hex())
self.priv.append(bytes_to_wif(k.get_bytes(), k.is_compressed))
- self.final = node2.getnewaddress()
+ if self.is_bdb_compiled():
+ self.final = node2.getnewaddress()
+ else:
+ self.final = getnewdestination()[2]
def run_test(self):
node0, node1, node2 = self.nodes
+ self.wallet = MiniWallet(test_node=node0)
- self.check_addmultisigaddress_errors()
+ if self.is_bdb_compiled():
+ self.check_addmultisigaddress_errors()
self.log.info('Generating blocks ...')
- self.generate(node0, 149)
+ self.generate(self.wallet, 149)
self.moved = 0
for self.nkeys in [3, 5]:
@@ -53,14 +61,14 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
for self.output_type in ["bech32", "p2sh-segwit", "legacy"]:
self.get_keys()
self.do_multisig()
-
- self.checkbalances()
+ if self.is_bdb_compiled():
+ self.checkbalances()
# Test mixed compressed and uncompressed pubkeys
self.log.info('Mixed compressed and uncompressed multisigs are not allowed')
- pk0 = node0.getaddressinfo(node0.getnewaddress())['pubkey']
- pk1 = node1.getaddressinfo(node1.getnewaddress())['pubkey']
- pk2 = node2.getaddressinfo(node2.getnewaddress())['pubkey']
+ pk0 = getnewdestination()[0].hex()
+ pk1 = getnewdestination()[0].hex()
+ pk2 = getnewdestination()[0].hex()
# decompress pk2
pk_obj = ECPubKey()
@@ -68,26 +76,30 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
pk_obj.compressed = False
pk2 = pk_obj.get_bytes().hex()
- node0.createwallet(wallet_name='wmulti0', disable_private_keys=True)
- wmulti0 = node0.get_wallet_rpc('wmulti0')
+ if self.is_bdb_compiled():
+ node0.createwallet(wallet_name='wmulti0', disable_private_keys=True)
+ wmulti0 = node0.get_wallet_rpc('wmulti0')
# Check all permutations of keys because order matters apparently
for keys in itertools.permutations([pk0, pk1, pk2]):
# Results should be the same as this legacy one
legacy_addr = node0.createmultisig(2, keys, 'legacy')['address']
- result = wmulti0.addmultisigaddress(2, keys, '', 'legacy')
- assert_equal(legacy_addr, result['address'])
- assert 'warnings' not in result
+
+ if self.is_bdb_compiled():
+ result = wmulti0.addmultisigaddress(2, keys, '', 'legacy')
+ assert_equal(legacy_addr, result['address'])
+ assert 'warnings' not in result
# Generate addresses with the segwit types. These should all make legacy addresses
for addr_type in ['bech32', 'p2sh-segwit']:
- result = wmulti0.createmultisig(2, keys, addr_type)
+ result = self.nodes[0].createmultisig(2, keys, 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."])
- result = wmulti0.addmultisigaddress(2, keys, '', 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."])
+ if self.is_bdb_compiled():
+ result = wmulti0.addmultisigaddress(2, keys, '', 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."])
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:
@@ -126,26 +138,29 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
bal0 = node0.getbalance()
bal1 = node1.getbalance()
bal2 = node2.getbalance()
+ balw = self.wallet.get_balance()
height = node0.getblockchaininfo()["blocks"]
assert 150 < height < 350
total = 149 * 50 + (height - 149 - 100) * 25
assert bal1 == 0
assert bal2 == self.moved
- assert bal0 + bal1 + bal2 == total
+ assert_equal(bal0 + bal1 + bal2 + balw, total)
def do_multisig(self):
node0, node1, node2 = self.nodes
- if 'wmulti' not in node1.listwallets():
- try:
- node1.loadwallet('wmulti')
- except JSONRPCException as e:
- path = os.path.join(self.options.tmpdir, "node1", "regtest", "wallets", "wmulti")
- if e.error['code'] == -18 and "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path) in e.error['message']:
- node1.createwallet(wallet_name='wmulti', disable_private_keys=True)
- else:
- raise
- wmulti = node1.get_wallet_rpc('wmulti')
+
+ if self.is_bdb_compiled():
+ if 'wmulti' not in node1.listwallets():
+ try:
+ node1.loadwallet('wmulti')
+ except JSONRPCException as e:
+ path = os.path.join(self.options.tmpdir, "node1", "regtest", "wallets", "wmulti")
+ if e.error['code'] == -18 and "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path) in e.error['message']:
+ node1.createwallet(wallet_name='wmulti', disable_private_keys=True)
+ else:
+ raise
+ wmulti = node1.get_wallet_rpc('wmulti')
# Construct the expected descriptor
desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub))
@@ -164,17 +179,19 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
if self.output_type == 'bech32':
assert madd[0:4] == "bcrt" # actually a bech32 address
- # compare against addmultisigaddress
- msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
- maddw = msigw["address"]
- mredeemw = msigw["redeemScript"]
- assert_equal(desc, drop_origins(msigw['descriptor']))
- # addmultisigiaddress and createmultisig work the same
- assert maddw == madd
- assert mredeemw == mredeem
-
- txid = node0.sendtoaddress(madd, 40)
-
+ if self.is_bdb_compiled():
+ # compare against addmultisigaddress
+ msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
+ maddw = msigw["address"]
+ mredeemw = msigw["redeemScript"]
+ assert_equal(desc, drop_origins(msigw['descriptor']))
+ # addmultisigiaddress and createmultisig work the same
+ assert maddw == madd
+ assert mredeemw == mredeem
+ wmulti.unloadwallet()
+
+ spk = bytes.fromhex(node0.validateaddress(madd)["scriptPubKey"])
+ txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=spk, amount=1300)
tx = node0.getrawtransaction(txid, True)
vout = [v["n"] for v in tx["vout"] if madd == v["scriptPubKey"]["address"]]
assert len(vout) == 1
@@ -225,8 +242,6 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
txinfo = node0.getrawtransaction(tx, True, blk)
self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"]))
- wmulti.unloadwallet()
-
if __name__ == '__main__':
RpcCreateMultiSigTest().main()
diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py
index 1721b6ffe8..4ca84748b2 100755
--- a/test/functional/rpc_dumptxoutset.py
+++ b/test/functional/rpc_dumptxoutset.py
@@ -37,16 +37,16 @@ class DumptxoutsetTest(BitcoinTestFramework):
# Blockhash should be deterministic based on mocked time.
assert_equal(
out['base_hash'],
- '6fd417acba2a8738b06fee43330c50d58e6a725046c3d843c8dd7e51d46d1ed6')
+ '09abf0e7b510f61ca6cf33bab104e9ee99b3528b371d27a2d4b39abb800fba7e')
with open(str(expected_path), 'rb') as f:
digest = hashlib.sha256(f.read()).hexdigest()
# UTXO snapshot hash should be deterministic based on mocked time.
assert_equal(
- digest, '7ae82c986fa5445678d2a21453bb1c86d39e47af13da137640c2b1cf8093691c')
+ digest, 'b1bacb602eacf5fbc9a7c2ef6eeb0d229c04e98bdf0c2ea5929012cd0eae3830')
assert_equal(
- out['txoutset_hash'], 'd4b614f476b99a6e569973bf1c0120d88b1a168076f8ce25691fb41dd1cef149')
+ out['txoutset_hash'], '1f7e3befd45dc13ae198dfbb22869a9c5c4196f8e9ef9735831af1288033f890')
assert_equal(out['nchaintx'], 101)
# Specifying a path to an existing file will fail.
diff --git a/test/functional/rpc_generate.py b/test/functional/rpc_generate.py
index 47d7814da3..2b1dd20ea1 100755
--- a/test/functional/rpc_generate.py
+++ b/test/functional/rpc_generate.py
@@ -2,9 +2,10 @@
# 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 generate RPC."""
+"""Test generate* RPCs."""
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.wallet import MiniWallet
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
@@ -16,6 +17,94 @@ class RPCGenerateTest(BitcoinTestFramework):
self.num_nodes = 1
def run_test(self):
+ self.test_generatetoaddress()
+ self.test_generate()
+ self.test_generateblock()
+
+ def test_generatetoaddress(self):
+ self.generatetoaddress(self.nodes[0], 1, 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ')
+ assert_raises_rpc_error(-5, "Invalid address", self.generatetoaddress, self.nodes[0], 1, '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy')
+
+ def test_generateblock(self):
+ node = self.nodes[0]
+ miniwallet = MiniWallet(node)
+ miniwallet.rescan_utxos()
+
+ self.log.info('Generate an empty block to address')
+ address = miniwallet.get_address()
+ hash = self.generateblock(node, output=address, transactions=[])['hash']
+ block = node.getblock(blockhash=hash, verbose=2)
+ assert_equal(len(block['tx']), 1)
+ assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address)
+
+ self.log.info('Generate an empty block to a descriptor')
+ hash = self.generateblock(node, 'addr(' + address + ')', [])['hash']
+ block = node.getblock(blockhash=hash, verbosity=2)
+ assert_equal(len(block['tx']), 1)
+ assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address)
+
+ self.log.info('Generate an empty block to a combo descriptor with compressed pubkey')
+ combo_key = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
+ combo_address = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080'
+ hash = self.generateblock(node, 'combo(' + combo_key + ')', [])['hash']
+ block = node.getblock(hash, 2)
+ assert_equal(len(block['tx']), 1)
+ assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address)
+
+ self.log.info('Generate an empty block to a combo descriptor with uncompressed pubkey')
+ combo_key = '0408ef68c46d20596cc3f6ddf7c8794f71913add807f1dc55949fa805d764d191c0b7ce6894c126fce0babc6663042f3dde9b0cf76467ea315514e5a6731149c67'
+ combo_address = 'mkc9STceoCcjoXEXe6cm66iJbmjM6zR9B2'
+ hash = self.generateblock(node, 'combo(' + combo_key + ')', [])['hash']
+ block = node.getblock(hash, 2)
+ assert_equal(len(block['tx']), 1)
+ assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address)
+
+ # Generate some extra mempool transactions to verify they don't get mined
+ for _ in range(10):
+ miniwallet.send_self_transfer(from_node=node)
+
+ self.log.info('Generate block with txid')
+ txid = miniwallet.send_self_transfer(from_node=node)['txid']
+ hash = self.generateblock(node, address, [txid])['hash']
+ block = node.getblock(hash, 1)
+ assert_equal(len(block['tx']), 2)
+ assert_equal(block['tx'][1], txid)
+
+ self.log.info('Generate block with raw tx')
+ rawtx = miniwallet.create_self_transfer()['hex']
+ hash = self.generateblock(node, address, [rawtx])['hash']
+
+ block = node.getblock(hash, 1)
+ assert_equal(len(block['tx']), 2)
+ txid = block['tx'][1]
+ assert_equal(node.getrawtransaction(txid=txid, verbose=False, blockhash=hash), rawtx)
+
+ self.log.info('Fail to generate block with out of order txs')
+ txid1 = miniwallet.send_self_transfer(from_node=node)['txid']
+ utxo1 = miniwallet.get_utxo(txid=txid1)
+ rawtx2 = miniwallet.create_self_transfer(utxo_to_spend=utxo1)['hex']
+ assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [rawtx2, txid1])
+
+ self.log.info('Fail to generate block with txid not in mempool')
+ missing_txid = '0000000000000000000000000000000000000000000000000000000000000000'
+ assert_raises_rpc_error(-5, 'Transaction ' + missing_txid + ' not in mempool.', self.generateblock, node, address, [missing_txid])
+
+ self.log.info('Fail to generate block with invalid raw tx')
+ invalid_raw_tx = '0000'
+ assert_raises_rpc_error(-22, 'Transaction decode failed for ' + invalid_raw_tx, self.generateblock, node, address, [invalid_raw_tx])
+
+ self.log.info('Fail to generate block with invalid address/descriptor')
+ assert_raises_rpc_error(-5, 'Invalid address or descriptor', self.generateblock, node, '1234', [])
+
+ self.log.info('Fail to generate block with a ranged descriptor')
+ ranged_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0/*)'
+ assert_raises_rpc_error(-8, 'Ranged descriptor not accepted. Maybe pass through deriveaddresses first?', self.generateblock, node, ranged_descriptor, [])
+
+ self.log.info('Fail to generate block with a descriptor missing a private key')
+ child_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0\'/0)'
+ assert_raises_rpc_error(-5, 'Cannot derive script without private keys', self.generateblock, node, child_descriptor, [])
+
+ def test_generate(self):
message = (
"generate\n\n"
"has been replaced by the -generate "
diff --git a/test/functional/rpc_generateblock.py b/test/functional/rpc_generateblock.py
deleted file mode 100755
index 7eeb745817..0000000000
--- a/test/functional/rpc_generateblock.py
+++ /dev/null
@@ -1,100 +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 generateblock rpc.
-'''
-
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.wallet import MiniWallet
-from test_framework.util import (
- assert_equal,
- assert_raises_rpc_error,
-)
-
-
-class GenerateBlockTest(BitcoinTestFramework):
- def set_test_params(self):
- self.num_nodes = 1
-
- def run_test(self):
- node = self.nodes[0]
- miniwallet = MiniWallet(node)
- miniwallet.rescan_utxos()
-
- self.log.info('Generate an empty block to address')
- address = miniwallet.get_address()
- hash = self.generateblock(node, output=address, transactions=[])['hash']
- block = node.getblock(blockhash=hash, verbose=2)
- assert_equal(len(block['tx']), 1)
- assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address)
-
- self.log.info('Generate an empty block to a descriptor')
- hash = self.generateblock(node, 'addr(' + address + ')', [])['hash']
- block = node.getblock(blockhash=hash, verbosity=2)
- assert_equal(len(block['tx']), 1)
- assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address)
-
- self.log.info('Generate an empty block to a combo descriptor with compressed pubkey')
- combo_key = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
- combo_address = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080'
- hash = self.generateblock(node, 'combo(' + combo_key + ')', [])['hash']
- block = node.getblock(hash, 2)
- assert_equal(len(block['tx']), 1)
- assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address)
-
- self.log.info('Generate an empty block to a combo descriptor with uncompressed pubkey')
- combo_key = '0408ef68c46d20596cc3f6ddf7c8794f71913add807f1dc55949fa805d764d191c0b7ce6894c126fce0babc6663042f3dde9b0cf76467ea315514e5a6731149c67'
- combo_address = 'mkc9STceoCcjoXEXe6cm66iJbmjM6zR9B2'
- hash = self.generateblock(node, 'combo(' + combo_key + ')', [])['hash']
- block = node.getblock(hash, 2)
- assert_equal(len(block['tx']), 1)
- assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address)
-
- # Generate some extra mempool transactions to verify they don't get mined
- for _ in range(10):
- miniwallet.send_self_transfer(from_node=node)
-
- self.log.info('Generate block with txid')
- txid = miniwallet.send_self_transfer(from_node=node)['txid']
- hash = self.generateblock(node, address, [txid])['hash']
- block = node.getblock(hash, 1)
- assert_equal(len(block['tx']), 2)
- assert_equal(block['tx'][1], txid)
-
- self.log.info('Generate block with raw tx')
- rawtx = miniwallet.create_self_transfer()['hex']
- hash = self.generateblock(node, address, [rawtx])['hash']
-
- block = node.getblock(hash, 1)
- assert_equal(len(block['tx']), 2)
- txid = block['tx'][1]
- assert_equal(node.getrawtransaction(txid=txid, verbose=False, blockhash=hash), rawtx)
-
- self.log.info('Fail to generate block with out of order txs')
- txid1 = miniwallet.send_self_transfer(from_node=node)['txid']
- utxo1 = miniwallet.get_utxo(txid=txid1)
- rawtx2 = miniwallet.create_self_transfer(utxo_to_spend=utxo1)['hex']
- assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [rawtx2, txid1])
-
- self.log.info('Fail to generate block with txid not in mempool')
- missing_txid = '0000000000000000000000000000000000000000000000000000000000000000'
- assert_raises_rpc_error(-5, 'Transaction ' + missing_txid + ' not in mempool.', self.generateblock, node, address, [missing_txid])
-
- self.log.info('Fail to generate block with invalid raw tx')
- invalid_raw_tx = '0000'
- assert_raises_rpc_error(-22, 'Transaction decode failed for ' + invalid_raw_tx, self.generateblock, node, address, [invalid_raw_tx])
-
- self.log.info('Fail to generate block with invalid address/descriptor')
- assert_raises_rpc_error(-5, 'Invalid address or descriptor', self.generateblock, node, '1234', [])
-
- self.log.info('Fail to generate block with a ranged descriptor')
- ranged_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0/*)'
- assert_raises_rpc_error(-8, 'Ranged descriptor not accepted. Maybe pass through deriveaddresses first?', self.generateblock, node, ranged_descriptor, [])
-
- self.log.info('Fail to generate block with a descriptor missing a private key')
- child_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0\'/0)'
- assert_raises_rpc_error(-5, 'Cannot derive script without private keys', self.generateblock, node, child_descriptor, [])
-
-if __name__ == '__main__':
- GenerateBlockTest().main()
diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py
index 2f1796d7cc..f64aae7223 100755
--- a/test/functional/rpc_misc.py
+++ b/test/functional/rpc_misc.py
@@ -56,9 +56,6 @@ class RpcMiscTest(BitcoinTestFramework):
self.log.info("test logging rpc and help")
- # Test logging RPC returns the expected number of logging categories.
- assert_equal(len(node.logging()), 27)
-
# Test toggling a logging category on/off/on with the logging RPC.
assert_equal(node.logging()['qt'], True)
node.logging(exclude=['qt'])
diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py
index c7fbf679b6..fcea24655b 100644
--- a/test/functional/test_framework/address.py
+++ b/test/functional/test_framework/address.py
@@ -35,7 +35,7 @@ class AddressType(enum.Enum):
legacy = 'legacy' # P2PKH
-chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
+b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def create_deterministic_address_bcrt1_p2tr_op_true():
@@ -59,10 +59,10 @@ def byte_to_base58(b, version):
b += hash256(b)[:4] # append checksum
value = int.from_bytes(b, 'big')
while value > 0:
- result = chars[value % 58] + result
+ result = b58chars[value % 58] + result
value //= 58
while b[0] == 0:
- result = chars[0] + result
+ result = b58chars[0] + result
b = b[1:]
return result
@@ -76,8 +76,8 @@ def base58_to_byte(s):
n = 0
for c in s:
n *= 58
- assert c in chars
- digit = chars.index(c)
+ assert c in b58chars
+ digit = b58chars.index(c)
n += digit
h = '%x' % n
if len(h) % 2:
@@ -85,14 +85,14 @@ def base58_to_byte(s):
res = n.to_bytes((n.bit_length() + 7) // 8, 'big')
pad = 0
for c in s:
- if c == chars[0]:
+ if c == b58chars[0]:
pad += 1
else:
break
res = b'\x00' * pad + res
- # Assert if the checksum is invalid
- assert_equal(hash256(res[:-4])[:4], res[-4:])
+ if hash256(res[:-4])[:4] != res[-4:]:
+ raise ValueError('Invalid Base58Check checksum')
return res[1:-4], int(res[0])
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index ecdc3bf424..2fb9ec0942 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -9,6 +9,7 @@ from enum import Enum
import argparse
import logging
import os
+import platform
import pdb
import random
import re
@@ -826,6 +827,29 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
except ImportError:
raise SkipTest("python3-zmq module not available.")
+ def skip_if_no_python_bcc(self):
+ """Attempt to import the bcc package and skip the tests if the import fails."""
+ try:
+ import bcc # type: ignore[import] # noqa: F401
+ except ImportError:
+ raise SkipTest("bcc python module not available")
+
+ def skip_if_no_bitcoind_tracepoints(self):
+ """Skip the running test if bitcoind has not been compiled with USDT tracepoint support."""
+ if not self.is_usdt_compiled():
+ raise SkipTest("bitcoind has not been built with USDT tracepoints enabled.")
+
+ def skip_if_no_bpf_permissions(self):
+ """Skip the running test if we don't have permissions to do BPF syscalls and load BPF maps."""
+ # check for 'root' permissions
+ if os.geteuid() != 0:
+ raise SkipTest("no permissions to use BPF (please review the tests carefully before running them with higher privileges)")
+
+ def skip_if_platform_not_linux(self):
+ """Skip the running test if we are not on a Linux platform"""
+ if platform.system() != "Linux":
+ raise SkipTest("not on a Linux system")
+
def skip_if_no_bitcoind_zmq(self):
"""Skip the running test if bitcoind has not been compiled with zmq support."""
if not self.is_zmq_compiled():
@@ -907,6 +931,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
"""Checks whether the zmq module was compiled."""
return self.config["components"].getboolean("ENABLE_ZMQ")
+ def is_usdt_compiled(self):
+ """Checks whether the USDT tracepoints were compiled."""
+ return self.config["components"].getboolean("ENABLE_USDT_TRACEPOINTS")
+
def is_sqlite_compiled(self):
"""Checks whether the wallet module was compiled with Sqlite support."""
return self.config["components"].getboolean("USE_SQLITE")
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index dd41a740ae..e86f365f11 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -8,7 +8,10 @@ from copy import deepcopy
from decimal import Decimal
from enum import Enum
from random import choice
-from typing import Optional
+from typing import (
+ Any,
+ Optional,
+)
from test_framework.address import (
base58_to_byte,
create_deterministic_address_bcrt1_p2tr_op_true,
@@ -93,6 +96,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 get_balance(self):
+ return sum(u['value'] for u in self._utxos)
+
def rescan_utxos(self):
"""Drop all utxos and rescan the utxo set"""
self._utxos = []
@@ -131,24 +137,30 @@ class MiniWallet:
self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value'], 'height': block_info['height']})
return blocks
+ def get_scriptPubKey(self):
+ return self._scriptPubKey
+
def get_descriptor(self):
return descsum_create(f'raw({self._scriptPubKey.hex()})')
def get_address(self):
return self._address
- def get_utxo(self, *, txid: Optional[str]='', mark_as_spent=True):
+ def get_utxo(self, *, txid: str = '', vout: Optional[int] = None, mark_as_spent=True):
"""
Returns a utxo and marks it as spent (pops it from the internal list)
Args:
txid: get the first utxo we find from a specific transaction
"""
- index = -1 # by default the last utxo
self._utxos = sorted(self._utxos, key=lambda k: (k['value'], -k['height'])) # Put the largest utxo last
if txid:
- utxo = next(filter(lambda utxo: txid == utxo['txid'], self._utxos))
- index = self._utxos.index(utxo)
+ utxo_filter: Any = filter(lambda utxo: txid == utxo['txid'], self._utxos)
+ else:
+ utxo_filter = reversed(self._utxos) # By default the largest utxo
+ if vout is not None:
+ utxo_filter = filter(lambda utxo: vout == utxo['vout'], utxo_filter)
+ index = self._utxos.index(next(utxo_filter))
if mark_as_spent:
return self._utxos.pop(index)
else:
@@ -179,6 +191,47 @@ class MiniWallet:
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
+ """
+ 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}
+
+ def create_self_transfer_multi(self, *, from_node, utxos_to_spend=None, num_outputs=1, fee_per_output=1000):
+ """
+ Create and return a transaction that spends the given UTXOs and creates a
+ certain number of outputs with equal amounts.
+ """
+ utxos_to_spend = utxos_to_spend or [self.get_utxo()]
+ # 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']
+
+ # duplicate inputs, witnesses and outputs
+ tx.vin = [deepcopy(tx.vin[0]) for _ in range(len(utxos_to_spend))]
+ 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)]
+
+ # adapt input prevouts
+ for i, utxo in enumerate(utxos_to_spend):
+ tx.vin[i] = CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout']))
+
+ # adapt output amounts (use fixed fee per output)
+ 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
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index b0f24e3b97..1f0f806d91 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -168,6 +168,9 @@ BASE_SCRIPTS = [
'wallet_reorgsrestore.py',
'interface_http.py',
'interface_rpc.py',
+ 'interface_usdt_net.py',
+ 'interface_usdt_utxocache.py',
+ 'interface_usdt_validation.py',
'rpc_psbt.py --legacy-wallet',
'rpc_psbt.py --descriptors',
'rpc_users.py',
@@ -188,8 +191,7 @@ BASE_SCRIPTS = [
'rpc_decodescript.py',
'rpc_blockchain.py',
'rpc_deprecated.py',
- 'wallet_disable.py --legacy-wallet',
- 'wallet_disable.py --descriptors',
+ 'wallet_disable.py',
'p2p_addr_relay.py',
'p2p_getaddr_caching.py',
'p2p_getdata.py',
@@ -225,8 +227,7 @@ BASE_SCRIPTS = [
'feature_rbf.py --descriptors',
'mempool_packages.py',
'mempool_package_onemore.py',
- 'rpc_createmultisig.py --legacy-wallet',
- 'rpc_createmultisig.py --descriptors',
+ 'rpc_createmultisig.py',
'rpc_packages.py',
'mempool_package_limits.py',
'feature_versionbits_warning.py',
@@ -237,7 +238,6 @@ BASE_SCRIPTS = [
'p2p_eviction.py',
'wallet_signmessagewithaddress.py',
'rpc_signmessagewithprivkey.py',
- 'rpc_generateblock.py',
'rpc_generate.py',
'wallet_balance.py --legacy-wallet',
'wallet_balance.py --descriptors',
@@ -280,6 +280,8 @@ BASE_SCRIPTS = [
'wallet_create_tx.py --legacy-wallet',
'wallet_send.py --legacy-wallet',
'wallet_send.py --descriptors',
+ 'wallet_sendall.py --legacy-wallet',
+ 'wallet_sendall.py --descriptors',
'wallet_create_tx.py --descriptors',
'wallet_taproot.py',
'wallet_inactive_hdchains.py',
@@ -307,10 +309,10 @@ BASE_SCRIPTS = [
'p2p_ping.py',
'rpc_scantxoutset.py',
'feature_txindex_compatibility.py',
+ 'feature_unsupported_utxo_db.py',
'feature_logging.py',
'feature_anchors.py',
- 'feature_coinstatsindex.py --legacy-wallet',
- 'feature_coinstatsindex.py --descriptors',
+ 'feature_coinstatsindex.py',
'wallet_orphanedreward.py',
'wallet_timelock.py',
'p2p_node_network_limited.py',
@@ -589,11 +591,12 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
# Clean up dangling processes if any. This may only happen with --failfast option.
# Killing the process group will also terminate the current process but that is
# not an issue
- if len(job_queue.jobs):
+ if not os.getenv("CI_FAILFAST_TEST_LEAVE_DANGLING") and len(job_queue.jobs):
os.killpg(os.getpgid(0), signal.SIGKILL)
sys.exit(not all_passed)
+
def print_results(test_results, max_len_name, runtime):
results = "\n" + BOLD[1] + "%s | %s | %s\n\n" % ("TEST".ljust(max_len_name), "STATUS ", "DURATION") + BOLD[0]
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 0cfbefb719..0c93821e7f 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -50,7 +50,9 @@ class WalletTest(BitcoinTestFramework):
self.num_nodes = 2
self.setup_clean_chain = True
self.extra_args = [
- ['-limitdescendantcount=3'], # Limit mempool descendants as a hack to have wallet txs rejected from the mempool
+ # Limit mempool descendants as a hack to have wallet txs rejected from the mempool.
+ # Set walletrejectlongchains=0 so the wallet still creates the transactions.
+ ['-limitdescendantcount=3', '-walletrejectlongchains=0'],
[],
]
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index a7873838be..a6c93ba5f9 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",
+ "-acceptnonstdtxn=1", "-walletrejectlongchains=0"
]] * self.num_nodes
self.setup_clean_chain = True
self.supports_cli = False
@@ -142,7 +142,7 @@ class WalletTest(BitcoinTestFramework):
self.nodes[2].lockunspent(False, [unspent_0], True)
# Restarting the node with the lock written to the wallet should keep the lock
- self.restart_node(2)
+ self.restart_node(2, ["-walletrejectlongchains=0"])
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
# Unloading and reloading the wallet with a persistent lock should keep the lock
@@ -568,7 +568,7 @@ class WalletTest(BitcoinTestFramework):
self.log.info("Test -reindex")
self.stop_nodes()
# set lower ancestor limit for later
- self.start_node(0, ['-reindex', "-limitancestorcount=" + str(chainlimit)])
+ self.start_node(0, ['-reindex', "-walletrejectlongchains=0", "-limitancestorcount=" + str(chainlimit)])
self.start_node(1, ['-reindex', "-limitancestorcount=" + str(chainlimit)])
self.start_node(2, ['-reindex', "-limitancestorcount=" + str(chainlimit)])
# reindex will leave rpc warm up "early"; Wait for it to finish
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index f6843d597d..3b23ee8e94 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -442,7 +442,9 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
self.generate(peer_node, 1)
# Create single-input PSBT for transaction to be bumped
- psbt = watcher.walletcreatefundedpsbt([], {dest_address: 0.0005}, 0, {"fee_rate": 1}, True)['psbt']
+ # Ensure the payment amount + change can be fully funded using one of the 0.001BTC inputs.
+ psbt = watcher.walletcreatefundedpsbt([watcher.listunspent()[0]], {dest_address: 0.0005}, 0,
+ {"fee_rate": 1, "add_inputs": False}, True)['psbt']
psbt_signed = signer.walletprocesspsbt(psbt=psbt, sign=True, sighashtype="ALL", bip32derivs=True)
psbt_final = watcher.finalizepsbt(psbt_signed["psbt"])
original_txid = watcher.sendrawtransaction(psbt_final["hex"])
diff --git a/test/functional/wallet_disable.py b/test/functional/wallet_disable.py
index 2c7996ca6b..74cddf2738 100755
--- a/test/functional/wallet_disable.py
+++ b/test/functional/wallet_disable.py
@@ -26,10 +26,6 @@ class DisableWalletTest (BitcoinTestFramework):
x = self.nodes[0].validateaddress('mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ')
assert x['isvalid'] == True
- # Checking mining to an address without a wallet. Generating to a valid address should succeed
- # but generating to an invalid address will fail.
- self.generatetoaddress(self.nodes[0], 1, 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ')
- assert_raises_rpc_error(-5, "Invalid address", self.generatetoaddress, self.nodes[0], 1, '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy')
if __name__ == '__main__':
- DisableWalletTest ().main ()
+ DisableWalletTest().main()
diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py
index 5aae2c813a..6552bfe60c 100755
--- a/test/functional/wallet_resendwallettransactions.py
+++ b/test/functional/wallet_resendwallettransactions.py
@@ -38,7 +38,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework):
# Can take a few seconds due to transaction trickling
peer_first.wait_for_broadcast([txid])
- # Add a second peer since txs aren't rebroadcast to the same peer (see filterInventoryKnown)
+ # Add a second peer since txs aren't rebroadcast to the same peer (see m_tx_inventory_known_filter)
peer_second = node.add_p2p_connection(P2PTxInvStore())
self.log.info("Create a block")
diff --git a/test/functional/wallet_sendall.py b/test/functional/wallet_sendall.py
new file mode 100755
index 0000000000..aa8d2a9d2c
--- /dev/null
+++ b/test/functional/wallet_sendall.py
@@ -0,0 +1,316 @@
+#!/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 the sendall RPC command."""
+
+from decimal import Decimal, getcontext
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ assert_raises_rpc_error,
+)
+
+# Decorator to reset activewallet to zero utxos
+def cleanup(func):
+ def wrapper(self):
+ try:
+ func(self)
+ finally:
+ if 0 < self.wallet.getbalances()["mine"]["trusted"]:
+ self.wallet.sendall([self.remainder_target])
+ assert_equal(0, self.wallet.getbalances()["mine"]["trusted"]) # wallet is empty
+ return wrapper
+
+class SendallTest(BitcoinTestFramework):
+ # Setup and helpers
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def set_test_params(self):
+ getcontext().prec=10
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def assert_balance_swept_completely(self, tx, balance):
+ output_sum = sum([o["value"] for o in tx["decoded"]["vout"]])
+ assert_equal(output_sum, balance + tx["fee"])
+ assert_equal(0, self.wallet.getbalances()["mine"]["trusted"]) # wallet is empty
+
+ def assert_tx_has_output(self, tx, addr, value=None):
+ for output in tx["decoded"]["vout"]:
+ if addr == output["scriptPubKey"]["address"] and value is None or value == output["value"]:
+ return
+ raise AssertionError("Output to {} not present or wrong amount".format(addr))
+
+ def assert_tx_has_outputs(self, tx, expected_outputs):
+ assert_equal(len(expected_outputs), len(tx["decoded"]["vout"]))
+ for eo in expected_outputs:
+ self.assert_tx_has_output(tx, eo["address"], eo["value"])
+
+ def add_utxos(self, amounts):
+ for a in amounts:
+ self.def_wallet.sendtoaddress(self.wallet.getnewaddress(), a)
+ self.generate(self.nodes[0], 1)
+ assert_greater_than(self.wallet.getbalances()["mine"]["trusted"], 0)
+ return self.wallet.getbalances()["mine"]["trusted"]
+
+ # Helper schema for success cases
+ def test_sendall_success(self, sendall_args, remaining_balance = 0):
+ sendall_tx_receipt = self.wallet.sendall(sendall_args)
+ self.generate(self.nodes[0], 1)
+ # wallet has remaining balance (usually empty)
+ assert_equal(remaining_balance, self.wallet.getbalances()["mine"]["trusted"])
+
+ assert_equal(sendall_tx_receipt["complete"], True)
+ return self.wallet.gettransaction(txid = sendall_tx_receipt["txid"], verbose = True)
+
+ @cleanup
+ def gen_and_clean(self):
+ self.add_utxos([15, 2, 4])
+
+ def test_cleanup(self):
+ self.log.info("Test that cleanup wrapper empties wallet")
+ self.gen_and_clean()
+ assert_equal(0, self.wallet.getbalances()["mine"]["trusted"]) # wallet is empty
+
+ # Actual tests
+ @cleanup
+ def sendall_two_utxos(self):
+ self.log.info("Testing basic sendall case without specific amounts")
+ pre_sendall_balance = self.add_utxos([10,11])
+ tx_from_wallet = self.test_sendall_success(sendall_args = [self.remainder_target])
+
+ self.assert_tx_has_outputs(tx = tx_from_wallet,
+ expected_outputs = [
+ { "address": self.remainder_target, "value": pre_sendall_balance + tx_from_wallet["fee"] } # fee is neg
+ ]
+ )
+ self.assert_balance_swept_completely(tx_from_wallet, pre_sendall_balance)
+
+ @cleanup
+ def sendall_split(self):
+ self.log.info("Testing sendall where two recipients have unspecified amount")
+ pre_sendall_balance = self.add_utxos([1, 2, 3, 15])
+ tx_from_wallet = self.test_sendall_success([self.remainder_target, self.split_target])
+
+ half = (pre_sendall_balance + tx_from_wallet["fee"]) / 2
+ self.assert_tx_has_outputs(tx_from_wallet,
+ expected_outputs = [
+ { "address": self.split_target, "value": half },
+ { "address": self.remainder_target, "value": half }
+ ]
+ )
+ self.assert_balance_swept_completely(tx_from_wallet, pre_sendall_balance)
+
+ @cleanup
+ def sendall_and_spend(self):
+ self.log.info("Testing sendall in combination with paying specified amount to recipient")
+ pre_sendall_balance = self.add_utxos([8, 13])
+ tx_from_wallet = self.test_sendall_success([{self.recipient: 5}, self.remainder_target])
+
+ self.assert_tx_has_outputs(tx_from_wallet,
+ expected_outputs = [
+ { "address": self.recipient, "value": 5 },
+ { "address": self.remainder_target, "value": pre_sendall_balance - 5 + tx_from_wallet["fee"] }
+ ]
+ )
+ self.assert_balance_swept_completely(tx_from_wallet, pre_sendall_balance)
+
+ @cleanup
+ def sendall_invalid_recipient_addresses(self):
+ self.log.info("Test having only recipient with specified amount, missing recipient with unspecified amount")
+ self.add_utxos([12, 9])
+
+ assert_raises_rpc_error(
+ -8,
+ "Must provide at least one address without a specified amount" ,
+ self.wallet.sendall,
+ [{self.recipient: 5}]
+ )
+
+ @cleanup
+ def sendall_duplicate_recipient(self):
+ self.log.info("Test duplicate destination")
+ self.add_utxos([1, 8, 3, 9])
+
+ assert_raises_rpc_error(
+ -8,
+ "Invalid parameter, duplicated address: {}".format(self.remainder_target),
+ self.wallet.sendall,
+ [self.remainder_target, self.remainder_target]
+ )
+
+ @cleanup
+ def sendall_invalid_amounts(self):
+ self.log.info("Test sending more than balance")
+ pre_sendall_balance = self.add_utxos([7, 14])
+
+ expected_tx = self.wallet.sendall(recipients=[{self.recipient: 5}, self.remainder_target], options={"add_to_wallet": False})
+ tx = self.wallet.decoderawtransaction(expected_tx['hex'])
+ fee = 21 - sum([o["value"] for o in tx["vout"]])
+
+ assert_raises_rpc_error(-6, "Assigned more value to outputs than available funds.", self.wallet.sendall,
+ [{self.recipient: pre_sendall_balance + 1}, self.remainder_target])
+ assert_raises_rpc_error(-6, "Insufficient funds for fees after creating specified outputs.", self.wallet.sendall,
+ [{self.recipient: pre_sendall_balance}, self.remainder_target])
+ assert_raises_rpc_error(-8, "Specified output amount to {} is below dust threshold".format(self.recipient),
+ self.wallet.sendall, [{self.recipient: 0.00000001}, self.remainder_target])
+ assert_raises_rpc_error(-6, "Dynamically assigned remainder results in dust output.", self.wallet.sendall,
+ [{self.recipient: pre_sendall_balance - fee}, self.remainder_target])
+ assert_raises_rpc_error(-6, "Dynamically assigned remainder results in dust output.", self.wallet.sendall,
+ [{self.recipient: pre_sendall_balance - fee - Decimal(0.00000010)}, self.remainder_target])
+
+ # @cleanup not needed because different wallet used
+ def sendall_negative_effective_value(self):
+ self.log.info("Test that sendall fails if all UTXOs have negative effective value")
+ # Use dedicated wallet for dust amounts and unload wallet at end
+ self.nodes[0].createwallet("dustwallet")
+ dust_wallet = self.nodes[0].get_wallet_rpc("dustwallet")
+
+ self.def_wallet.sendtoaddress(dust_wallet.getnewaddress(), 0.00000400)
+ self.def_wallet.sendtoaddress(dust_wallet.getnewaddress(), 0.00000300)
+ self.generate(self.nodes[0], 1)
+ assert_greater_than(dust_wallet.getbalances()["mine"]["trusted"], 0)
+
+ assert_raises_rpc_error(-6, "Total value of UTXO pool too low to pay for transaction."
+ + " Try using lower feerate or excluding uneconomic UTXOs with 'send_max' option.",
+ dust_wallet.sendall, recipients=[self.remainder_target], fee_rate=300)
+
+ dust_wallet.unloadwallet()
+
+ @cleanup
+ def sendall_with_send_max(self):
+ self.log.info("Check that `send_max` option causes negative value UTXOs to be left behind")
+ self.add_utxos([0.00000400, 0.00000300, 1])
+
+ # sendall with send_max
+ sendall_tx_receipt = self.wallet.sendall(recipients=[self.remainder_target], fee_rate=300, options={"send_max": True})
+ tx_from_wallet = self.wallet.gettransaction(txid = sendall_tx_receipt["txid"], verbose = True)
+
+ assert_equal(len(tx_from_wallet["decoded"]["vin"]), 1)
+ self.assert_tx_has_outputs(tx_from_wallet, [{"address": self.remainder_target, "value": 1 + tx_from_wallet["fee"]}])
+ assert_equal(self.wallet.getbalances()["mine"]["trusted"], Decimal("0.00000700"))
+
+ self.def_wallet.sendtoaddress(self.wallet.getnewaddress(), 1)
+ self.generate(self.nodes[0], 1)
+
+ @cleanup
+ def sendall_specific_inputs(self):
+ self.log.info("Test sendall with a subset of UTXO pool")
+ self.add_utxos([17, 4])
+ utxo = self.wallet.listunspent()[0]
+
+ sendall_tx_receipt = self.wallet.sendall(recipients=[self.remainder_target], options={"inputs": [utxo]})
+ tx_from_wallet = self.wallet.gettransaction(txid = sendall_tx_receipt["txid"], verbose = True)
+ assert_equal(len(tx_from_wallet["decoded"]["vin"]), 1)
+ assert_equal(len(tx_from_wallet["decoded"]["vout"]), 1)
+ assert_equal(tx_from_wallet["decoded"]["vin"][0]["txid"], utxo["txid"])
+ assert_equal(tx_from_wallet["decoded"]["vin"][0]["vout"], utxo["vout"])
+ self.assert_tx_has_output(tx_from_wallet, self.remainder_target)
+
+ self.generate(self.nodes[0], 1)
+ assert_greater_than(self.wallet.getbalances()["mine"]["trusted"], 0)
+
+ @cleanup
+ def sendall_fails_on_missing_input(self):
+ # fails because UTXO was previously spent, and wallet is empty
+ self.log.info("Test sendall fails because specified UTXO is not available")
+ self.add_utxos([16, 5])
+ spent_utxo = self.wallet.listunspent()[0]
+
+ # fails on unconfirmed spent UTXO
+ self.wallet.sendall(recipients=[self.remainder_target])
+ assert_raises_rpc_error(-8,
+ "Input not available. UTXO ({}:{}) was already spent.".format(spent_utxo["txid"], spent_utxo["vout"]),
+ self.wallet.sendall, recipients=[self.remainder_target], options={"inputs": [spent_utxo]})
+
+ # fails on specific previously spent UTXO, while other UTXOs exist
+ self.generate(self.nodes[0], 1)
+ self.add_utxos([19, 2])
+ assert_raises_rpc_error(-8,
+ "Input not available. UTXO ({}:{}) was already spent.".format(spent_utxo["txid"], spent_utxo["vout"]),
+ self.wallet.sendall, recipients=[self.remainder_target], options={"inputs": [spent_utxo]})
+
+ # fails because UTXO is unknown, while other UTXOs exist
+ foreign_utxo = self.def_wallet.listunspent()[0]
+ assert_raises_rpc_error(-8, "Input not found. UTXO ({}:{}) is not part of wallet.".format(foreign_utxo["txid"],
+ foreign_utxo["vout"]), self.wallet.sendall, recipients=[self.remainder_target],
+ options={"inputs": [foreign_utxo]})
+
+ @cleanup
+ def sendall_fails_on_no_address(self):
+ self.log.info("Test sendall fails because no address is provided")
+ self.add_utxos([19, 2])
+
+ assert_raises_rpc_error(
+ -8,
+ "Must provide at least one address without a specified amount" ,
+ self.wallet.sendall,
+ []
+ )
+
+ @cleanup
+ def sendall_fails_on_specific_inputs_with_send_max(self):
+ self.log.info("Test sendall fails because send_max is used while specific inputs are provided")
+ self.add_utxos([15, 6])
+ utxo = self.wallet.listunspent()[0]
+
+ assert_raises_rpc_error(-8,
+ "Cannot combine send_max with specific inputs.",
+ self.wallet.sendall,
+ recipients=[self.remainder_target],
+ options={"inputs": [utxo], "send_max": True})
+
+ def run_test(self):
+ self.nodes[0].createwallet("activewallet")
+ self.wallet = self.nodes[0].get_wallet_rpc("activewallet")
+ self.def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+ self.generate(self.nodes[0], 101)
+ self.recipient = self.def_wallet.getnewaddress() # payee for a specific amount
+ self.remainder_target = self.def_wallet.getnewaddress() # address that receives everything left after payments and fees
+ self.split_target = self.def_wallet.getnewaddress() # 2nd target when splitting rest
+
+ # Test cleanup
+ self.test_cleanup()
+
+ # Basic sweep: everything to one address
+ self.sendall_two_utxos()
+
+ # Split remainder to two addresses with equal amounts
+ self.sendall_split()
+
+ # Pay recipient and sweep remainder
+ self.sendall_and_spend()
+
+ # sendall fails if no recipient has unspecified amount
+ self.sendall_invalid_recipient_addresses()
+
+ # Sendall fails if same destination is provided twice
+ self.sendall_duplicate_recipient()
+
+ # Sendall fails when trying to spend more than the balance
+ self.sendall_invalid_amounts()
+
+ # Sendall fails when wallet has no economically spendable UTXOs
+ self.sendall_negative_effective_value()
+
+ # Leave dust behind if using send_max
+ self.sendall_with_send_max()
+
+ # Sendall succeeds with specific inputs
+ self.sendall_specific_inputs()
+
+ # Fails for the right reasons on missing or previously spent UTXOs
+ self.sendall_fails_on_missing_input()
+
+ # Sendall fails when no address is provided
+ self.sendall_fails_on_no_address()
+
+ # Sendall fails when using send_max while specifying inputs
+ self.sendall_fails_on_specific_inputs_with_send_max()
+
+if __name__ == '__main__':
+ SendallTest().main()
diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py
index 423cfecdc0..8e4e1f5d36 100755
--- a/test/functional/wallet_signer.py
+++ b/test/functional/wallet_signer.py
@@ -194,6 +194,12 @@ class WalletSignerTest(BitcoinTestFramework):
assert(res["complete"])
assert_equal(res["hex"], mock_tx)
+ self.log.info('Test sendall using hww1')
+
+ res = hww.sendall(recipients=[{dest:0.5}, hww.getrawchangeaddress()],options={"add_to_wallet": False})
+ assert(res["complete"])
+ assert_equal(res["hex"], mock_tx)
+
# # Handle error thrown by script
# self.set_mock_result(self.nodes[4], "2")
# assert_raises_rpc_error(-1, 'Unable to parse JSON',
diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py
index e0d48f8047..688ca58d7f 100755
--- a/test/get_previous_releases.py
+++ b/test/get_previous_releases.py
@@ -19,8 +19,12 @@ import subprocess
import sys
import hashlib
-
SHA256_SUMS = {
+ "0e2819135366f150d9906e294b61dff58fd1996ebd26c2f8e979d6c0b7a79580": "bitcoin-0.14.3-aarch64-linux-gnu.tar.gz",
+ "d86fc90824a85c38b25c8488115178d5785dbc975f5ff674f9f5716bc8ad6e65": "bitcoin-0.14.3-arm-linux-gnueabihf.tar.gz",
+ "1b0a7408c050e3d09a8be8e21e183ef7ee570385dc41216698cc3ab392a484e7": "bitcoin-0.14.3-osx64.tar.gz",
+ "706e0472dbc933ed2757650d54cbcd780fd3829ebf8f609b32780c7eedebdbc9": "bitcoin-0.14.3-x86_64-linux-gnu.tar.gz",
+ #
"d40f18b4e43c6e6370ef7db9131f584fbb137276ec2e3dba67a4b267f81cb644": "bitcoin-0.15.2-aarch64-linux-gnu.tar.gz",
"54fb877a148a6ad189a1e1ab1ff8b11181e58ff2aaf430da55b3fd46ae549a6b": "bitcoin-0.15.2-arm-linux-gnueabihf.tar.gz",
"87e9340ff3d382d543b2b69112376077f0c8b4f7450d372e83b68f5a1e22b2df": "bitcoin-0.15.2-osx64.tar.gz",
diff --git a/test/lint/lint-all.sh b/test/lint/lint-all.sh
index fabc24c91b..fa37fa51c6 100755
--- a/test/lint/lint-all.sh
+++ b/test/lint/lint-all.sh
@@ -4,7 +4,7 @@
# 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-*.sh files, and fails if any exit
+# 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"
@@ -18,7 +18,7 @@ LINTALL=$(basename "${BASH_SOURCE[0]}")
EXIT_CODE=0
-for f in "${SCRIPTDIR}"/lint-*.sh; do
+for f in "${SCRIPTDIR}"/lint-*; do
if [ "$(basename "$f")" != "$LINTALL" ]; then
if ! "$f"; then
echo "^---- failure generated from $f"
diff --git a/test/lint/lint-cpp.sh b/test/lint/lint-cpp.sh
deleted file mode 100755
index cac57b968d..0000000000
--- a/test/lint/lint-cpp.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-#
-# 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.
-#
-# Check for various C++ code patterns we want to avoid.
-
-export LC_ALL=C
-
-EXIT_CODE=0
-
-OUTPUT=$(git grep -E "boost::bind\(" -- "*.cpp" "*.h")
-if [[ ${OUTPUT} != "" ]]; then
- echo "Use of boost::bind detected. Use std::bind instead."
- echo
- echo "${OUTPUT}"
- EXIT_CODE=1
-fi
-
-exit ${EXIT_CODE} \ No newline at end of file
diff --git a/test/lint/lint-files.py b/test/lint/lint-files.py
index 68b795eef7..3723e0ee6a 100755
--- a/test/lint/lint-files.py
+++ b/test/lint/lint-files.py
@@ -13,6 +13,7 @@ import sys
from subprocess import check_output
from typing import 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 '^#!'"
@@ -184,6 +185,8 @@ def check_shebang_file_permissions() -> int:
def main() -> NoReturn:
+ root_dir = check_output(CMD_TOP_LEVEL).decode("utf8").strip()
+ os.chdir(root_dir)
failed_tests = 0
failed_tests += check_all_filenames()
failed_tests += check_source_filenames()
diff --git a/test/lint/lint-files.sh b/test/lint/lint-files.sh
deleted file mode 100755
index 86d7fc724a..0000000000
--- a/test/lint/lint-files.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env bash
-# 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.
-
-export LC_ALL=C
-
-set -e
-cd "$(dirname "$0")/../.."
-test/lint/lint-files.py
diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh
index d98f12b1a1..73730f16b3 100755
--- a/test/lint/lint-format-strings.sh
+++ b/test/lint/lint-format-strings.sh
@@ -29,7 +29,7 @@ FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS=(
)
EXIT_CODE=0
-if ! python3 -m doctest test/lint/lint-format-strings.py; then
+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
@@ -37,7 +37,7 @@ for S in "${FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS[@]}"; do
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/lint-format-strings.py --skip-arguments "${SKIP_ARGUMENTS}" "${FUNCTION_NAME}" "${MATCHING_FILES[@]}"; then
+ if ! "test/lint/run-lint-format-strings.py" --skip-arguments "${SKIP_ARGUMENTS}" "${FUNCTION_NAME}" "${MATCHING_FILES[@]}"; then
EXIT_CODE=1
fi
done
diff --git a/test/lint/lint-python-dead-code.py b/test/lint/lint-python-dead-code.py
new file mode 100755
index 0000000000..b3f9394788
--- /dev/null
+++ b/test/lint/lint-python-dead-code.py
@@ -0,0 +1,41 @@
+#!/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.
+
+"""
+Find dead Python code.
+"""
+
+from subprocess import check_output, STDOUT, CalledProcessError
+
+FILES_ARGS = ['git', 'ls-files', '--', '*.py']
+
+
+def check_vulture_install():
+ try:
+ check_output(["vulture", "--version"])
+ except FileNotFoundError:
+ print("Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\"")
+ exit(0)
+
+
+def main():
+ check_vulture_install()
+
+ files = check_output(FILES_ARGS).decode("utf-8").splitlines()
+ # --min-confidence 100 will only report code that is guaranteed to be unused within the analyzed files.
+ # Any value below 100 introduces the risk of false positives, which would create an unacceptable maintenance burden.
+ vulture_args = ['vulture', '--min-confidence=100'] + files
+
+ try:
+ check_output(vulture_args, stderr=STDOUT)
+ except CalledProcessError as e:
+ print(e.output.decode("utf-8"), end="")
+ print("Python dead code detection found some issues")
+ exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh
deleted file mode 100755
index 247bfb310a..0000000000
--- a/test/lint/lint-python-dead-code.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env bash
-#
-# 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.
-#
-# Find dead Python code.
-
-export LC_ALL=C
-
-if ! command -v vulture > /dev/null; then
- echo "Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\""
- exit 0
-fi
-
-# --min-confidence 100 will only report code that is guaranteed to be unused within the analyzed files.
-# Any value below 100 introduces the risk of false positives, which would create an unacceptable maintenance burden.
-mapfile -t FILES < <(git ls-files -- "*.py")
-if ! vulture --min-confidence 100 "${FILES[@]}"; then
- echo "Python dead code detection found some issues"
- exit 1
-fi
diff --git a/test/lint/lint-python-mutable-default-parameters.py b/test/lint/lint-python-mutable-default-parameters.py
new file mode 100755
index 0000000000..7991e3630b
--- /dev/null
+++ b/test/lint/lint-python-mutable-default-parameters.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 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.
+
+"""
+Detect when a mutable list or dict is used as a default parameter value in a Python function.
+"""
+
+import subprocess
+import sys
+
+
+def main():
+ command = [
+ "git",
+ "grep",
+ "-E",
+ r"^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)",
+ "--",
+ "*.py",
+ ]
+ output = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True)
+ if len(output.stdout) > 0:
+ error_msg = (
+ "A mutable list or dict seems to be used as default parameter value:\n\n"
+ f"{output.stdout}\n"
+ f"{example()}"
+ )
+ print(error_msg)
+ sys.exit(1)
+ else:
+ sys.exit(0)
+
+
+def example():
+ return """This is how mutable list and dict default parameter values behave:
+
+>>> def f(i, j=[], k={}):
+... j.append(i)
+... k[i] = True
+... return j, k
+...
+>>> f(1)
+([1], {1: True})
+>>> f(1)
+([1, 1], {1: True})
+>>> f(2)
+([1, 1, 2], {1: True, 2: True})
+
+The intended behaviour was likely:
+
+>>> def f(i, j=None, k=None):
+... if j is None:
+... j = []
+... if k is None:
+... k = {}
+... j.append(i)
+... k[i] = True
+... return j, k
+...
+>>> f(1)
+([1], {1: True})
+>>> f(1)
+([1], {1: True})
+>>> f(2)
+([2], {2: True})"""
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/lint/lint-python-mutable-default-parameters.sh b/test/lint/lint-python-mutable-default-parameters.sh
deleted file mode 100755
index 1f9f035d30..0000000000
--- a/test/lint/lint-python-mutable-default-parameters.sh
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 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.
-#
-# Detect when a mutable list or dict is used as a default parameter value in a Python function.
-
-export LC_ALL=C
-EXIT_CODE=0
-OUTPUT=$(git grep -E '^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)' -- "*.py")
-if [[ ${OUTPUT} != "" ]]; then
- echo "A mutable list or dict seems to be used as default parameter value:"
- echo
- echo "${OUTPUT}"
- echo
- cat << EXAMPLE
-This is how mutable list and dict default parameter values behave:
-
->>> def f(i, j=[], k={}):
-... j.append(i)
-... k[i] = True
-... return j, k
-...
->>> f(1)
-([1], {1: True})
->>> f(1)
-([1, 1], {1: True})
->>> f(2)
-([1, 1, 2], {1: True, 2: True})
-
-The intended behaviour was likely:
-
->>> def f(i, j=None, k=None):
-... if j is None:
-... j = []
-... if k is None:
-... k = {}
-... j.append(i)
-... k[i] = True
-... return j, k
-...
->>> f(1)
-([1], {1: True})
->>> f(1)
-([1], {1: True})
->>> f(2)
-([2], {2: True})
-EXAMPLE
- EXIT_CODE=1
-fi
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-qt.sh b/test/lint/lint-qt.sh
deleted file mode 100755
index 2e77682aa2..0000000000
--- a/test/lint/lint-qt.sh
+++ /dev/null
@@ -1,20 +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 for SIGNAL/SLOT connect style, removed since Qt4 support drop.
-
-export LC_ALL=C
-
-EXIT_CODE=0
-
-OUTPUT=$(git grep -E '(SIGNAL|, ?SLOT)\(' -- src/qt)
-if [[ ${OUTPUT} != "" ]]; then
- echo "Use Qt5 connect style in:"
- echo "$OUTPUT"
- EXIT_CODE=1
-fi
-
-exit ${EXIT_CODE}
diff --git a/test/lint/lint-spelling.py b/test/lint/lint-spelling.py
new file mode 100755
index 0000000000..5da1b243f7
--- /dev/null
+++ b/test/lint/lint-spelling.py
@@ -0,0 +1,40 @@
+#!/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.
+
+"""
+Warn in case of spelling errors.
+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"]
+
+
+def check_codespell_install():
+ try:
+ check_output(["codespell", "--version"])
+ except FileNotFoundError:
+ print("Skipping spell check linting since codespell is not installed.")
+ exit(0)
+
+
+def main():
+ check_codespell_install()
+
+ files = check_output(FILES_ARGS).decode("utf-8").splitlines()
+ codespell_args = ['codespell', '--check-filenames', '--disable-colors', '--quiet-level=7', '--ignore-words={}'.format(IGNORE_WORDS_FILE)] + files
+
+ try:
+ check_output(codespell_args, stderr=STDOUT)
+ except CalledProcessError as e:
+ print(e.output.decode("utf-8"), end="")
+ print('^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in {}'.format(IGNORE_WORDS_FILE))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/lint/lint-spelling.sh b/test/lint/lint-spelling.sh
deleted file mode 100755
index b3e558b02a..0000000000
--- a/test/lint/lint-spelling.sh
+++ /dev/null
@@ -1,21 +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.
-#
-# Warn in case of spelling errors.
-# Note: Will exit successfully regardless of spelling errors.
-
-export LC_ALL=C
-
-if ! command -v codespell > /dev/null; then
- echo "Skipping spell check linting since codespell is not installed."
- exit 0
-fi
-
-IGNORE_WORDS_FILE=test/lint/lint-spelling.ignore-words.txt
-mapfile -t FILES < <(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")
-if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} "${FILES[@]}"; then
- echo "^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in ${IGNORE_WORDS_FILE}"
-fi
diff --git a/test/lint/lint-format-strings.py b/test/lint/run-lint-format-strings.py
index b814446125..b814446125 100755
--- a/test/lint/lint-format-strings.py
+++ b/test/lint/run-lint-format-strings.py
diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/spelling.ignore-words.txt
index afdb0692d8..afdb0692d8 100644
--- a/test/lint/lint-spelling.ignore-words.txt
+++ b/test/lint/spelling.ignore-words.txt