aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml19
-rw-r--r--Makefile.am16
-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/common.init.vcxproj.in (renamed from build_msvc/common.init.vcxproj)4
-rwxr-xr-xbuild_msvc/msvc-autogen.py23
-rwxr-xr-xci/test/00_setup_env.sh1
-rwxr-xr-xci/test/00_setup_env_native_fuzz_with_valgrind.sh3
-rwxr-xr-xci/test/00_setup_env_native_qt5.sh2
-rwxr-xr-xci/test/00_setup_env_native_tidy.sh19
-rwxr-xr-xci/test/00_setup_env_native_valgrind.sh3
-rwxr-xr-xci/test/06_script_a.sh7
-rwxr-xr-xci/test/06_script_b.sh5
-rw-r--r--configure.ac77
-rwxr-xr-xcontrib/guix/guix-attest22
-rw-r--r--contrib/guix/manifest.scm6
-rw-r--r--contrib/guix/patches/vmov-alignment.patch267
-rwxr-xr-xcontrib/macdeploy/macdeployqtplus29
-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/packages/libmultiprocess.mk2
-rw-r--r--depends/packages/native_libmultiprocess.mk2
-rw-r--r--depends/packages/qt.mk34
-rw-r--r--depends/patches/qt/dont_use_avx_android_x86_64.patch32
-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/use_android_ndk23.patch2
-rw-r--r--doc/REST-interface.md10
-rw-r--r--doc/build-openbsd.md132
-rw-r--r--doc/cjdns.md35
-rw-r--r--doc/dependencies.md4
-rw-r--r--doc/release-notes-24098.md22
-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--src/Makefile.am9
-rw-r--r--src/Makefile.test.include4
-rw-r--r--src/addrdb.cpp6
-rw-r--r--src/bench/addrman.cpp2
-rw-r--r--src/bench/mempool_stress.cpp6
-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/httpserver.cpp53
-rw-r--r--src/httpserver.h33
-rw-r--r--src/init.cpp10
-rw-r--r--src/init.h2
-rw-r--r--src/net.cpp2
-rw-r--r--src/net_processing.cpp4
-rw-r--r--src/node/chainstate.cpp10
-rw-r--r--src/node/interfaces.cpp4
-rw-r--r--src/node/miner.cpp18
-rw-r--r--src/node/miner.h2
-rw-r--r--src/qt/bitcoingui.cpp2
-rw-r--r--src/qt/intro.cpp6
-rw-r--r--src/qt/optionsmodel.cpp21
-rw-r--r--src/qt/peertablemodel.cpp2
-rw-r--r--src/qt/rpcconsole.cpp4
-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/rest.cpp171
-rw-r--r--src/rest.h28
-rw-r--r--src/rpc/blockchain.cpp2
-rw-r--r--src/rpc/external_signer.cpp2
-rw-r--r--src/rpc/mempool.cpp4
-rw-r--r--src/rpc/mining.cpp29
-rw-r--r--src/rpc/misc.cpp13
-rw-r--r--src/rpc/net.cpp4
-rw-r--r--src/rpc/server.cpp2
-rw-r--r--src/rpc/txoutproof.cpp2
-rw-r--r--src/rpc/util.cpp4
-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/support/lockedpool.cpp6
-rw-r--r--src/test/blockfilter_index_tests.cpp11
-rw-r--r--src/test/descriptor_tests.cpp2
-rw-r--r--src/test/fuzz/http_request.cpp15
-rw-r--r--src/test/fuzz/miniscript_decode.cpp72
-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.cpp4
-rw-r--r--src/test/rest_tests.cpp48
-rw-r--r--src/txdb.cpp138
-rw-r--r--src/txdb.h4
-rw-r--r--src/txmempool.cpp3
-rw-r--r--src/util/check.h2
-rw-r--r--src/util/syscall_sandbox.cpp3
-rw-r--r--src/util/syscall_sandbox.h3
-rw-r--r--src/util/threadnames.cpp4
-rw-r--r--src/validation.cpp19
-rw-r--r--src/wallet/feebumper.cpp2
-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.cpp4
-rw-r--r--src/wallet/rpc/spend.cpp16
-rw-r--r--src/wallet/spend.cpp4
-rw-r--r--src/wallet/walletdb.cpp4
-rw-r--r--test/README.md7
-rw-r--r--test/config.ini.in1
-rwxr-xr-xtest/functional/feature_blockfilterindex_prune.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.py45
-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/mempool_unbroadcast.py45
-rwxr-xr-xtest/functional/rpc_dumptxoutset.py6
-rw-r--r--test/functional/test_framework/address.py16
-rwxr-xr-xtest/functional/test_framework/test_framework.py28
-rwxr-xr-xtest/functional/test_runner.py4
-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-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
139 files changed, 4639 insertions, 1137 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index e89804a3b2..5bfdcbc9b1 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -73,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:
@@ -88,9 +101,9 @@ task:
VCPKG_DEFAULT_BINARY_CACHE: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\archives'
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.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'
+ 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'
diff --git a/Makefile.am b/Makefile.am
index 8637af362e..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
@@ -129,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/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/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/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_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_qt5.sh b/ci/test/00_setup_env_native_qt5.sh
index 70a27ed66c..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 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/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 e70d811d5a..30788f1543 100755
--- a/ci/test/06_script_b.sh
+++ b/ci/test/06_script_b.sh
@@ -34,6 +34,11 @@ 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/configure.ac b/configure.ac
index 13add4903b..2ffd42f80b 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
@@ -366,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 overriden 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
@@ -473,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
@@ -622,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],
@@ -704,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
@@ -715,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
@@ -753,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
@@ -792,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*)
@@ -856,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 overriden 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
@@ -883,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])
@@ -973,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
@@ -1302,7 +1305,7 @@ if test "$enable_fuzz" = "yes"; then
AX_CHECK_LINK_FLAG(
[-fsanitize=$use_sanitizers],
[AC_MSG_RESULT([no])],
- [AC_MSG_RESULT([yes]); CPPFLAGS="$CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"],
+ [AC_MSG_RESULT([yes]); CORE_CPPFLAGS="$CORE_CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"],
[],
[AC_LANG_PROGRAM([[
#include <cstdint>
@@ -1326,7 +1329,7 @@ else
QT_TEST_INCLUDES=SUPPRESS_WARNINGS($QT_TEST_INCLUDES)
fi
- CPPFLAGS="$CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"
+ CORE_CPPFLAGS="$CORE_CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"
fi
if test "$enable_wallet" != "no"; then
@@ -1378,6 +1381,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
@@ -1435,6 +1439,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
@@ -1515,7 +1522,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
@@ -1530,9 +1537,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
@@ -1876,6 +1883,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)
@@ -1928,6 +1938,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])
@@ -2007,9 +2018,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/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/manifest.scm b/contrib/guix/manifest.scm
index 3f110ab995..fcec592c2c 100644
--- a/contrib/guix/manifest.scm
+++ b/contrib/guix/manifest.scm
@@ -162,13 +162,17 @@ desirable for building Bitcoin Core release binaries."
(define (make-gcc-with-pthreads gcc)
(package-with-extra-configure-variable gcc "--enable-threads" "posix"))
+(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"
(let* ((xbinutils (cross-binutils target))
(pthreads-xlibc mingw-w64-x86_64-winpthreads)
(pthreads-xgcc (make-gcc-with-pthreads
(cross-gcc target
- #:xgcc (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
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/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/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/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 46587c7a89..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 += rcc_hardcode_timestamp.patch duplicate_lcqpafonts.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,15 +239,12 @@ 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 && \
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/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/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/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-openbsd.md b/doc/build-openbsd.md
index 275b7ce124..1d8da1143e 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 follow 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/cjdns.md b/doc/cjdns.md
index 5b2bcaf874..b69564729f 100644
--- a/doc/cjdns.md
+++ b/doc/cjdns.md
@@ -10,7 +10,8 @@ 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).
+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.
@@ -23,17 +24,37 @@ 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 connecting to the network
+## 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#cjdns.
+https://github.com/cjdelisle/cjdns#how-to-install-cjdns.
-Don't skip steps
+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).
-You need to be connected to the CJDNS network before it will work with your
-Bitcoin Core node.
+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
diff --git a/doc/dependencies.md b/doc/dependencies.md
index d715a06dba..d5d0c46679 100644
--- a/doc/dependencies.md
+++ b/doc/dependencies.md
@@ -18,7 +18,7 @@ You can find installation instructions in the `build-*.md` file for your platfor
| 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.0.21](https://github.com/bitcoin/bitcoin/pull/18676) | 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
@@ -29,7 +29,7 @@ You can find installation instructions in the `build-*.md` file for your platfor
| [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.2](https://download.qt.io/official_releases/qt/) | [5.11.3](https://github.com/bitcoin/bitcoin/pull/24132) | 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 |
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-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/src/Makefile.am b/src/Makefile.am
index e14b5ec040..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 \
@@ -590,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 \
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 5d25104327..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,6 +117,7 @@ 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 \
@@ -266,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 \
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/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/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/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/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/init.cpp b/src/init.cpp
index f934fd751d..86e6ec4451 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -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
@@ -1058,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
@@ -1453,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;
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/net.cpp b/src/net.cpp
index 799d678520..602d56ab98 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -2782,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.
//
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index fb585937ee..ba72a11ec9 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -3562,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;
}
}
@@ -3902,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);
}
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 74d53d2062..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
@@ -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 917df91933..be5d58527b 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -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 5fd9abc280..c8093ec883 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -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 293f5ddea4..85e3c23085 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -853,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/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 057767eb26..52bda59748 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -151,9 +151,26 @@ 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 (!settings.value("fListen").toBool()) {
+ } 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);
}
diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp
index cd3193a1d2..563bca76e5 100644
--- a/src/qt/peertablemodel.cpp
+++ b/src/qt/peertablemodel.cpp
@@ -80,7 +80,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 bf4ace89a5..dcc4f36aaa 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -848,7 +848,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());
}
@@ -1187,7 +1187,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);
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/rest.cpp b/src/rest.cpp
index d59b6d1c13..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,7 +286,7 @@ 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))
@@ -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,7 +687,7 @@ 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, /*block_hash=*/hashBlock, /*entry=*/ objTx);
std::string strJSON = objTx.write() + "\n";
@@ -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
@@ -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 0ad07de8c4..cf72af1012 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -671,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() + ")"},
}},
}},
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 33f50d9013..1caf4ad96c 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -71,7 +71,7 @@ static RPCHelpMan sendrawtransaction()
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);
+ 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);
}
@@ -163,7 +163,7 @@ static RPCHelpMan testmempoolaccept()
CChainState& chainstate = chainman.ActiveChainstate();
const PackageMempoolAcceptResult package_result = [&] {
LOCK(::cs_main);
- if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true);
+ if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true);
return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
}();
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 305005077d..225feabf14 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -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"},
@@ -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/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
index 2700fb400c..a5443b0329 100644
--- a/src/rpc/txoutproof.cpp
+++ b/src/rpc/txoutproof.cpp
@@ -87,7 +87,7 @@ static RPCHelpMan gettxoutproof()
LOCK(cs_main);
if (pblockindex == nullptr) {
- const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
+ 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");
}
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 9c9e6e9f11..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 += " )";
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/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/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/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index 404929b690..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);
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/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 e5ce936519..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
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/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 33918cceab..f73cc5da5f 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -328,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);
@@ -694,6 +694,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
void CTxMemPool::_clear()
{
+ vTxHashes.clear();
mapTx.clear();
mapNextTx.clear();
totalTxSize = 0;
diff --git a/src/util/check.h b/src/util/check.h
index 80e973e7e2..4ee65c8d34 100644
--- a/src/util/check.h
+++ b/src/util/check.h
@@ -51,7 +51,7 @@ void assertion_fail(const char* file, int line, const char* func, const char* as
/** Helper for Assert()/Assume() */
template <bool IS_ASSERT, typename T>
-T&& inline_assertion_check(T&& val, const char* file, int line, const char* func, const char* assertion)
+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
diff --git a/src/util/syscall_sandbox.cpp b/src/util/syscall_sandbox.cpp
index a05efac602..a69f815ce4 100644
--- a/src/util/syscall_sandbox.cpp
+++ b/src/util/syscall_sandbox.cpp
@@ -592,8 +592,6 @@ public:
allowed_syscalls.insert(__NR_getcwd); // get current working directory
allowed_syscalls.insert(__NR_getdents); // get directory entries
allowed_syscalls.insert(__NR_getdents64); // get directory entries
- allowed_syscalls.insert(__NR_inotify_rm_watch);// remove an existing watch from an inotify instance
- allowed_syscalls.insert(__NR_linkat); // create relative to a directory file descriptor
allowed_syscalls.insert(__NR_lstat); // get file status
allowed_syscalls.insert(__NR_mkdir); // create a directory
allowed_syscalls.insert(__NR_newfstatat); // get file status
@@ -823,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/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 ae3171fa14..f4b316f67a 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1961,8 +1961,9 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Co
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;
@@ -2256,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);
@@ -2265,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(),
@@ -2605,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;
@@ -2673,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);
@@ -4897,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/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index bc53180c0e..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);
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 781ae3c6e0..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));
},
};
}
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 5a8ddc70a4..07119133b7 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -109,7 +109,7 @@ static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
result.pushKV("txid", tx->GetHash().GetHex());
if (add_to_wallet && !psbt_opt_in) {
- pwallet->CommitTransaction(tx, {}, /*orderForm*/ {});
+ pwallet->CommitTransaction(tx, {}, /*orderForm=*/{});
} else {
result.pushKV("hex", hex);
}
@@ -198,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;
@@ -293,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);
@@ -396,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);
@@ -838,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)));
@@ -1043,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
@@ -1241,7 +1241,7 @@ RPCHelpMan send()
// 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);
return FinishTransaction(pwallet, options, rawTx);
}
@@ -1667,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/spend.cpp b/src/wallet/spend.cpp
index b2839f2afe..9e508f3a32 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -463,7 +463,7 @@ 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)) {
@@ -783,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;
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 1251c9f37e..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
diff --git a/test/README.md b/test/README.md
index 87a3acb768..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
@@ -308,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).
@@ -320,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_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..4f8676ec53 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,11 @@ class RESTTest (BitcoinTestFramework):
json_obj = self.test_rest_request("/chaininfo")
assert_equal(json_obj['bestblockhash'], bb_hash)
+ # 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/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/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/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_runner.py b/test/functional/test_runner.py
index 39f4edb1ce..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',
@@ -306,6 +309,7 @@ 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',
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-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