diff --git a/.github/ci-test-each-commit-exec.sh b/.github/ci-test-each-commit-exec.sh index 1b081243a2..4e5240ca72 100755 --- a/.github/ci-test-each-commit-exec.sh +++ b/.github/ci-test-each-commit-exec.sh @@ -12,7 +12,7 @@ set -o errexit -o pipefail -o xtrace echo "Running test-one-commit on $( git log -1 )" # Use clang++, because it is a bit faster and uses less memory than g++ -CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_USDT=ON -DCMAKE_CXX_FLAGS='-Wno-error=unused-member-function' +CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_USDT=ON -DCMAKE_CXX_FLAGS='-Wno-error=unused-member-function' cmake --build build -j "$( nproc )" && ctest --output-on-failure --stop-on-failure --test-dir build -j "$( nproc )" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d8fdb4a16..77f0877c00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: git config user.name "CI" - run: | sudo apt-get update - sudo apt-get install clang ccache build-essential cmake ninja-build pkgconf python3-zmq libevent-dev libboost-dev libsqlite3-dev libdb++-dev systemtap-sdt-dev libzmq3-dev qt6-base-dev qt6-tools-dev qt6-l10n-tools libqrencode-dev -y + sudo apt-get install clang ccache build-essential cmake ninja-build pkgconf python3-zmq libevent-dev libboost-dev libsqlite3-dev systemtap-sdt-dev libzmq3-dev qt6-base-dev qt6-tools-dev qt6-l10n-tools libqrencode-dev -y - name: Compile and run tests run: | # Run tests on commits after the last merge commit and before the PR head commit @@ -175,7 +175,7 @@ jobs: job-type: [standard, fuzz] include: - job-type: standard - generate-options: '-DBUILD_GUI=ON -DWITH_BDB=ON -DWITH_ZMQ=ON -DBUILD_BENCH=ON -DWERROR=ON' + generate-options: '-DBUILD_GUI=ON -DWITH_ZMQ=ON -DBUILD_BENCH=ON -DWERROR=ON' job-name: 'Windows native, VS 2022' - job-type: fuzz generate-options: '-DVCPKG_MANIFEST_NO_DEFAULT_FEATURES=ON -DVCPKG_MANIFEST_FEATURES="wallet" -DBUILD_GUI=OFF -DBUILD_FOR_FUZZING=ON -DWERROR=ON' diff --git a/CMakeLists.txt b/CMakeLists.txt index 99490f742a..03caa0ffb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,22 +110,6 @@ if(ENABLE_WALLET) find_package(SQLite3 3.7.17 REQUIRED) endif() endif() -option(WITH_BDB "Enable Berkeley DB (BDB) wallet support." OFF) -cmake_dependent_option(WARN_INCOMPATIBLE_BDB "Warn when using a Berkeley DB (BDB) version other than 4.8." ON "WITH_BDB" OFF) -if(WITH_BDB) - find_package(BerkeleyDB 4.8 MODULE REQUIRED) - set(USE_BDB ON) - if(NOT BerkeleyDB_VERSION VERSION_EQUAL 4.8) - message(WARNING "Found Berkeley DB (BDB) other than 4.8.\n" - "BDB (legacy) wallets opened by this build will not be portable!" - ) - if(WARN_INCOMPATIBLE_BDB) - message(WARNING "If this is intended, pass \"-DWARN_INCOMPATIBLE_BDB=OFF\".\n" - "Passing \"-DWITH_BDB=OFF\" will suppress this warning." - ) - endif() - endif() -endif() cmake_dependent_option(BUILD_WALLET_TOOL "Build bitcoin-wallet tool." ${BUILD_TESTS} "ENABLE_WALLET" OFF) option(REDUCE_EXPORTS "Attempt to reduce exported symbols in the resulting executables." OFF) @@ -677,9 +661,6 @@ message(" bitcoin-chainstate (experimental) ... ${BUILD_UTIL_CHAINSTATE}") message(" libbitcoinkernel (experimental) ..... ${BUILD_KERNEL_LIB}") message("Optional features:") message(" wallet support ...................... ${ENABLE_WALLET}") -if(ENABLE_WALLET) - message(" - legacy wallets (Berkeley DB) ..... ${WITH_BDB}") -endif() message(" external signer ..................... ${ENABLE_EXTERNAL_SIGNER}") message(" ZeroMQ .............................. ${WITH_ZMQ}") if(ENABLE_IPC) diff --git a/CMakePresets.json b/CMakePresets.json index de40c87181..05b055d40b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -78,8 +78,6 @@ "BUILD_WALLET_TOOL": "ON", "ENABLE_EXTERNAL_SIGNER": "ON", "ENABLE_WALLET": "ON", - "WARN_INCOMPATIBLE_BDB": "OFF", - "WITH_BDB": "ON", "ENABLE_IPC": "ON", "WITH_QRENCODE": "ON", "WITH_USDT": "ON", diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh index bd67dba688..b2650ada0c 100755 --- a/ci/test/00_setup_env_native_asan.sh +++ b/ci/test/00_setup_env_native_asan.sh @@ -20,11 +20,11 @@ fi export CONTAINER_NAME=ci_native_asan export APT_LLVM_V="20" -export PACKAGES="systemtap-sdt-dev clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev python3-zmq qt6-base-dev qt6-tools-dev qt6-l10n-tools libevent-dev libboost-dev libdb5.3++-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE}" +export PACKAGES="systemtap-sdt-dev clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev python3-zmq qt6-base-dev qt6-tools-dev qt6-l10n-tools libevent-dev libboost-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE}" export NO_DEPENDS=1 export GOAL="install" export BITCOIN_CONFIG="\ - -DWITH_USDT=ON -DWITH_ZMQ=ON -DWITH_BDB=ON -DWARN_INCOMPATIBLE_BDB=OFF -DBUILD_GUI=ON \ + -DWITH_USDT=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON \ -DSANITIZERS=address,float-divide-by-zero,integer,undefined \ -DCMAKE_C_COMPILER=clang-${APT_LLVM_V} \ -DCMAKE_CXX_COMPILER=clang++-${APT_LLVM_V} \ diff --git a/ci/test/00_setup_env_native_fuzz_with_msan.sh b/ci/test/00_setup_env_native_fuzz_with_msan.sh index a6e53dc8a2..fe107a74ba 100755 --- a/ci/test/00_setup_env_native_fuzz_with_msan.sh +++ b/ci/test/00_setup_env_native_fuzz_with_msan.sh @@ -14,8 +14,7 @@ export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}" export CONTAINER_NAME="ci_native_fuzz_msan" export PACKAGES="ninja-build" -# BDB generates false-positives and will be removed in future -export DEP_OPTS="DEBUG=1 NO_BDB=1 NO_QT=1 CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'" +export DEP_OPTS="DEBUG=1 NO_QT=1 CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'" export GOAL="all" # Setting CMAKE_{C,CXX}_FLAGS_DEBUG flags to an empty string ensures that the flags set in MSAN_FLAGS remain unaltered. # _FORTIFY_SOURCE is not compatible with MSAN. diff --git a/ci/test/00_setup_env_native_msan.sh b/ci/test/00_setup_env_native_msan.sh index 8784aaa5b7..756c3d3027 100755 --- a/ci/test/00_setup_env_native_msan.sh +++ b/ci/test/00_setup_env_native_msan.sh @@ -14,8 +14,7 @@ export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}" export CONTAINER_NAME="ci_native_msan" export PACKAGES="ninja-build" -# BDB generates false-positives and will be removed in future -export DEP_OPTS="DEBUG=1 NO_BDB=1 NO_QT=1 CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'" +export DEP_OPTS="DEBUG=1 NO_QT=1 CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'" export GOAL="install" # Setting CMAKE_{C,CXX}_FLAGS_DEBUG flags to an empty string ensures that the flags set in MSAN_FLAGS remain unaltered. # _FORTIFY_SOURCE is not compatible with MSAN. diff --git a/ci/test/00_setup_env_native_tidy.sh b/ci/test/00_setup_env_native_tidy.sh index 0a593a2182..d67616c1c6 100755 --- a/ci/test/00_setup_env_native_tidy.sh +++ b/ci/test/00_setup_env_native_tidy.sh @@ -10,7 +10,7 @@ export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:24.04" export CONTAINER_NAME=ci_native_tidy export TIDY_LLVM_V="20" export APT_LLVM_V="${TIDY_LLVM_V}" -export PACKAGES="clang-${TIDY_LLVM_V} libclang-${TIDY_LLVM_V}-dev llvm-${TIDY_LLVM_V}-dev libomp-${TIDY_LLVM_V}-dev clang-tidy-${TIDY_LLVM_V} jq libevent-dev libboost-dev libzmq3-dev systemtap-sdt-dev qt6-base-dev qt6-tools-dev qt6-l10n-tools libqrencode-dev libsqlite3-dev libdb++-dev" +export PACKAGES="clang-${TIDY_LLVM_V} libclang-${TIDY_LLVM_V}-dev llvm-${TIDY_LLVM_V}-dev libomp-${TIDY_LLVM_V}-dev clang-tidy-${TIDY_LLVM_V} jq libevent-dev libboost-dev libzmq3-dev systemtap-sdt-dev qt6-base-dev qt6-tools-dev qt6-l10n-tools libqrencode-dev libsqlite3-dev" export NO_DEPENDS=1 export RUN_UNIT_TESTS=false export RUN_FUNCTIONAL_TESTS=false @@ -19,7 +19,7 @@ export RUN_CHECK_DEPS=true export RUN_TIDY=true export GOAL="install" export BITCOIN_CONFIG="\ - -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DWITH_USDT=ON -DWITH_BDB=ON -DWARN_INCOMPATIBLE_BDB=OFF \ + -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DWITH_USDT=ON \ -DCMAKE_C_COMPILER=clang-${TIDY_LLVM_V} \ -DCMAKE_CXX_COMPILER=clang++-${TIDY_LLVM_V} \ -DCMAKE_C_FLAGS_RELWITHDEBINFO='-O0 -g0' \ diff --git a/ci/test/00_setup_env_native_valgrind.sh b/ci/test/00_setup_env_native_valgrind.sh index 347a113505..f57a5a71b8 100755 --- a/ci/test/00_setup_env_native_valgrind.sh +++ b/ci/test/00_setup_env_native_valgrind.sh @@ -8,12 +8,12 @@ export LC_ALL=C.UTF-8 export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:24.04" export CONTAINER_NAME=ci_native_valgrind -export PACKAGES="valgrind python3-zmq libevent-dev libboost-dev libdb5.3++-dev libzmq3-dev libsqlite3-dev" +export PACKAGES="valgrind python3-zmq libevent-dev libboost-dev libzmq3-dev libsqlite3-dev" export USE_VALGRIND=1 export NO_DEPENDS=1 export TEST_RUNNER_EXTRA="--exclude feature_init,rpc_bind,feature_bind_extra" # feature_init excluded for now, see https://github.com/bitcoin/bitcoin/issues/30011 ; bind tests excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547 export GOAL="install" # TODO enable GUI export BITCOIN_CONFIG="\ - -DWITH_ZMQ=ON -DWITH_BDB=ON -DWARN_INCOMPATIBLE_BDB=OFF -DBUILD_GUI=OFF \ + -DWITH_ZMQ=ON -DBUILD_GUI=OFF \ " diff --git a/cmake/bitcoin-build-config.h.in b/cmake/bitcoin-build-config.h.in index 41bd5d33d9..ef846fd0b7 100644 --- a/cmake/bitcoin-build-config.h.in +++ b/cmake/bitcoin-build-config.h.in @@ -123,9 +123,6 @@ /* Define to 1 if strerror_r returns char *. */ #cmakedefine STRERROR_R_CHAR_P 1 -/* Define if BDB support should be compiled in */ -#cmakedefine USE_BDB 1 - /* Define if dbus support should be compiled in */ #cmakedefine USE_DBUS 1 diff --git a/cmake/module/FindBerkeleyDB.cmake b/cmake/module/FindBerkeleyDB.cmake deleted file mode 100644 index 03a3cce10c..0000000000 --- a/cmake/module/FindBerkeleyDB.cmake +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright (c) 2023-present The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or https://opensource.org/license/mit/. - -#[=======================================================================[ -FindBerkeleyDB --------------- - -Finds the Berkeley DB headers and library. - -Imported Targets -^^^^^^^^^^^^^^^^ - -This module provides imported target ``BerkeleyDB::BerkeleyDB``, if -Berkeley DB has been found. - -Result Variables -^^^^^^^^^^^^^^^^ - -This module defines the following variables: - -``BerkeleyDB_FOUND`` - "True" if Berkeley DB found. - -``BerkeleyDB_VERSION`` - The MAJOR.MINOR version of Berkeley DB found. - -#]=======================================================================] - -set(_BerkeleyDB_homebrew_prefix) -if(CMAKE_HOST_APPLE) - find_program(HOMEBREW_EXECUTABLE brew) - if(HOMEBREW_EXECUTABLE) - # The Homebrew package manager installs the berkeley-db* packages as - # "keg-only", which means they are not symlinked into the default prefix. - # To find such a package, the find_path() and find_library() commands - # need additional path hints that are computed by Homebrew itself. - execute_process( - COMMAND ${HOMEBREW_EXECUTABLE} --prefix berkeley-db@4 - OUTPUT_VARIABLE _BerkeleyDB_homebrew_prefix - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - endif() -endif() - -find_path(BerkeleyDB_INCLUDE_DIR - NAMES db_cxx.h - HINTS ${_BerkeleyDB_homebrew_prefix}/include - PATH_SUFFIXES 4.8 48 db4.8 4 db4 5.3 db5.3 5 db5 -) -mark_as_advanced(BerkeleyDB_INCLUDE_DIR) -unset(_BerkeleyDB_homebrew_prefix) - -if(NOT BerkeleyDB_LIBRARY) - if(VCPKG_TARGET_TRIPLET) - # The vcpkg package manager installs the berkeleydb package with the same name - # of release and debug libraries. Therefore, the default search paths set by - # vcpkg's toolchain file cannot be used to search libraries as the debug one - # will always be found. - set(CMAKE_FIND_USE_CMAKE_PATH FALSE) - endif() - - get_filename_component(_BerkeleyDB_lib_hint "${BerkeleyDB_INCLUDE_DIR}" DIRECTORY) - - find_library(BerkeleyDB_LIBRARY_RELEASE - NAMES db_cxx-4.8 db4_cxx db48 db_cxx-5.3 db_cxx-5 db_cxx libdb48 - NAMES_PER_DIR - HINTS ${_BerkeleyDB_lib_hint} - PATH_SUFFIXES lib - ) - mark_as_advanced(BerkeleyDB_LIBRARY_RELEASE) - - find_library(BerkeleyDB_LIBRARY_DEBUG - NAMES db_cxx-4.8 db4_cxx db48 db_cxx-5.3 db_cxx-5 db_cxx libdb48 - NAMES_PER_DIR - HINTS ${_BerkeleyDB_lib_hint} - PATH_SUFFIXES debug/lib - ) - mark_as_advanced(BerkeleyDB_LIBRARY_DEBUG) - - unset(_BerkeleyDB_lib_hint) - unset(CMAKE_FIND_USE_CMAKE_PATH) - - include(SelectLibraryConfigurations) - select_library_configurations(BerkeleyDB) - # The select_library_configurations() command sets BerkeleyDB_FOUND, but we - # want the one from the find_package_handle_standard_args() command below. - unset(BerkeleyDB_FOUND) -endif() - -if(BerkeleyDB_INCLUDE_DIR) - file(STRINGS "${BerkeleyDB_INCLUDE_DIR}/db.h" _BerkeleyDB_version_strings REGEX "^#define[\t ]+DB_VERSION_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+.*") - string(REGEX REPLACE ".*#define[\t ]+DB_VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" _BerkeleyDB_version_major "${_BerkeleyDB_version_strings}") - string(REGEX REPLACE ".*#define[\t ]+DB_VERSION_MINOR[ \t]+([0-9]+).*" "\\1" _BerkeleyDB_version_minor "${_BerkeleyDB_version_strings}") - string(REGEX REPLACE ".*#define[\t ]+DB_VERSION_PATCH[ \t]+([0-9]+).*" "\\1" _BerkeleyDB_version_patch "${_BerkeleyDB_version_strings}") - unset(_BerkeleyDB_version_strings) - # The MAJOR.MINOR.PATCH version will be logged in the following find_package_handle_standard_args() command. - set(_BerkeleyDB_full_version ${_BerkeleyDB_version_major}.${_BerkeleyDB_version_minor}.${_BerkeleyDB_version_patch}) - set(BerkeleyDB_VERSION ${_BerkeleyDB_version_major}.${_BerkeleyDB_version_minor}) - unset(_BerkeleyDB_version_major) - unset(_BerkeleyDB_version_minor) - unset(_BerkeleyDB_version_patch) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(BerkeleyDB - REQUIRED_VARS BerkeleyDB_LIBRARY BerkeleyDB_INCLUDE_DIR - VERSION_VAR _BerkeleyDB_full_version -) -unset(_BerkeleyDB_full_version) - -if(BerkeleyDB_FOUND AND NOT TARGET BerkeleyDB::BerkeleyDB) - add_library(BerkeleyDB::BerkeleyDB UNKNOWN IMPORTED) - set_target_properties(BerkeleyDB::BerkeleyDB PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${BerkeleyDB_INCLUDE_DIR}" - ) - if(BerkeleyDB_LIBRARY_RELEASE) - set_property(TARGET BerkeleyDB::BerkeleyDB APPEND PROPERTY - IMPORTED_CONFIGURATIONS RELEASE - ) - set_target_properties(BerkeleyDB::BerkeleyDB PROPERTIES - IMPORTED_LOCATION_RELEASE "${BerkeleyDB_LIBRARY_RELEASE}" - ) - endif() - if(BerkeleyDB_LIBRARY_DEBUG) - set_property(TARGET BerkeleyDB::BerkeleyDB APPEND PROPERTY - IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties(BerkeleyDB::BerkeleyDB PROPERTIES - IMPORTED_LOCATION_DEBUG "${BerkeleyDB_LIBRARY_DEBUG}" - ) - endif() -endif() diff --git a/contrib/completions/bash/bitcoin-cli.bash b/contrib/completions/bash/bitcoin-cli.bash index b04fdbcb0e..3018d076c6 100644 --- a/contrib/completions/bash/bitcoin-cli.bash +++ b/contrib/completions/bash/bitcoin-cli.bash @@ -39,7 +39,7 @@ _bitcoin_cli() { if ((cword > 4)); then case ${words[cword-4]} in - importaddress|listtransactions|setban) + listtransactions|setban) COMPREPLY=( $( compgen -W "true false" -- "$cur" ) ) return 0 ;; @@ -52,10 +52,7 @@ _bitcoin_cli() { if ((cword > 3)); then case ${words[cword-3]} in - addmultisigaddress) - return 0 - ;; - getbalance|gettxout|importaddress|importpubkey|importprivkey|listreceivedbyaddress|listsinceblock) + getbalance|gettxout|listreceivedbyaddress|listsinceblock) COMPREPLY=( $( compgen -W "true false" -- "$cur" ) ) return 0 ;; @@ -80,7 +77,7 @@ _bitcoin_cli() { fi case "$prev" in - backupwallet|dumpwallet|importwallet) + backupwallet) _filedir return 0 ;; diff --git a/contrib/devtools/check-deps.sh b/contrib/devtools/check-deps.sh index 25e948647c..0ae817254d 100755 --- a/contrib/devtools/check-deps.sh +++ b/contrib/devtools/check-deps.sh @@ -41,9 +41,6 @@ ALLOWED_DEPENDENCIES+=( # Declare list of known errors that should be suppressed. declare -A SUPPRESS -# init.cpp file currently calls Berkeley DB sanity check function on startup, so -# there is an undocumented dependency of the node library on the wallet library. -SUPPRESS["init.cpp.o bdb.cpp.o _ZN6wallet27BerkeleyDatabaseSanityCheckEv"]=1 # init/common.cpp file calls InitError and InitWarning from interface_ui which # is currently part of the node library. interface_ui should just be part of the # common library instead, and is moved in diff --git a/contrib/valgrind.supp b/contrib/valgrind.supp index a0a82d0501..7960f9c8e4 100644 --- a/contrib/valgrind.supp +++ b/contrib/valgrind.supp @@ -14,44 +14,12 @@ # Note that suppressions may depend on OS and/or library versions. # Tested on aarch64 and x86_64 with Ubuntu Noble system libs, using clang-16 # and GCC, without gui. -{ - Suppress libdb warning - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=662917 - Memcheck:Cond - obj:*/libdb_cxx-*.so - fun:__log_put -} -{ - Suppress libdb warning - Memcheck:Param - pwrite64(buf) - fun:pwrite - fun:__os_io -} -{ - Suppress libdb warning - Memcheck:Cond - fun:__log_putr.isra.1 -} -{ - Suppress libdb warning - Memcheck:Param - pwrite64(buf) - ... - obj:*/libdb_cxx-*.so -} { Suppress uninitialized bytes warning in compat code Memcheck:Param ioctl(TCSET{S,SW,SF}) fun:tcsetattr } -{ - Suppress libdb warning - Memcheck:Leak - fun:malloc - ... - obj:*/libdb_cxx-*.so -} { Suppress leaks on shutdown Memcheck:Leak diff --git a/depends/Makefile b/depends/Makefile index 9eb00a425a..9767b8eb29 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -36,7 +36,6 @@ NO_BOOST ?= NO_LIBEVENT ?= NO_QT ?= NO_QR ?= -NO_BDB ?= NO_WALLET ?= NO_ZMQ ?= NO_USDT ?= @@ -159,8 +158,7 @@ qrencode_packages_$(NO_QR) = $(qrencode_$(host_os)_packages) qt_packages_$(NO_QT) = $(qt_packages) $(qt_$(host_os)_packages) $(qt_$(host_arch)_$(host_os)_packages) $(qrencode_packages_) qt_native_packages_$(NO_QT) = $(qt_native_packages) -bdb_packages_$(NO_BDB) = $(bdb_packages) -wallet_packages_$(NO_WALLET) = $(bdb_packages_) $(sqlite_packages) +wallet_packages_$(NO_WALLET) = $(sqlite_packages) zmq_packages_$(NO_ZMQ) = $(zmq_packages) multiprocess_packages_$(MULTIPROCESS) = $(multiprocess_packages) @@ -232,7 +230,6 @@ $(host_prefix)/toolchain.cmake : toolchain.cmake.in $(host_prefix)/.stamp_$(fina -e 's|@qrencode_packages@|$(qrencode_packages_)|' \ -e 's|@zmq_packages@|$(zmq_packages_)|' \ -e 's|@wallet_packages@|$(wallet_packages_)|' \ - -e 's|@bdb_packages@|$(bdb_packages_)|' \ -e 's|@usdt_packages@|$(usdt_packages_)|' \ -e 's|@multiprocess@|$(MULTIPROCESS)|' \ $< > $@ diff --git a/depends/README.md b/depends/README.md index b72cf9c13a..b1247b319e 100644 --- a/depends/README.md +++ b/depends/README.md @@ -79,7 +79,6 @@ The following can be set when running make: `make FOO=bar` - `NO_QR`: Don't download/build/cache packages needed for enabling qrencode - `NO_ZMQ`: Don't download/build/cache packages needed for enabling ZeroMQ - `NO_WALLET`: Don't download/build/cache libs needed to enable the wallet (SQLite) -- `NO_BDB`: Don't download/build/cache BerkeleyDB - `NO_USDT`: Don't download/build/cache packages needed for enabling USDT tracepoints - `MULTIPROCESS`: Build libmultiprocess (experimental) - `DEBUG`: Disable some optimizations and enable more runtime checking diff --git a/depends/packages/bdb.mk b/depends/packages/bdb.mk deleted file mode 100644 index be82b0d309..0000000000 --- a/depends/packages/bdb.mk +++ /dev/null @@ -1,33 +0,0 @@ -package=bdb -$(package)_version=4.8.30 -$(package)_download_path=https://download.oracle.com/berkeley-db -$(package)_file_name=db-$($(package)_version).NC.tar.gz -$(package)_sha256_hash=12edc0df75bf9abd7f82f821795bcee50f42cb2e5f76a6a281b85732798364ef -$(package)_build_subdir=build_unix -$(package)_patches=clang_cxx_11.patch - -define $(package)_set_vars -$(package)_config_opts=--disable-shared --enable-cxx --disable-replication --enable-option-checking -$(package)_config_opts_mingw32=--enable-mingw -$(package)_cflags+=-Wno-error=implicit-function-declaration -Wno-error=format-security -Wno-error=implicit-int -$(package)_cppflags_freebsd=-D_XOPEN_SOURCE=600 -D__BSD_VISIBLE=1 -$(package)_cppflags_netbsd=-D_XOPEN_SOURCE=600 -$(package)_cppflags_mingw32=-DUNICODE -D_UNICODE -endef - -define $(package)_preprocess_cmds - patch -p1 < $($(package)_patch_dir)/clang_cxx_11.patch && \ - cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub dist -endef - -define $(package)_config_cmds - ../dist/$($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) libdb_cxx-4.8.a libdb-4.8.a -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install_lib install_include -endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index dddf833963..7802cd915e 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -15,7 +15,6 @@ ifneq ($(host),$(build)) qt_native_packages := native_qt endif -bdb_packages=bdb sqlite_packages=sqlite zmq_packages=zeromq diff --git a/depends/patches/bdb/clang_cxx_11.patch b/depends/patches/bdb/clang_cxx_11.patch deleted file mode 100644 index 58f7ddc7d5..0000000000 --- a/depends/patches/bdb/clang_cxx_11.patch +++ /dev/null @@ -1,147 +0,0 @@ -commit 3311d68f11d1697565401eee6efc85c34f022ea7 -Author: fanquake -Date: Mon Aug 17 20:03:56 2020 +0800 - - Fix C++11 compatibility - -diff --git a/dbinc/atomic.h b/dbinc/atomic.h -index 0034dcc..7c11d4a 100644 ---- a/dbinc/atomic.h -+++ b/dbinc/atomic.h -@@ -70,7 +70,7 @@ typedef struct { - * These have no memory barriers; the caller must include them when necessary. - */ - #define atomic_read(p) ((p)->value) --#define atomic_init(p, val) ((p)->value = (val)) -+#define atomic_init_db(p, val) ((p)->value = (val)) - - #ifdef HAVE_ATOMIC_SUPPORT - -@@ -144,7 +144,7 @@ typedef LONG volatile *interlocked_val; - #define atomic_inc(env, p) __atomic_inc(p) - #define atomic_dec(env, p) __atomic_dec(p) - #define atomic_compare_exchange(env, p, o, n) \ -- __atomic_compare_exchange((p), (o), (n)) -+ __atomic_compare_exchange_db((p), (o), (n)) - static inline int __atomic_inc(db_atomic_t *p) - { - int temp; -@@ -176,7 +176,7 @@ static inline int __atomic_dec(db_atomic_t *p) - * http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html - * which configure could be changed to use. - */ --static inline int __atomic_compare_exchange( -+static inline int __atomic_compare_exchange_db( - db_atomic_t *p, atomic_value_t oldval, atomic_value_t newval) - { - atomic_value_t was; -@@ -206,7 +206,7 @@ static inline int __atomic_compare_exchange( - #define atomic_dec(env, p) (--(p)->value) - #define atomic_compare_exchange(env, p, oldval, newval) \ - (DB_ASSERT(env, atomic_read(p) == (oldval)), \ -- atomic_init(p, (newval)), 1) -+ atomic_init_db(p, (newval)), 1) - #else - #define atomic_inc(env, p) __atomic_inc(env, p) - #define atomic_dec(env, p) __atomic_dec(env, p) -diff --git a/mp/mp_fget.c b/mp/mp_fget.c -index 5fdee5a..0b75f57 100644 ---- a/mp/mp_fget.c -+++ b/mp/mp_fget.c -@@ -617,7 +617,7 @@ alloc: /* Allocate a new buffer header and data space. */ - - /* Initialize enough so we can call __memp_bhfree. */ - alloc_bhp->flags = 0; -- atomic_init(&alloc_bhp->ref, 1); -+ atomic_init_db(&alloc_bhp->ref, 1); - #ifdef DIAGNOSTIC - if ((uintptr_t)alloc_bhp->buf & (sizeof(size_t) - 1)) { - __db_errx(env, -@@ -911,7 +911,7 @@ alloc: /* Allocate a new buffer header and data space. */ - MVCC_MPROTECT(bhp->buf, mfp->stat.st_pagesize, - PROT_READ); - -- atomic_init(&alloc_bhp->ref, 1); -+ atomic_init_db(&alloc_bhp->ref, 1); - MUTEX_LOCK(env, alloc_bhp->mtx_buf); - alloc_bhp->priority = bhp->priority; - alloc_bhp->pgno = bhp->pgno; -diff --git a/mp/mp_mvcc.c b/mp/mp_mvcc.c -index 34467d2..f05aa0c 100644 ---- a/mp/mp_mvcc.c -+++ b/mp/mp_mvcc.c -@@ -276,7 +276,7 @@ __memp_bh_freeze(dbmp, infop, hp, bhp, need_frozenp) - #else - memcpy(frozen_bhp, bhp, SSZA(BH, buf)); - #endif -- atomic_init(&frozen_bhp->ref, 0); -+ atomic_init_db(&frozen_bhp->ref, 0); - if (mutex != MUTEX_INVALID) - frozen_bhp->mtx_buf = mutex; - else if ((ret = __mutex_alloc(env, MTX_MPOOL_BH, -@@ -428,7 +428,7 @@ __memp_bh_thaw(dbmp, infop, hp, frozen_bhp, alloc_bhp) - #endif - alloc_bhp->mtx_buf = mutex; - MUTEX_LOCK(env, alloc_bhp->mtx_buf); -- atomic_init(&alloc_bhp->ref, 1); -+ atomic_init_db(&alloc_bhp->ref, 1); - F_CLR(alloc_bhp, BH_FROZEN); - } - -diff --git a/mp/mp_region.c b/mp/mp_region.c -index e6cece9..ddbe906 100644 ---- a/mp/mp_region.c -+++ b/mp/mp_region.c -@@ -224,7 +224,7 @@ __memp_init(env, dbmp, reginfo_off, htab_buckets, max_nreg) - MTX_MPOOL_FILE_BUCKET, 0, &htab[i].mtx_hash)) != 0) - return (ret); - SH_TAILQ_INIT(&htab[i].hash_bucket); -- atomic_init(&htab[i].hash_page_dirty, 0); -+ atomic_init_db(&htab[i].hash_page_dirty, 0); - } - - /* -@@ -269,7 +269,7 @@ __memp_init(env, dbmp, reginfo_off, htab_buckets, max_nreg) - hp->mtx_hash = (mtx_base == MUTEX_INVALID) ? MUTEX_INVALID : - mtx_base + i; - SH_TAILQ_INIT(&hp->hash_bucket); -- atomic_init(&hp->hash_page_dirty, 0); -+ atomic_init_db(&hp->hash_page_dirty, 0); - #ifdef HAVE_STATISTICS - hp->hash_io_wait = 0; - hp->hash_frozen = hp->hash_thawed = hp->hash_frozen_freed = 0; -diff --git a/mutex/mut_method.c b/mutex/mut_method.c -index 2588763..5c6d516 100644 ---- a/mutex/mut_method.c -+++ b/mutex/mut_method.c -@@ -426,7 +426,7 @@ atomic_compare_exchange(env, v, oldval, newval) - MUTEX_LOCK(env, mtx); - ret = atomic_read(v) == oldval; - if (ret) -- atomic_init(v, newval); -+ atomic_init_db(v, newval); - MUTEX_UNLOCK(env, mtx); - - return (ret); -diff --git a/mutex/mut_tas.c b/mutex/mut_tas.c -index f3922e0..e40fcdf 100644 ---- a/mutex/mut_tas.c -+++ b/mutex/mut_tas.c -@@ -46,7 +46,7 @@ __db_tas_mutex_init(env, mutex, flags) - - #ifdef HAVE_SHARED_LATCHES - if (F_ISSET(mutexp, DB_MUTEX_SHARED)) -- atomic_init(&mutexp->sharecount, 0); -+ atomic_init_db(&mutexp->sharecount, 0); - else - #endif - if (MUTEX_INIT(&mutexp->tas)) { -@@ -486,7 +486,7 @@ __db_tas_mutex_unlock(env, mutex) - F_CLR(mutexp, DB_MUTEX_LOCKED); - /* Flush flag update before zeroing count */ - MEMBAR_EXIT(); -- atomic_init(&mutexp->sharecount, 0); -+ atomic_init_db(&mutexp->sharecount, 0); - } else { - DB_ASSERT(env, sharecount > 0); - MEMBAR_EXIT(); diff --git a/depends/toolchain.cmake.in b/depends/toolchain.cmake.in index d247a80c9c..62dcd8d2c5 100644 --- a/depends/toolchain.cmake.in +++ b/depends/toolchain.cmake.in @@ -144,13 +144,6 @@ else() set(ENABLE_WALLET ON CACHE BOOL "") endif() -set(bdb_packages @bdb_packages@) -if("${wallet_packages}" STREQUAL "" OR "${bdb_packages}" STREQUAL "") - set(WITH_BDB OFF CACHE BOOL "") -else() - set(WITH_BDB ON CACHE BOOL "") -endif() - set(usdt_packages @usdt_packages@) if("${usdt_packages}" STREQUAL "") set(WITH_USDT OFF CACHE BOOL "") diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md index 432a2ee2be..ba3b5cd595 100644 --- a/doc/build-freebsd.md +++ b/doc/build-freebsd.md @@ -34,33 +34,6 @@ Skip if you don't intend to use descriptor wallets. pkg install sqlite3 ``` -###### Legacy Wallet Support -BerkeleyDB is only required if legacy wallet support is required. - -It is required to use Berkeley DB 4.8. You **cannot** use the BerkeleyDB library -from ports. However, you can build DB 4.8 yourself [using depends](/depends). - -```bash -pkg install gmake -gmake -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_ZMQ=1 NO_USDT=1 -``` - -When the build is complete, the Berkeley DB installation location will be displayed: - -``` -to: /path/to/bitcoin/depends/x86_64-unknown-freebsd[release-number] -``` - -Finally, set `BDB_PREFIX` to this path according to your shell: - -``` -csh: setenv BDB_PREFIX [path displayed above] -``` - -``` -sh/bash: export BDB_PREFIX=[path displayed above] -``` - #### GUI Dependencies ###### Qt6 @@ -107,20 +80,13 @@ pkg install python3 databases/py-sqlite3 net/py-pyzmq There are many ways to configure Bitcoin Core, here are a few common examples: ##### Descriptor Wallet and GUI: -This disables legacy wallet support and enables the GUI, assuming `sqlite` and `qt` are installed. +This enables the GUI, assuming `sqlite` and `qt` are installed. ```bash -cmake -B build -DWITH_BDB=OFF -DBUILD_GUI=ON +cmake -B build -DBUILD_GUI=ON ``` Run `cmake -B build -LH` to see the full list of available options. -##### Descriptor & Legacy Wallet. No GUI: -This enables support for both wallet types, assuming -`sqlite3` and `db4` are both installed. -```bash -cmake -B build -DBerkeleyDB_INCLUDE_DIR:PATH="${BDB_PREFIX}/include" -DWITH_BDB=ON -``` - ##### No Wallet or GUI ```bash cmake -B build -DENABLE_WALLET=OFF diff --git a/doc/build-netbsd.md b/doc/build-netbsd.md index 31cac3f6b6..da51c8744a 100644 --- a/doc/build-netbsd.md +++ b/doc/build-netbsd.md @@ -55,14 +55,6 @@ It is not necessary to build wallet functionality to run bitcoind or the GUI. pkgin install sqlite3 ``` -###### Legacy Wallet Support - -`db4` is required to enable support for legacy wallets. - -```bash -pkgin install db4 -``` - #### GUI Dependencies ###### Qt6 diff --git a/doc/build-openbsd.md b/doc/build-openbsd.md index 8beda3259f..8929e3b742 100644 --- a/doc/build-openbsd.md +++ b/doc/build-openbsd.md @@ -26,35 +26,13 @@ git clone https://github.com/bitcoin/bitcoin.git #### Wallet Dependencies It is not necessary to build wallet functionality to run either `bitcoind` or `bitcoin-qt`. +SQLite is required to build the wallet. -###### Descriptor Wallet Support - -SQLite is required to support [descriptor wallets](descriptors.md). ``` bash pkg_add sqlite3 ``` -###### 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. However you can build it yourself, [using depends](/depends). - -Refer to [depends/README.md](/depends/README.md) for detailed instructions. - -```bash -gmake -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_ZMQ=1 NO_USDT=1 -... -to: /path/to/bitcoin/depends/*-unknown-openbsd* -``` - -Then set `BDB_PREFIX`: - -```bash -export BDB_PREFIX="[path displayed above]" -``` - #### GUI Dependencies ###### Qt6 @@ -108,13 +86,6 @@ cmake -B build -DBUILD_GUI=ON Run `cmake -B build -LH` to see the full list of available options. -##### Descriptor & Legacy Wallet. No GUI: -This enables support for both wallet types: - -```bash -cmake -B build -DBerkeleyDB_INCLUDE_DIR:PATH="${BDB_PREFIX}/include" -DWITH_BDB=ON -``` - ### 2. Compile ```bash diff --git a/doc/build-osx.md b/doc/build-osx.md index eb0dacb956..b30568b50c 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -74,14 +74,6 @@ It is not necessary to build wallet functionality to run `bitcoind` or `bitcoin macOS ships with a useable `sqlite` package, meaning you don't need to install anything. -###### Legacy Wallet Support - -`berkeley-db@4` is only required to support for legacy wallets. -Skip if you don't intend to use legacy wallets. - -``` bash -brew install berkeley-db@4 -``` --- #### GUI Dependencies @@ -160,14 +152,6 @@ It is required that you have `python` and `zip` installed. There are many ways to configure Bitcoin Core, here are a few common examples: -##### Wallet (BDB + SQlite) Support, No GUI: - -If `berkeley-db@4` or `sqlite` are not installed, this will throw an error. - -``` bash -cmake -B build -DWITH_BDB=ON -``` - ##### Wallet (only SQlite) and GUI Support: This enables the GUI. diff --git a/doc/build-unix.md b/doc/build-unix.md index c7dfefc271..d8857d43a7 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -54,10 +54,6 @@ SQLite is required for the descriptor wallet: sudo apt install libsqlite3-dev -Berkeley DB is only required for the legacy wallet. Ubuntu and Debian have their own `libdb-dev` and `libdb++-dev` packages, -but these will install Berkeley DB 5.3 or later. This will break binary wallet compatibility with the distributed -executables, which are based on BerkeleyDB 4.8. Otherwise, you can build Berkeley DB [yourself](#berkeley-db). - To build Bitcoin Core without wallet, see [*Disable-wallet mode*](#disable-wallet-mode) ZMQ dependencies (provides ZMQ API): @@ -109,10 +105,6 @@ SQLite is required for the descriptor wallet: sudo dnf install sqlite-devel -Berkeley DB is only required for the legacy wallet. Fedora releases have only `libdb-devel` and `libdb-cxx-devel` packages, but these will install -Berkeley DB 5.3 or later. This will break binary wallet compatibility with the distributed executables, which -are based on Berkeley DB 4.8. Otherwise, you can build Berkeley DB [yourself](#berkeley-db). - To build Bitcoin Core without wallet, see [*Disable-wallet mode*](#disable-wallet-mode) ZMQ dependencies (provides ZMQ API): @@ -153,27 +145,6 @@ See [dependencies.md](dependencies.md) for a complete overview, and [depends](/depends/README.md) on how to compile them yourself, if you wish to not use the packages of your Linux distribution. -### Berkeley DB - -The legacy wallet uses Berkeley DB. To ensure backwards compatibility it is -recommended to use Berkeley DB 4.8. If you have to build it yourself, and don't -want to use any other libraries built in depends, you can do: -```bash -make -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_ZMQ=1 NO_USDT=1 -... -to: /path/to/bitcoin/depends/x86_64-pc-linux-gnu -``` -and configure using the following: -```bash -export BDB_PREFIX="/path/to/bitcoin/depends/x86_64-pc-linux-gnu" - -cmake -B build -DBerkeleyDB_INCLUDE_DIR:PATH="${BDB_PREFIX}/include" -DWITH_BDB=ON -``` - -**Note**: Make sure that `BDB_PREFIX` is an absolute path. - -**Note**: You only need Berkeley DB if the legacy wallet is enabled (see [*Disable-wallet mode*](#disable-wallet-mode)). - Disable-wallet mode -------------------- When the intention is to only run a P2P node, without a wallet, Bitcoin Core can @@ -181,7 +152,7 @@ be compiled in disable-wallet mode with: cmake -B build -DENABLE_WALLET=OFF -In this case there is no dependency on SQLite or Berkeley DB. +In this case there is no dependency on SQLite. Mining is also possible in disable-wallet mode using the `getblocktemplate` RPC call. @@ -204,4 +175,3 @@ This example lists the steps necessary to setup and build a command line only di ctest --test-dir build ./build/bin/bitcoind -If you intend to work with legacy Berkeley DB wallets, see [Berkeley DB](#berkeley-db) section. diff --git a/doc/dependencies.md b/doc/dependencies.md index 332e6d8700..ba97289525 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -32,7 +32,6 @@ Bitcoin Core requires one of the following compilers. | [qrencode](../depends/packages/qrencode.mk) (gui) | [link](https://fukuchi.org/works/qrencode/) | [4.1.1](https://github.com/bitcoin/bitcoin/pull/27312) | N/A | No | | [Qt](../depends/packages/qt.mk) (gui) | [link](https://download.qt.io/archive/qt/) | [6.7.3](https://github.com/bitcoin/bitcoin/pull/30997) | [6.2](https://github.com/bitcoin/bitcoin/pull/30997) | No | | [ZeroMQ](../depends/packages/zeromq.mk) (notifications) | [link](https://github.com/zeromq/libzmq/releases) | [4.3.4](https://github.com/bitcoin/bitcoin/pull/23956) | 4.0.0 | No | -| [Berkeley DB](../depends/packages/bdb.mk) (legacy wallet) | [link](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.30 | 4.8.x | No | | [SQLite](../depends/packages/sqlite.mk) (wallet) | [link](https://sqlite.org) | [3.38.5](https://github.com/bitcoin/bitcoin/pull/25378) | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | No | | Python (scripts, tests) | [link](https://www.python.org) | N/A | [3.10](https://github.com/bitcoin/bitcoin/pull/30527) | No | | [systemtap](../depends/packages/systemtap.mk) ([tracing](tracing.md)) | [link](https://sourceware.org/systemtap/) | [4.8](https://github.com/bitcoin/bitcoin/pull/26945)| N/A | No | diff --git a/doc/descriptors.md b/doc/descriptors.md index 625a511bba..928e8806c6 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -11,8 +11,6 @@ Supporting RPCs are: addresses. - `listunspent` outputs a specialized descriptor for the reported unspent outputs. - `getaddressinfo` outputs a descriptor for solvable addresses (since v0.18). -- `importmulti` takes as input descriptors to import into a legacy wallet - (since v0.18). - `generatetodescriptor` takes as input a descriptor and generates coins to it (`regtest` only, since v0.19). - `utxoupdatepsbt` takes as input descriptors to add information to the psbt @@ -319,5 +317,5 @@ roughly 1 in a trillion chance of not detecting the errors. All RPCs in Bitcoin Core will include the checksum in their output. Only certain RPCs require checksums on input, including `deriveaddresses` and -`importmulti`. The checksum for a descriptor without one can be computed +`importdescriptors`. The checksum for a descriptor without one can be computed using the `getdescriptorinfo` RPC. diff --git a/doc/files.md b/doc/files.md index b738d6055a..1b93ab797d 100644 --- a/doc/files.md +++ b/doc/files.md @@ -8,14 +8,14 @@ - [Multi-wallet environment](#multi-wallet-environment) - - [Berkeley DB database based wallets](#berkeley-db-database-based-wallets) - - [SQLite database based wallets](#sqlite-database-based-wallets) - [GUI settings](#gui-settings) - [Legacy subdirectories and files](#legacy-subdirectories-and-files) + - [Berkeley DB database based wallets](#berkeley-db-database-based-wallets) + - [Notes](#notes) ## Data directory location @@ -75,7 +75,7 @@ Subdirectory | File(s) | Description ## Multi-wallet environment -Wallets are Berkeley DB (BDB) or SQLite databases. +Wallets are SQLite databases. 1. Each user-defined wallet named "wallet_name" resides in the `wallets/wallet_name/` subdirectory. @@ -88,15 +88,6 @@ Wallets are Berkeley DB (BDB) or SQLite databases. 5. Any copy or backup of the wallet should be done through a `backupwallet` call in order to update and lock the wallet, preventing any file corruption caused by updates during the copy. -### Berkeley DB database based wallets - -Subdirectory | File(s) | Description --------------|-------------------|------------- -`database/` | BDB logging files | Part of BDB environment; created at start and deleted on shutdown; a user *must keep it as safe* as personal wallet `wallet.dat` -`./` | `db.log` | BDB error file -`./` | `wallet.dat` | Personal wallet (a BDB database) with keys and transactions -`./` | `.walletlock` | BDB wallet lock file - ### SQLite database based wallets Subdirectory | File | Description @@ -123,6 +114,15 @@ Path | Description | Repository notes `addr.dat` | Peer IP address BDB database; replaced by `peers.dat` in [0.7.0](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.7.0.md) | [PR #1198](https://github.com/bitcoin/bitcoin/pull/1198), [`928d3a01`](https://github.com/bitcoin/bitcoin/commit/928d3a011cc66c7f907c4d053f674ea77dc611cc) `onion_private_key` | Cached Tor onion service private key for `-listenonion` option. Was used for Tor v2 services; replaced by `onion_v3_private_key` in [0.21.0](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.21.0.md) | [PR #19954](https://github.com/bitcoin/bitcoin/pull/19954) +### Berkeley DB database based wallets + +Subdirectory | File(s) | Description +-------------|-------------------|------------- +`database/` | BDB logging files | Part of BDB environment; created at start and deleted on shutdown; a user *must keep it as safe* as personal wallet `wallet.dat` +`./` | `db.log` | BDB error file +`./` | `wallet.dat` | Personal wallet (a BDB database) with keys and transactions +`./` | `.walletlock` | BDB wallet lock file + ## Notes 1. The `/` (slash, U+002F) is used as the platform-independent path component separator in this document. diff --git a/doc/productivity.md b/doc/productivity.md index f025b4bf6e..181c697d73 100644 --- a/doc/productivity.md +++ b/doc/productivity.md @@ -52,8 +52,6 @@ During the generation of the build system only essential build options are enabl Run `cmake -B build -LH` to see the full list of available options. GUI tools, such as `ccmake` and `cmake-gui`, can be also helpful. -If you do need the wallet enabled (`-DENABLE_WALLET=ON`), it is common for devs to use your system bdb version for the wallet, so you don't have to find a copy of bdb 4.8. Wallets from such a build will be incompatible with any release binary (and vice versa), so use with caution on mainnet. - ### Make use of your threads with `-j` If you have multiple threads on your machine, you can utilize all of them with: diff --git a/doc/psbt.md b/doc/psbt.md index e555718349..7a885701c8 100644 --- a/doc/psbt.md +++ b/doc/psbt.md @@ -97,54 +97,3 @@ hardware implementations will typically implement multiple roles simultaneously. #### Multisig with multiple Bitcoin Core instances For a quick start see [Basic M-of-N multisig example using descriptor wallets and PSBTs](./descriptors.md#basic-multisig-example). -If you are using legacy wallets feel free to continue with the example provided here. - -Alice, Bob, and Carol want to create a 2-of-3 multisig address. They're all using -Bitcoin Core. We assume their wallets only contain the multisig funds. In case -they also have a personal wallet, this can be accomplished through the -multiwallet feature - possibly resulting in a need to add `-rpcwallet=name` to -the command line in case `bitcoin-cli` is used. - -Setup: -- All three call `getnewaddress` to create a new address; call these addresses - *Aalice*, *Abob*, and *Acarol*. -- All three call `getaddressinfo "X"`, with *X* their respective address, and - remember the corresponding public keys. Call these public keys *Kalice*, - *Kbob*, and *Kcarol*. -- All three now run `addmultisigaddress 2 ["Kalice","Kbob","Kcarol"]` to teach - their wallet about the multisig script. Call the address produced by this - command *Amulti*. They may be required to explicitly specify the same - addresstype option each, to avoid constructing different versions due to - differences in configuration. -- They also run `importaddress "Amulti" "" false` to make their wallets treat - payments to *Amulti* as contributing to the watch-only balance. -- Others can verify the produced address by running - `createmultisig 2 ["Kalice","Kbob","Kcarol"]`, and expecting *Amulti* as - output. Again, it may be necessary to explicitly specify the addresstype - in order to get a result that matches. This command won't enable them to - initiate transactions later, however. -- They can now give out *Amulti* as address others can pay to. - -Later, when *V* BTC has been received on *Amulti*, and Bob and Carol want to -move the coins in their entirety to address *Asend*, with no change. Alice -does not need to be involved. -- One of them - let's assume Carol here - initiates the creation. She runs - `walletcreatefundedpsbt [] {"Asend":V} 0 {"subtractFeeFromOutputs":[0], "includeWatching":true}`. - We call the resulting PSBT *P*. *P* does not contain any signatures. -- Carol needs to sign the transaction herself. In order to do so, she runs - `walletprocesspsbt "P"`, and gives the resulting PSBT *P2* to Bob. -- Bob inspects the PSBT using `decodepsbt "P2"` to determine if the transaction - has indeed just the expected input, and an output to *Asend*, and the fee is - reasonable. If he agrees, he calls `walletprocesspsbt "P2"` to sign. The - resulting PSBT *P3* contains both Carol's and Bob's signature. -- Now anyone can call `finalizepsbt "P3"` to extract a fully signed transaction - *T*. -- Finally anyone can broadcast the transaction using `sendrawtransaction "T"`. - -In case there are more signers, it may be advantageous to let them all sign in -parallel, rather than passing the PSBT from one signer to the next one. In the -above example this would translate to Carol handing a copy of *P* to each signer -separately. They can then all invoke `walletprocesspsbt "P"`, and end up with -their individually-signed PSBT structures. They then all send those back to -Carol (or anyone) who can combine them using `combinepsbt`. The last two steps -(`finalizepsbt` and `sendrawtransaction`) remain unchanged. diff --git a/src/bench/wallet_migration.cpp b/src/bench/wallet_migration.cpp index afb79d98af..b5e512f369 100644 --- a/src/bench/wallet_migration.cpp +++ b/src/bench/wallet_migration.cpp @@ -57,7 +57,7 @@ static void WalletMigration(benchmark::Bench& bench) mtx.vout.emplace_back(COIN, GetScriptForDestination(dest)); mtx.vout.emplace_back(COIN, scripts_watch_only.at(j % NUM_WATCH_ONLY_ADDR)); mtx.vin.resize(2); - wallet->AddToWallet(MakeTransactionRef(mtx), TxStateInactive{}, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, /*rescanning_old_block=*/true); + wallet->AddToWallet(MakeTransactionRef(mtx), TxStateInactive{}, /*update_wtx=*/nullptr, /*rescanning_old_block=*/true); batch.WriteKey(pubkey, key.GetPrivKey(), CKeyMetadata()); } diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index a8baa9a176..51caae0a10 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -41,7 +41,6 @@ static void SetupWalletToolArgs(ArgsManager& argsman) argsman.AddArg("-descriptors", "Create descriptors wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-legacy", "Create legacy wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-withinternalbdb", "Use the internal Berkeley DB parser when dumping a Berkeley DB wallet file (default: false)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddCommand("info", "Get wallet info"); argsman.AddCommand("create", "Create new wallet file"); diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 42282c32d1..c0603cb134 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -47,13 +47,9 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const "-walletdir=", "-walletnotify=", "-walletrbf", - "-dblogsize=", - "-flushwallet", - "-privdb", "-walletrejectlongchains", "-walletcrosschain", "-unsafesqlitesync", - "-swapbdbendian", }); } diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 87f8c35a14..48d68c4e6b 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -107,9 +107,6 @@ public: //! Return whether wallet has private key. virtual bool isSpendable(const CTxDestination& dest) = 0; - //! Return whether wallet has watch only keys. - virtual bool haveWatchOnly() = 0; - //! Add or update address. virtual bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional& purpose) = 0; @@ -282,9 +279,6 @@ public: // Remove wallet. virtual void remove() = 0; - //! Return whether is a legacy wallet - virtual bool isLegacy() = 0; - //! Register handler for unload message. using UnloadFn = std::function; virtual std::unique_ptr handleUnload(UnloadFn fn) = 0; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 7d58492082..8536da0142 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -195,28 +195,10 @@ OverviewPage::~OverviewPage() void OverviewPage::setBalance(const interfaces::WalletBalances& balances) { BitcoinUnit unit = walletModel->getOptionsModel()->getDisplayUnit(); - if (walletModel->wallet().isLegacy()) { - if (walletModel->wallet().privateKeysDisabled()) { - ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - } else { - ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelWatchAvailable->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelWatchPending->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelWatchImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelWatchTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - } - } else { - ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - } + ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); + ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); + ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); + ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); // only show immature (newly mined) balance if it's non-zero, so as not to complicate things // for the non-mining users bool showImmature = balances.immature_balance != 0; @@ -281,11 +263,7 @@ void OverviewPage::setWalletModel(WalletModel *model) connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit); - interfaces::Wallet& wallet = model->wallet(); - updateWatchOnlyLabels(wallet.haveWatchOnly() && !wallet.privateKeysDisabled()); - connect(model, &WalletModel::notifyWatchonlyChanged, [this](bool showWatchOnly) { - updateWatchOnlyLabels(showWatchOnly && !walletModel->wallet().privateKeysDisabled()); - }); + updateWatchOnlyLabels(false); } // update the display unit, to not use the default ("BTC") diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 9d7c17ac91..2a688e9eac 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -72,9 +72,6 @@ namespace { // don't add private key handling cmd's to the history const QStringList historyFilter = QStringList() - << "importprivkey" - << "importmulti" - << "sethdseed" << "signmessagewithprivkey" << "signrawtransactionwithkey" << "walletpassphrase" diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index b04e249d0b..b8a9854f0e 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -711,9 +711,6 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances) CAmount balance = balances.balance; if (model->wallet().hasExternalSigner()) { ui->labelBalanceName->setText(tr("External balance:")); - } else if (model->wallet().isLegacy() && model->wallet().privateKeysDisabled()) { - balance = balances.watch_only_balance; - ui->labelBalanceName->setText(tr("Watch-only balance:")); } ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance)); } diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index eb74ef8559..0857a4ebd5 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -85,8 +85,6 @@ void RPCNestedTests::rpcNestedTests() QVERIFY(result == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"); QVERIFY(filtered == "getblock(getbestblockhash())[tx][0]"); - RPCConsole::RPCParseCommandLine(nullptr, result, "importprivkey", false, &filtered); - QVERIFY(filtered == "importprivkey(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "signmessagewithprivkey abc", false, &filtered); QVERIFY(filtered == "signmessagewithprivkey(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "signmessagewithprivkey abc,def", false, &filtered); @@ -99,12 +97,6 @@ void RPCNestedTests::rpcNestedTests() QVERIFY(filtered == "walletpassphrasechange(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "help(encryptwallet(abc, def))", false, &filtered); QVERIFY(filtered == "help(encryptwallet(…))"); - RPCConsole::RPCParseCommandLine(nullptr, result, "help(importprivkey())", false, &filtered); - QVERIFY(filtered == "help(importprivkey(…))"); - RPCConsole::RPCParseCommandLine(nullptr, result, "help(importprivkey(help()))", false, &filtered); - QVERIFY(filtered == "help(importprivkey(…))"); - RPCConsole::RPCParseCommandLine(nullptr, result, "help(importprivkey(abc), walletpassphrase(def))", false, &filtered); - QVERIFY(filtered == "help(importprivkey(…), walletpassphrase(…))"); RPCConsole::RPCExecuteCommandLine(m_node, result, "rpcNestedTest"); QVERIFY(result == "[]"); diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 574824b419..b4a60b427e 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -241,8 +241,8 @@ void TransactionView::setModel(WalletModel *_model) } } - // show/hide column Watch-only - updateWatchOnlyColumn(_model->wallet().haveWatchOnly()); + // hide column Watch-only + updateWatchOnlyColumn(false); // Watch-only signal connect(_model, &WalletModel::notifyWatchonlyChanged, this, &TransactionView::updateWatchOnlyColumn); @@ -368,8 +368,6 @@ void TransactionView::exportClicked() // name, column, role writer.setModel(transactionProxyModel); writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole); - if (model->wallet().haveWatchOnly()) - writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly); writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole); writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole); writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 0a01c0a45b..455f9f469e 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -45,7 +45,7 @@ WalletModel::WalletModel(std::unique_ptr wallet, ClientModel optionsModel(client_model.getOptionsModel()), timer(new QTimer(this)) { - fHaveWatchOnly = m_wallet->haveWatchOnly(); + fHaveWatchOnly = false; addressTableModel = new AddressTableModel(this); transactionTableModel = new TransactionTableModel(platformStyle, this); recentRequestsTableModel = new RecentRequestsTableModel(this); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 1b711e3c5b..08bd1142fc 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -48,7 +48,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendtoaddress", 9, "fee_rate"}, { "sendtoaddress", 10, "verbose"}, { "settxfee", 0, "amount" }, - { "sethdseed", 0, "newkeypool" }, { "getreceivedbyaddress", 1, "minconf" }, { "getreceivedbyaddress", 2, "include_immature_coinbase" }, { "getreceivedbylabel", 1, "minconf" }, @@ -96,8 +95,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getdescriptoractivity", 1, "scanobjects" }, { "getdescriptoractivity", 2, "include_mempool" }, { "scantxoutset", 1, "scanobjects" }, - { "addmultisigaddress", 0, "nrequired" }, - { "addmultisigaddress", 1, "keys" }, { "createmultisig", 0, "nrequired" }, { "createmultisig", 1, "keys" }, { "listunspent", 0, "minconf" }, @@ -236,17 +233,10 @@ static const CRPCConvertParam vRPCConvertParams[] = { "simulaterawtransaction", 0, "rawtxs" }, { "simulaterawtransaction", 1, "options" }, { "simulaterawtransaction", 1, "include_watchonly"}, - { "importprivkey", 2, "rescan" }, - { "importaddress", 2, "rescan" }, - { "importaddress", 3, "p2sh" }, - { "importpubkey", 2, "rescan" }, { "importmempool", 1, "options" }, { "importmempool", 1, "apply_fee_delta_priority" }, { "importmempool", 1, "use_current_time" }, { "importmempool", 1, "apply_unbroadcast_set" }, - { "importmulti", 0, "requests" }, - { "importmulti", 1, "options" }, - { "importmulti", 1, "rescan" }, { "importdescriptors", 0, "requests" }, { "listdescriptors", 0, "private" }, { "verifychain", 0, "checklevel" }, diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index 7bf90b9b77..580a6338a8 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -75,14 +75,12 @@ const std::vector RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{ "addnode", // avoid DNS lookups "addpeeraddress", // avoid DNS lookups "dumptxoutset", // avoid writing to disk - "dumpwallet", // avoid writing to disk "enumeratesigners", "echoipc", // avoid assertion failure (Assertion `"EnsureAnyNodeContext(request.context).init" && check' failed.) "generatetoaddress", // avoid prohibitively slow execution (when `num_blocks` is large) "generatetodescriptor", // avoid prohibitively slow execution (when `nblocks` is large) "gettxoutproof", // avoid prohibitively slow execution "importmempool", // avoid reading from disk - "importwallet", // avoid reading from disk "loadtxoutset", // avoid reading from disk "loadwallet", // avoid reading from disk "savemempool", // disabled as a precautionary measure: may take a file path argument in the future diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 00b62c3c35..dc0e630a22 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -181,7 +181,7 @@ BOOST_AUTO_TEST_CASE(parse_hex) result = TryParseHex("12 34 56 78").value(); BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); - // Leading space must be supported (used in BerkeleyEnvironment::Salvage) + // Leading space must be supported expected = {0x89, 0x34, 0x56, 0x78}; result = ParseHex(" 89 34 56 78"); BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 0f2fde8cc6..8ec381df5a 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -44,8 +44,3 @@ target_link_libraries(bitcoin_wallet Boost::headers $ ) - -if(USE_BDB) - target_sources(bitcoin_wallet PRIVATE bdb.cpp) - target_link_libraries(bitcoin_wallet PUBLIC BerkeleyDB::BerkeleyDB) -endif() diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp deleted file mode 100644 index 5bb6eeb88d..0000000000 --- a/src/wallet/bdb.cpp +++ /dev/null @@ -1,972 +0,0 @@ -// Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-present The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include - -// Windows may not define S_IRUSR or S_IWUSR. We define both -// here, with the same values as glibc (see stat.h). -#ifdef WIN32 -#ifndef S_IRUSR -#define S_IRUSR 0400 -#define S_IWUSR 0200 -#endif -#endif - -static_assert(BDB_DB_FILE_ID_LEN == DB_FILE_ID_LEN, "DB_FILE_ID_LEN should be 20."); - -namespace wallet { -namespace { - -//! Make sure database has a unique fileid within the environment. If it -//! doesn't, throw an error. BDB caches do not work properly when more than one -//! open database has the same fileid (values written to one database may show -//! up in reads to other databases). -//! -//! BerkeleyDB generates unique fileids by default -//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html), -//! so bitcoin should never create different databases with the same fileid, but -//! this error can be triggered if users manually copy database files. -void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db, WalletDatabaseFileId& fileid) -{ - if (env.IsMock()) return; - - int ret = db.get_mpf()->get_fileid(fileid.value); - if (ret != 0) { - throw std::runtime_error(strprintf("BerkeleyDatabase: Can't open database %s (get_fileid failed with %d)", filename, ret)); - } - - for (const auto& item : env.m_fileids) { - if (fileid == item.second && &fileid != &item.second) { - throw std::runtime_error(strprintf("BerkeleyDatabase: Can't open database %s (duplicates fileid %s from %s)", filename, - HexStr(item.second.value), item.first)); - } - } -} - -RecursiveMutex cs_db; -std::map> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to db environment. -} // namespace - -static constexpr auto REVERSE_BYTE_ORDER{std::endian::native == std::endian::little ? 4321 : 1234}; - -bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const -{ - return memcmp(value, &rhs.value, sizeof(value)) == 0; -} - -/** - * @param[in] env_directory Path to environment directory - * @return A shared pointer to the BerkeleyEnvironment object for the wallet directory, never empty because ~BerkeleyEnvironment - * erases the weak pointer from the g_dbenvs map. - * @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map. - */ -std::shared_ptr GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory) -{ - LOCK(cs_db); - auto inserted = g_dbenvs.emplace(fs::PathToString(env_directory), std::weak_ptr()); - if (inserted.second) { - auto env = std::make_shared(env_directory, use_shared_memory); - inserted.first->second = env; - return env; - } - return inserted.first->second.lock(); -} - -// -// BerkeleyBatch -// - -void BerkeleyEnvironment::Close() -{ - if (!fDbEnvInit) - return; - - fDbEnvInit = false; - - for (auto& db : m_databases) { - BerkeleyDatabase& database = db.second.get(); - assert(database.m_refcount <= 0); - if (database.m_db) { - database.m_db->close(0); - database.m_db.reset(); - } - } - - FILE* error_file = nullptr; - dbenv->get_errfile(&error_file); - - int ret = dbenv->close(0); - if (ret != 0) - LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret)); - if (!fMockDb) - DbEnv(uint32_t{0}).remove(strPath.c_str(), 0); - - if (error_file) fclose(error_file); - - UnlockDirectory(fs::PathFromString(strPath), ".walletlock"); -} - -void BerkeleyEnvironment::Reset() -{ - dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS)); - fDbEnvInit = false; - fMockDb = false; -} - -BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path, bool use_shared_memory) : strPath(fs::PathToString(dir_path)), m_use_shared_memory(use_shared_memory) -{ - Reset(); -} - -BerkeleyEnvironment::~BerkeleyEnvironment() -{ - LOCK(cs_db); - g_dbenvs.erase(strPath); - Close(); -} - -bool BerkeleyEnvironment::Open(bilingual_str& err) -{ - if (fDbEnvInit) { - return true; - } - - fs::path pathIn = fs::PathFromString(strPath); - TryCreateDirectories(pathIn); - if (util::LockDirectory(pathIn, ".walletlock") != util::LockResult::Success) { - LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance may be using it.\n", strPath); - err = strprintf(_("Error initializing wallet database environment %s!"), fs::quoted(fs::PathToString(Directory()))); - return false; - } - - fs::path pathLogDir = pathIn / "database"; - TryCreateDirectories(pathLogDir); - fs::path pathErrorFile = pathIn / "db.log"; - LogPrintf("BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n", fs::PathToString(pathLogDir), fs::PathToString(pathErrorFile)); - - unsigned int nEnvFlags = 0; - if (!m_use_shared_memory) { - nEnvFlags |= DB_PRIVATE; - } - - dbenv->set_lg_dir(fs::PathToString(pathLogDir).c_str()); - dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet - dbenv->set_lg_bsize(0x10000); - dbenv->set_lg_max(1048576); - dbenv->set_lk_max_locks(40000); - dbenv->set_lk_max_objects(40000); - dbenv->set_errfile(fsbridge::fopen(pathErrorFile, "a")); /// debug - dbenv->set_flags(DB_AUTO_COMMIT, 1); - dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1); - dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1); - int ret = dbenv->open(strPath.c_str(), - DB_CREATE | - DB_INIT_LOCK | - DB_INIT_LOG | - DB_INIT_MPOOL | - DB_INIT_TXN | - DB_THREAD | - DB_RECOVER | - nEnvFlags, - S_IRUSR | S_IWUSR); - if (ret != 0) { - LogPrintf("BerkeleyEnvironment::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret)); - int ret2 = dbenv->close(0); - if (ret2 != 0) { - LogPrintf("BerkeleyEnvironment::Open: Error %d closing failed database environment: %s\n", ret2, DbEnv::strerror(ret2)); - } - Reset(); - err = strprintf(_("Error initializing wallet database environment %s!"), fs::quoted(fs::PathToString(Directory()))); - if (ret == DB_RUNRECOVERY) { - err += Untranslated(" ") + _("This error could occur if this wallet was not shutdown cleanly and was last loaded using a build with a newer version of Berkeley DB. If so, please use the software that last loaded this wallet"); - } - return false; - } - - fDbEnvInit = true; - fMockDb = false; - return true; -} - -//! Construct an in-memory mock Berkeley environment for testing -BerkeleyEnvironment::BerkeleyEnvironment() : m_use_shared_memory(false) -{ - Reset(); - - LogDebug(BCLog::WALLETDB, "BerkeleyEnvironment::MakeMock\n"); - - dbenv->set_cachesize(1, 0, 1); - dbenv->set_lg_bsize(10485760 * 4); - dbenv->set_lg_max(10485760); - dbenv->set_lk_max_locks(10000); - dbenv->set_lk_max_objects(10000); - dbenv->set_flags(DB_AUTO_COMMIT, 1); - dbenv->log_set_config(DB_LOG_IN_MEMORY, 1); - int ret = dbenv->open(nullptr, - DB_CREATE | - DB_INIT_LOCK | - DB_INIT_LOG | - DB_INIT_MPOOL | - DB_INIT_TXN | - DB_THREAD | - DB_PRIVATE, - S_IRUSR | S_IWUSR); - if (ret > 0) { - throw std::runtime_error(strprintf("BerkeleyEnvironment::MakeMock: Error %d opening database environment.", ret)); - } - - fDbEnvInit = true; - fMockDb = true; -} - -/** RAII class that automatically cleanses its data on destruction */ -class SafeDbt final -{ - Dbt m_dbt; - -public: - // construct Dbt with internally-managed data - SafeDbt(); - // construct Dbt with provided data - SafeDbt(void* data, size_t size); - ~SafeDbt(); - - // delegate to Dbt - const void* get_data() const; - uint32_t get_size() const; - - // conversion operator to access the underlying Dbt - operator Dbt*(); -}; - -SafeDbt::SafeDbt() -{ - m_dbt.set_flags(DB_DBT_MALLOC); -} - -SafeDbt::SafeDbt(void* data, size_t size) - : m_dbt(data, size) -{ -} - -SafeDbt::~SafeDbt() -{ - if (m_dbt.get_data() != nullptr) { - // Clear memory, e.g. in case it was a private key - memory_cleanse(m_dbt.get_data(), m_dbt.get_size()); - // under DB_DBT_MALLOC, data is malloced by the Dbt, but must be - // freed by the caller. - // https://docs.oracle.com/cd/E17275_01/html/api_reference/C/dbt.html - if (m_dbt.get_flags() & DB_DBT_MALLOC) { - free(m_dbt.get_data()); - } - } -} - -const void* SafeDbt::get_data() const -{ - return m_dbt.get_data(); -} - -uint32_t SafeDbt::get_size() const -{ - return m_dbt.get_size(); -} - -SafeDbt::operator Dbt*() -{ - return &m_dbt; -} - -static std::span SpanFromDbt(const SafeDbt& dbt) -{ - return {reinterpret_cast(dbt.get_data()), dbt.get_size()}; -} - -BerkeleyDatabase::BerkeleyDatabase(std::shared_ptr env, fs::path filename, const DatabaseOptions& options) : - WalletDatabase(), - env(std::move(env)), - m_byteswap(options.require_format == DatabaseFormat::BERKELEY_SWAP), - m_filename(std::move(filename)), - m_max_log_mb(options.max_log_mb) -{ - auto inserted = this->env->m_databases.emplace(m_filename, std::ref(*this)); - assert(inserted.second); -} - -bool BerkeleyDatabase::Verify(bilingual_str& errorStr) -{ - fs::path walletDir = env->Directory(); - fs::path file_path = walletDir / m_filename; - - LogPrintf("Using BerkeleyDB version %s\n", BerkeleyDatabaseVersion()); - LogPrintf("Using wallet %s\n", fs::PathToString(file_path)); - - if (!env->Open(errorStr)) { - return false; - } - - if (fs::exists(file_path)) - { - assert(m_refcount == 0); - - Db db(env->dbenv.get(), 0); - const std::string strFile = fs::PathToString(m_filename); - int result = db.verify(strFile.c_str(), nullptr, nullptr, 0); - if (result != 0) { - errorStr = strprintf(_("%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring a backup."), fs::quoted(fs::PathToString(file_path))); - return false; - } - } - // also return true if files does not exists - return true; -} - -void BerkeleyEnvironment::CheckpointLSN(const std::string& strFile) -{ - dbenv->txn_checkpoint(0, 0, 0); - if (fMockDb) - return; - dbenv->lsn_reset(strFile.c_str(), 0); -} - -BerkeleyDatabase::~BerkeleyDatabase() -{ - if (env) { - LOCK(cs_db); - env->CloseDb(m_filename); - assert(!m_db); - size_t erased = env->m_databases.erase(m_filename); - assert(erased == 1); - env->m_fileids.erase(fs::PathToString(m_filename)); - } -} - -BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, bool fFlushOnCloseIn) : m_database(database) -{ - database.AddRef(); - database.Open(); - fReadOnly = read_only; - fFlushOnClose = fFlushOnCloseIn; - env = database.env.get(); - pdb = database.m_db.get(); - strFile = fs::PathToString(database.m_filename); -} - -void BerkeleyDatabase::Open() -{ - unsigned int nFlags = DB_THREAD | DB_CREATE; - - { - LOCK(cs_db); - bilingual_str open_err; - if (!env->Open(open_err)) - throw std::runtime_error("BerkeleyDatabase: Failed to open database environment."); - - if (m_db == nullptr) { - int ret; - std::unique_ptr pdb_temp = std::make_unique(env->dbenv.get(), 0); - const std::string strFile = fs::PathToString(m_filename); - - bool fMockDb = env->IsMock(); - if (fMockDb) { - DbMpoolFile* mpf = pdb_temp->get_mpf(); - ret = mpf->set_flags(DB_MPOOL_NOFILE, 1); - if (ret != 0) { - throw std::runtime_error(strprintf("BerkeleyDatabase: Failed to configure for no temp file backing for database %s", strFile)); - } - } - - if (m_byteswap) { - pdb_temp->set_lorder(REVERSE_BYTE_ORDER); - } - - ret = pdb_temp->open(nullptr, // Txn pointer - fMockDb ? nullptr : strFile.c_str(), // Filename - fMockDb ? strFile.c_str() : "main", // Logical db name - DB_BTREE, // Database type - nFlags, // Flags - 0); - - if (ret != 0) { - throw std::runtime_error(strprintf("BerkeleyDatabase: Error %d, can't open database %s", ret, strFile)); - } - - // Call CheckUniqueFileid on the containing BDB environment to - // avoid BDB data consistency bugs that happen when different data - // files in the same environment have the same fileid. - CheckUniqueFileid(*env, strFile, *pdb_temp, this->env->m_fileids[strFile]); - - m_db.reset(pdb_temp.release()); - - } - } -} - -void BerkeleyBatch::Flush() -{ - if (activeTxn) - return; - - // Flush database activity from memory pool to disk log - unsigned int nMinutes = 0; - if (fReadOnly) - nMinutes = 1; - - if (env) { // env is nullptr for dummy databases (i.e. in tests). Don't actually flush if env is nullptr so we don't segfault - env->dbenv->txn_checkpoint(nMinutes ? m_database.m_max_log_mb * 1024 : 0, nMinutes, 0); - } -} - -void BerkeleyDatabase::IncrementUpdateCounter() -{ - ++nUpdateCounter; -} - -BerkeleyBatch::~BerkeleyBatch() -{ - Close(); - m_database.RemoveRef(); -} - -void BerkeleyBatch::Close() -{ - if (!pdb) - return; - if (activeTxn) - activeTxn->abort(); - activeTxn = nullptr; - pdb = nullptr; - - if (fFlushOnClose) - Flush(); -} - -void BerkeleyEnvironment::CloseDb(const fs::path& filename) -{ - { - LOCK(cs_db); - auto it = m_databases.find(filename); - assert(it != m_databases.end()); - BerkeleyDatabase& database = it->second.get(); - if (database.m_db) { - // Close the database handle - database.m_db->close(0); - database.m_db.reset(); - } - } -} - -void BerkeleyEnvironment::ReloadDbEnv() -{ - // Make sure that no Db's are in use - AssertLockNotHeld(cs_db); - std::unique_lock lock(cs_db); - m_db_in_use.wait(lock, [this](){ - for (auto& db : m_databases) { - if (db.second.get().m_refcount > 0) return false; - } - return true; - }); - - std::vector filenames; - filenames.reserve(m_databases.size()); - for (const auto& it : m_databases) { - filenames.push_back(it.first); - } - // Close the individual Db's - for (const fs::path& filename : filenames) { - CloseDb(filename); - } - // Reset the environment - Flush(true); // This will flush and close the environment - Reset(); - bilingual_str open_err; - Open(open_err); -} - -DbTxn* BerkeleyEnvironment::TxnBegin(int flags) -{ - DbTxn* ptxn = nullptr; - int ret = dbenv->txn_begin(nullptr, &ptxn, flags); - if (!ptxn || ret != 0) - return nullptr; - return ptxn; -} - -bool BerkeleyDatabase::Rewrite(const char* pszSkip) -{ - while (true) { - { - LOCK(cs_db); - const std::string strFile = fs::PathToString(m_filename); - if (m_refcount <= 0) { - // Flush log data to the dat file - env->CloseDb(m_filename); - env->CheckpointLSN(strFile); - m_refcount = -1; - - bool fSuccess = true; - LogPrintf("BerkeleyBatch::Rewrite: Rewriting %s...\n", strFile); - std::string strFileRes = strFile + ".rewrite"; - { // surround usage of db with extra {} - BerkeleyBatch db(*this, true); - std::unique_ptr pdbCopy = std::make_unique(env->dbenv.get(), 0); - - if (m_byteswap) { - pdbCopy->set_lorder(REVERSE_BYTE_ORDER); - } - - int ret = pdbCopy->open(nullptr, // Txn pointer - strFileRes.c_str(), // Filename - "main", // Logical db name - DB_BTREE, // Database type - DB_CREATE, // Flags - 0); - if (ret > 0) { - LogPrintf("BerkeleyBatch::Rewrite: Can't create database file %s\n", strFileRes); - fSuccess = false; - } - - std::unique_ptr cursor = db.GetNewCursor(); - if (cursor) { - while (fSuccess) { - DataStream ssKey{}; - DataStream ssValue{}; - DatabaseCursor::Status ret1 = cursor->Next(ssKey, ssValue); - if (ret1 == DatabaseCursor::Status::DONE) { - break; - } else if (ret1 == DatabaseCursor::Status::FAIL) { - fSuccess = false; - break; - } - if (pszSkip && - strncmp((const char*)ssKey.data(), pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0) - continue; - if (strncmp((const char*)ssKey.data(), "\x07version", 8) == 0) { - // Update version: - ssValue.clear(); - ssValue << CLIENT_VERSION; - } - Dbt datKey(ssKey.data(), ssKey.size()); - Dbt datValue(ssValue.data(), ssValue.size()); - int ret2 = pdbCopy->put(nullptr, &datKey, &datValue, DB_NOOVERWRITE); - if (ret2 > 0) - fSuccess = false; - } - cursor.reset(); - } - if (fSuccess) { - db.Close(); - env->CloseDb(m_filename); - if (pdbCopy->close(0)) - fSuccess = false; - } else { - pdbCopy->close(0); - } - } - if (fSuccess) { - Db dbA(env->dbenv.get(), 0); - if (dbA.remove(strFile.c_str(), nullptr, 0)) - fSuccess = false; - Db dbB(env->dbenv.get(), 0); - if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), 0)) - fSuccess = false; - } - if (!fSuccess) - LogPrintf("BerkeleyBatch::Rewrite: Failed to rewrite database file %s\n", strFileRes); - return fSuccess; - } - } - UninterruptibleSleep(std::chrono::milliseconds{100}); - } -} - - -void BerkeleyEnvironment::Flush(bool fShutdown) -{ - const auto start{SteadyClock::now()}; - // Flush log data to the actual data file on all files that are not in use - LogDebug(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started"); - if (!fDbEnvInit) - return; - { - LOCK(cs_db); - bool no_dbs_accessed = true; - for (auto& db_it : m_databases) { - const fs::path& filename = db_it.first; - int nRefCount = db_it.second.get().m_refcount; - if (nRefCount < 0) continue; - const std::string strFile = fs::PathToString(filename); - LogDebug(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount); - if (nRefCount == 0) { - // Move log data to the dat file - CloseDb(filename); - LogDebug(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile); - dbenv->txn_checkpoint(0, 0, 0); - LogDebug(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s detach\n", strFile); - if (!fMockDb) - dbenv->lsn_reset(strFile.c_str(), 0); - LogDebug(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s closed\n", strFile); - nRefCount = -1; - } else { - no_dbs_accessed = false; - } - } - LogDebug(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", Ticks(SteadyClock::now() - start)); - if (fShutdown) { - char** listp; - if (no_dbs_accessed) { - dbenv->log_archive(&listp, DB_ARCH_REMOVE); - Close(); - if (!fMockDb) { - fs::remove_all(fs::PathFromString(strPath) / "database"); - } - } - } - } -} - -bool BerkeleyDatabase::PeriodicFlush() -{ - // Don't flush if we can't acquire the lock. - TRY_LOCK(cs_db, lockDb); - if (!lockDb) return false; - - // Don't flush if any databases are in use - for (auto& it : env->m_databases) { - if (it.second.get().m_refcount > 0) return false; - } - - // Don't flush if there haven't been any batch writes for this database. - if (m_refcount < 0) return false; - - const std::string strFile = fs::PathToString(m_filename); - LogDebug(BCLog::WALLETDB, "Flushing %s\n", strFile); - const auto start{SteadyClock::now()}; - - // Flush wallet file so it's self contained - env->CloseDb(m_filename); - env->CheckpointLSN(strFile); - m_refcount = -1; - - LogDebug(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, Ticks(SteadyClock::now() - start)); - - return true; -} - -bool BerkeleyDatabase::Backup(const std::string& strDest) const -{ - const std::string strFile = fs::PathToString(m_filename); - while (true) - { - { - LOCK(cs_db); - if (m_refcount <= 0) - { - // Flush log data to the dat file - env->CloseDb(m_filename); - env->CheckpointLSN(strFile); - - // Copy wallet file - fs::path pathSrc = env->Directory() / m_filename; - fs::path pathDest(fs::PathFromString(strDest)); - if (fs::is_directory(pathDest)) - pathDest /= m_filename; - - try { - if (fs::exists(pathDest) && fs::equivalent(pathSrc, pathDest)) { - LogPrintf("cannot backup to wallet source file %s\n", fs::PathToString(pathDest)); - return false; - } - - fs::copy_file(pathSrc, pathDest, fs::copy_options::overwrite_existing); - LogPrintf("copied %s to %s\n", strFile, fs::PathToString(pathDest)); - return true; - } catch (const fs::filesystem_error& e) { - LogWarning("error copying %s to %s - %s\n", strFile, fs::PathToString(pathDest), e.code().message()); - return false; - } - } - } - UninterruptibleSleep(std::chrono::milliseconds{100}); - } -} - -void BerkeleyDatabase::Flush() -{ - env->Flush(false); -} - -void BerkeleyDatabase::Close() -{ - env->Flush(true); -} - -void BerkeleyDatabase::ReloadDbEnv() -{ - env->ReloadDbEnv(); -} - -BerkeleyCursor::BerkeleyCursor(BerkeleyDatabase& database, const BerkeleyBatch& batch, std::span prefix) - : m_key_prefix(prefix.begin(), prefix.end()) -{ - if (!database.m_db.get()) { - throw std::runtime_error(STR_INTERNAL_BUG("BerkeleyDatabase does not exist")); - } - // Transaction argument to cursor is only needed when using the cursor to - // write to the database. Read-only cursors do not need a txn pointer. - int ret = database.m_db->cursor(batch.txn(), &m_cursor, 0); - if (ret != 0) { - throw std::runtime_error(STR_INTERNAL_BUG(strprintf("BDB Cursor could not be created. Returned %d", ret))); - } -} - -DatabaseCursor::Status BerkeleyCursor::Next(DataStream& ssKey, DataStream& ssValue) -{ - if (m_cursor == nullptr) return Status::FAIL; - // Read at cursor - SafeDbt datKey(m_key_prefix.data(), m_key_prefix.size()); - SafeDbt datValue; - int ret = -1; - if (m_first && !m_key_prefix.empty()) { - ret = m_cursor->get(datKey, datValue, DB_SET_RANGE); - } else { - ret = m_cursor->get(datKey, datValue, DB_NEXT); - } - m_first = false; - if (ret == DB_NOTFOUND) { - return Status::DONE; - } - if (ret != 0) { - return Status::FAIL; - } - - std::span raw_key = SpanFromDbt(datKey); - if (!m_key_prefix.empty() && std::mismatch(raw_key.begin(), raw_key.end(), m_key_prefix.begin(), m_key_prefix.end()).second != m_key_prefix.end()) { - return Status::DONE; - } - - // Convert to streams - ssKey.clear(); - ssKey.write(raw_key); - ssValue.clear(); - ssValue.write(SpanFromDbt(datValue)); - return Status::MORE; -} - -BerkeleyCursor::~BerkeleyCursor() -{ - if (!m_cursor) return; - m_cursor->close(); - m_cursor = nullptr; -} - -std::unique_ptr BerkeleyBatch::GetNewCursor() -{ - if (!pdb) return nullptr; - return std::make_unique(m_database, *this); -} - -std::unique_ptr BerkeleyBatch::GetNewPrefixCursor(std::span prefix) -{ - if (!pdb) return nullptr; - return std::make_unique(m_database, *this, prefix); -} - -bool BerkeleyBatch::TxnBegin() -{ - if (!pdb || activeTxn) - return false; - DbTxn* ptxn = env->TxnBegin(DB_TXN_WRITE_NOSYNC); - if (!ptxn) - return false; - activeTxn = ptxn; - return true; -} - -bool BerkeleyBatch::TxnCommit() -{ - if (!pdb || !activeTxn) - return false; - int ret = activeTxn->commit(0); - activeTxn = nullptr; - return (ret == 0); -} - -bool BerkeleyBatch::TxnAbort() -{ - if (!pdb || !activeTxn) - return false; - int ret = activeTxn->abort(); - activeTxn = nullptr; - return (ret == 0); -} - -bool BerkeleyDatabaseSanityCheck() -{ - int major, minor; - DbEnv::version(&major, &minor, nullptr); - - /* If the major version differs, or the minor version of library is *older* - * than the header that was compiled against, flag an error. - */ - if (major != DB_VERSION_MAJOR || minor < DB_VERSION_MINOR) { - LogPrintf("BerkeleyDB database version conflict: header version is %d.%d, library version is %d.%d\n", - DB_VERSION_MAJOR, DB_VERSION_MINOR, major, minor); - return false; - } - - return true; -} - -std::string BerkeleyDatabaseVersion() -{ - return DbEnv::version(nullptr, nullptr, nullptr); -} - -bool BerkeleyBatch::ReadKey(DataStream&& key, DataStream& value) -{ - if (!pdb) - return false; - - SafeDbt datKey(key.data(), key.size()); - - SafeDbt datValue; - int ret = pdb->get(activeTxn, datKey, datValue, 0); - if (ret == 0 && datValue.get_data() != nullptr) { - value.clear(); - value.write(SpanFromDbt(datValue)); - return true; - } - return false; -} - -bool BerkeleyBatch::WriteKey(DataStream&& key, DataStream&& value, bool overwrite) -{ - if (!pdb) - return false; - if (fReadOnly) - assert(!"Write called on database in read-only mode"); - - SafeDbt datKey(key.data(), key.size()); - - SafeDbt datValue(value.data(), value.size()); - - int ret = pdb->put(activeTxn, datKey, datValue, (overwrite ? 0 : DB_NOOVERWRITE)); - return (ret == 0); -} - -bool BerkeleyBatch::EraseKey(DataStream&& key) -{ - if (!pdb) - return false; - if (fReadOnly) - assert(!"Erase called on database in read-only mode"); - - SafeDbt datKey(key.data(), key.size()); - - int ret = pdb->del(activeTxn, datKey, 0); - return (ret == 0 || ret == DB_NOTFOUND); -} - -bool BerkeleyBatch::HasKey(DataStream&& key) -{ - if (!pdb) - return false; - - SafeDbt datKey(key.data(), key.size()); - - int ret = pdb->exists(activeTxn, datKey, 0); - return ret == 0; -} - -bool BerkeleyBatch::ErasePrefix(std::span prefix) -{ - // Because this function erases records one by one, ensure that it is executed within a txn context. - // Otherwise, consistency is at risk; it's possible that certain records are removed while others - // remain due to an internal failure during the procedure. - // Additionally, the Dbc::del() cursor delete call below would fail without an active transaction. - if (!Assume(activeTxn)) return false; - - auto cursor{std::make_unique(m_database, *this)}; - // const_cast is safe below even though prefix_key is an in/out parameter, - // because we are not using the DB_DBT_USERMEM flag, so BDB will allocate - // and return a different output data pointer - Dbt prefix_key{const_cast(prefix.data()), static_cast(prefix.size())}, prefix_value{}; - int ret{cursor->dbc()->get(&prefix_key, &prefix_value, DB_SET_RANGE)}; - for (int flag{DB_CURRENT}; ret == 0; flag = DB_NEXT) { - SafeDbt key, value; - ret = cursor->dbc()->get(key, value, flag); - if (ret != 0 || key.get_size() < prefix.size() || memcmp(key.get_data(), prefix.data(), prefix.size()) != 0) break; - ret = cursor->dbc()->del(0); - } - cursor.reset(); - return ret == 0 || ret == DB_NOTFOUND; -} - -void BerkeleyDatabase::AddRef() -{ - LOCK(cs_db); - if (m_refcount < 0) { - m_refcount = 1; - } else { - m_refcount++; - } -} - -void BerkeleyDatabase::RemoveRef() -{ - LOCK(cs_db); - m_refcount--; - if (env) env->m_db_in_use.notify_all(); -} - -std::unique_ptr BerkeleyDatabase::MakeBatch(bool flush_on_close) -{ - return std::make_unique(*this, false, flush_on_close); -} - -std::unique_ptr MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error) -{ - fs::path data_file = BDBDataFile(path); - std::unique_ptr db; - { - LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor - fs::path data_filename = data_file.filename(); - std::shared_ptr env = GetBerkeleyEnv(data_file.parent_path(), options.use_shared_memory); - if (env->m_databases.count(data_filename)) { - error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", fs::PathToString(env->Directory() / data_filename))); - status = DatabaseStatus::FAILED_ALREADY_LOADED; - return nullptr; - } - db = std::make_unique(std::move(env), std::move(data_filename), options); - } - - if (options.verify && !db->Verify(error)) { - status = DatabaseStatus::FAILED_VERIFY; - return nullptr; - } - - status = DatabaseStatus::SUCCESS; - return db; -} -} // namespace wallet diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h deleted file mode 100644 index b8cfde6003..0000000000 --- a/src/wallet/bdb.h +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-present The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_WALLET_BDB_H -#define BITCOIN_WALLET_BDB_H - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -struct bilingual_str; - -class DbEnv; -class DbTxn; -class Db; -class Dbc; - -// This constant was introduced in BDB 4.0.14 and has never changed, but there -// is a belt-and-suspenders check in the cpp file just in case. -#define BDB_DB_FILE_ID_LEN 20 /* Unique file ID length. */ - -namespace wallet { - -struct WalletDatabaseFileId { - uint8_t value[BDB_DB_FILE_ID_LEN]; - bool operator==(const WalletDatabaseFileId& rhs) const; -}; - -class BerkeleyDatabase; - -class BerkeleyEnvironment -{ -private: - bool fDbEnvInit; - bool fMockDb; - // Don't change into fs::path, as that can result in - // shutdown problems/crashes caused by a static initialized internal pointer. - std::string strPath; - -public: - std::unique_ptr dbenv; - std::map> m_databases; - std::unordered_map m_fileids; - std::condition_variable_any m_db_in_use; - bool m_use_shared_memory; - - explicit BerkeleyEnvironment(const fs::path& env_directory, bool use_shared_memory); - BerkeleyEnvironment(); - ~BerkeleyEnvironment(); - void Reset(); - - bool IsMock() const { return fMockDb; } - bool IsInitialized() const { return fDbEnvInit; } - fs::path Directory() const { return fs::PathFromString(strPath); } - - bool Open(bilingual_str& error); - void Close(); - void Flush(bool fShutdown); - void CheckpointLSN(const std::string& strFile); - - void CloseDb(const fs::path& filename); - void ReloadDbEnv(); - - DbTxn* TxnBegin(int flags); -}; - -/** Get BerkeleyEnvironment given a directory path. */ -std::shared_ptr GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory); - -class BerkeleyBatch; - -/** An instance of this class represents one database. - * For BerkeleyDB this is just a (env, strFile) tuple. - **/ -class BerkeleyDatabase : public WalletDatabase -{ -public: - BerkeleyDatabase() = delete; - - /** Create DB handle to real database */ - BerkeleyDatabase(std::shared_ptr env, fs::path filename, const DatabaseOptions& options); - - ~BerkeleyDatabase() override; - - /** Open the database if it is not already opened. */ - void Open() override; - - /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero - */ - bool Rewrite(const char* pszSkip=nullptr) override; - - /** Indicate that a new database user has begun using the database. */ - void AddRef() override; - /** Indicate that database user has stopped using the database and that it could be flushed or closed. */ - void RemoveRef() override; - - /** Back up the entire database to a file. - */ - bool Backup(const std::string& strDest) const override; - - /** Make sure all changes are flushed to database file. - */ - void Flush() override; - /** Flush to the database file and close the database. - * Also close the environment if no other databases are open in it. - */ - void Close() override; - /* flush the wallet passively (TRY_LOCK) - ideal to be called periodically */ - bool PeriodicFlush() override; - - void IncrementUpdateCounter() override; - - void ReloadDbEnv() override; - - /** Verifies the environment and database file */ - bool Verify(bilingual_str& error); - - /** Return path to main database filename */ - std::string Filename() override { return fs::PathToString(env->Directory() / m_filename); } - - std::string Format() override { return "bdb"; } - /** - * Pointer to shared database environment. - * - * Normally there is only one BerkeleyDatabase object per - * BerkeleyEnvivonment, but in the special, backwards compatible case where - * multiple wallet BDB data files are loaded from the same directory, this - * will point to a shared instance that gets freed when the last data file - * is closed. - */ - std::shared_ptr env; - - /** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */ - std::unique_ptr m_db; - - // Whether to byteswap - bool m_byteswap; - - fs::path m_filename; - int64_t m_max_log_mb; - - /** Make a BerkeleyBatch connected to this database */ - std::unique_ptr MakeBatch(bool flush_on_close = true) override; -}; - -class BerkeleyCursor : public DatabaseCursor -{ -private: - Dbc* m_cursor; - std::vector m_key_prefix; - bool m_first{true}; - -public: - // Constructor for cursor for records matching the prefix - // To match all records, an empty prefix may be provided. - explicit BerkeleyCursor(BerkeleyDatabase& database, const BerkeleyBatch& batch, std::span prefix = {}); - ~BerkeleyCursor() override; - - Status Next(DataStream& key, DataStream& value) override; - Dbc* dbc() const { return m_cursor; } -}; - -/** RAII class that provides access to a Berkeley database */ -class BerkeleyBatch : public DatabaseBatch -{ -private: - bool ReadKey(DataStream&& key, DataStream& value) override; - bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override; - bool EraseKey(DataStream&& key) override; - bool HasKey(DataStream&& key) override; - bool ErasePrefix(std::span prefix) override; - -protected: - Db* pdb{nullptr}; - std::string strFile; - DbTxn* activeTxn{nullptr}; - bool fReadOnly; - bool fFlushOnClose; - BerkeleyEnvironment *env; - BerkeleyDatabase& m_database; - -public: - explicit BerkeleyBatch(BerkeleyDatabase& database, const bool fReadOnly, bool fFlushOnCloseIn=true); - ~BerkeleyBatch() override; - - BerkeleyBatch(const BerkeleyBatch&) = delete; - BerkeleyBatch& operator=(const BerkeleyBatch&) = delete; - - void Flush() override; - void Close() override; - - std::unique_ptr GetNewCursor() override; - std::unique_ptr GetNewPrefixCursor(std::span prefix) override; - bool TxnBegin() override; - bool TxnCommit() override; - bool TxnAbort() override; - bool HasActiveTxn() override { return activeTxn != nullptr; } - DbTxn* txn() const { return activeTxn; } -}; - -std::string BerkeleyDatabaseVersion(); - -/** Perform sanity check of runtime BDB version versus linked BDB version. - */ -bool BerkeleyDatabaseSanityCheck(); - -//! Return object giving access to Berkeley database at specified path. -std::unique_ptr MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error); -} // namespace wallet - -#endif // BITCOIN_WALLET_BDB_H diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index db4284595f..9de0572947 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -155,8 +155,6 @@ void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options) { // Override current options with args values, if any were specified options.use_unsafe_sync = args.GetBoolArg("-unsafesqlitesync", options.use_unsafe_sync); - options.use_shared_memory = !args.GetBoolArg("-privdb", !options.use_shared_memory); - options.max_log_mb = args.GetIntArg("-dblogsize", options.max_log_mb); } } // namespace wallet diff --git a/src/wallet/db.h b/src/wallet/db.h index b632e2df0e..7870dcc4b2 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -62,7 +62,6 @@ public: DatabaseBatch(const DatabaseBatch&) = delete; DatabaseBatch& operator=(const DatabaseBatch&) = delete; - virtual void Flush() = 0; virtual void Close() = 0; template @@ -131,7 +130,7 @@ class WalletDatabase { public: /** Create dummy DB handle */ - WalletDatabase() : nUpdateCounter(0) {} + WalletDatabase() = default; virtual ~WalletDatabase() = default; /** Open the database if it is not already opened. */ @@ -139,10 +138,6 @@ public: //! Counts the number of active database users to be sure that the database is not closed while someone is using it std::atomic m_refcount{0}; - /** Indicate the a new database user has began using the database. Increments m_refcount */ - virtual void AddRef() = 0; - /** Indicate that database user has stopped using the database and that it could be flushed or closed. Decrement m_refcount */ - virtual void RemoveRef() = 0; /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero */ @@ -152,39 +147,23 @@ public: */ virtual bool Backup(const std::string& strDest) const = 0; - /** Make sure all changes are flushed to database file. - */ - virtual void Flush() = 0; /** Flush to the database file and close the database. * Also close the environment if no other databases are open in it. */ virtual void Close() = 0; - /* flush the wallet passively (TRY_LOCK) - ideal to be called periodically */ - virtual bool PeriodicFlush() = 0; - - virtual void IncrementUpdateCounter() = 0; - - virtual void ReloadDbEnv() = 0; /** Return path to main database file for logs and error messages. */ virtual std::string Filename() = 0; virtual std::string Format() = 0; - std::atomic nUpdateCounter; - unsigned int nLastSeen{0}; - unsigned int nLastFlushed{0}; - int64_t nLastWalletUpdate{0}; - /** Make a DatabaseBatch connected to this database */ - virtual std::unique_ptr MakeBatch(bool flush_on_close = true) = 0; + virtual std::unique_ptr MakeBatch() = 0; }; enum class DatabaseFormat { SQLITE, BERKELEY_RO, - BERKELEY_SWAP, }; struct DatabaseOptions { diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index f17d100e75..0a58285a96 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -48,7 +48,7 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet if (require_mine) { // check that original tx consists entirely of our inputs // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) - isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; + isminefilter filter = ISMINE_SPENDABLE; if (!AllInputsMine(wallet, *wtx.tx, filter)) { errors.emplace_back(Untranslated("Transaction contains inputs that don't belong to this wallet")); return feebumper::Result::WALLET_ERROR; diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 403342f1cf..9ac90cecd5 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -18,9 +18,6 @@ #include #include #include -#ifdef USE_BDB -#include -#endif #include #include #include @@ -81,15 +78,6 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const #endif argsman.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); -#ifdef USE_BDB - argsman.AddArg("-dblogsize=", strprintf("Flush wallet database activity from memory to disk log every megabytes (default: %u)", DatabaseOptions().max_log_mb), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); - argsman.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); - argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", !DatabaseOptions().use_shared_memory), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); - argsman.AddArg("-swapbdbendian", "Swaps the internal endianness of BDB wallet databases (default: false)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); -#else - argsman.AddHiddenArgs({"-dblogsize", "-flushwallet", "-privdb", "-swapbdbendian"}); -#endif - argsman.AddArg("-unsafesqlitesync", "Set SQLite synchronous=OFF to disable waiting for the database to sync to disk. This is unsafe and can cause data loss and corruption. This option is only used by tests to improve their performance (default: false)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 21e8a0b3bd..9ce7b5b24f 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -175,14 +175,6 @@ public: LOCK(m_wallet->cs_wallet); return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; } - bool haveWatchOnly() override - { - auto spk_man = m_wallet->GetLegacyScriptPubKeyMan(); - if (spk_man) { - return spk_man->HaveWatchOnly(); - } - return false; - }; bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional& purpose) override { return m_wallet->SetAddressBook(dest, name, purpose); @@ -407,12 +399,7 @@ public: result.balance = bal.m_mine_trusted; result.unconfirmed_balance = bal.m_mine_untrusted_pending; result.immature_balance = bal.m_mine_immature; - result.have_watch_only = haveWatchOnly(); - if (result.have_watch_only) { - result.watch_only_balance = bal.m_watchonly_trusted; - result.unconfirmed_watch_only_balance = bal.m_watchonly_untrusted_pending; - result.immature_watch_only_balance = bal.m_watchonly_immature; - } + result.have_watch_only = false; return result; } bool tryGetBalances(WalletBalances& balances, uint256& block_hash) override @@ -516,7 +503,6 @@ public: bool hasExternalSigner() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER); } bool privateKeysDisabled() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } bool taprootEnabled() override { - if (m_wallet->IsLegacy()) return false; auto spk_man = m_wallet->GetScriptPubKeyMan(OutputType::BECH32M, /*internal=*/false); return spk_man != nullptr; } @@ -526,7 +512,6 @@ public: { RemoveWallet(m_context, m_wallet, /*load_on_start=*/false); } - bool isLegacy() override { return m_wallet->IsLegacy(); } std::unique_ptr handleUnload(UnloadFn fn) override { return MakeSignalHandler(m_wallet->NotifyUnload.connect(fn)); @@ -593,7 +578,7 @@ public: m_context.scheduler = &scheduler; return StartWallets(m_context); } - void flush() override { return FlushWallets(m_context); } + void flush() override {} void stop() override { return StopWallets(m_context); } void setMockTime(int64_t time) override { return SetMockTime(time); } void schedulerMockForward(std::chrono::seconds delta) override { Assert(m_context.scheduler)->MockForward(delta); } diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index e77999b111..3fdad7d6fb 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -159,20 +159,9 @@ void StartWallets(WalletContext& context) pwallet->postInitProcess(); } - // Schedule periodic wallet flushes and tx rebroadcasts - if (context.args->GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) { - context.scheduler->scheduleEvery([&context] { MaybeCompactWalletDB(context); }, 500ms); - } context.scheduler->scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min); } -void FlushWallets(WalletContext& context) -{ - for (const std::shared_ptr& pwallet : GetWallets(context)) { - pwallet->Flush(); - } -} - void StopWallets(WalletContext& context) { for (const std::shared_ptr& pwallet : GetWallets(context)) { diff --git a/src/wallet/load.h b/src/wallet/load.h index c079cad955..e7224c27ee 100644 --- a/src/wallet/load.h +++ b/src/wallet/load.h @@ -28,9 +28,6 @@ bool LoadWallets(WalletContext& context); //! Complete startup of wallets. void StartWallets(WalletContext& context); -//! Flush all wallets in preparation for shutdown. -void FlushWallets(WalletContext& context); - //! Stop all wallets. Wallets will be flushed first. void StopWallets(WalletContext& context); diff --git a/src/wallet/migrate.cpp b/src/wallet/migrate.cpp index 735279f8bf..aab31f7ab1 100644 --- a/src/wallet/migrate.cpp +++ b/src/wallet/migrate.cpp @@ -699,7 +699,7 @@ void BerkeleyRODatabase::Open() } } -std::unique_ptr BerkeleyRODatabase::MakeBatch(bool flush_on_close) +std::unique_ptr BerkeleyRODatabase::MakeBatch() { return std::make_unique(*this); } diff --git a/src/wallet/migrate.h b/src/wallet/migrate.h index 58f3e5701a..6f388809f0 100644 --- a/src/wallet/migrate.h +++ b/src/wallet/migrate.h @@ -35,11 +35,6 @@ public: /** Open the database if it is not already opened. */ void Open() override; - /** Indicate the a new database user has began using the database. Increments m_refcount */ - void AddRef() override {} - /** Indicate that database user has stopped using the database and that it could be flushed or closed. Decrement m_refcount */ - void RemoveRef() override {} - /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero */ bool Rewrite(const char* pszSkip = nullptr) override { return false; } @@ -48,20 +43,10 @@ public: */ bool Backup(const std::string& strDest) const override; - /** Make sure all changes are flushed to database file. - */ - void Flush() override {} /** Flush to the database file and close the database. * Also close the environment if no other databases are open in it. */ void Close() override {} - /* flush the wallet passively (TRY_LOCK) - ideal to be called periodically */ - bool PeriodicFlush() override { return false; } - - void IncrementUpdateCounter() override {} - - void ReloadDbEnv() override {} /** Return path to main database file for logs and error messages. */ std::string Filename() override { return fs::PathToString(m_filepath); } @@ -69,7 +54,7 @@ public: std::string Format() override { return "bdb_ro"; } /** Make a DatabaseBatch connected to this database */ - std::unique_ptr MakeBatch(bool flush_on_close = true) override; + std::unique_ptr MakeBatch() override; }; class BerkeleyROCursor : public DatabaseCursor @@ -107,7 +92,6 @@ public: BerkeleyROBatch(const BerkeleyROBatch&) = delete; BerkeleyROBatch& operator=(const BerkeleyROBatch&) = delete; - void Flush() override {} void Close() override {} std::unique_ptr GetNewCursor() override { return std::make_unique(m_database); } diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp index 1c2951deee..0ea1cf9e1c 100644 --- a/src/wallet/rpc/addresses.cpp +++ b/src/wallet/rpc/addresses.cpp @@ -54,8 +54,6 @@ RPCHelpMan getnewaddress() std::optional parsed = ParseOutputType(request.params[1].get_str()); if (!parsed) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); - } else if (parsed.value() == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); } output_type = parsed.value(); } @@ -101,8 +99,6 @@ RPCHelpMan getrawchangeaddress() std::optional parsed = ParseOutputType(request.params[0].get_str()); if (!parsed) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); - } else if (parsed.value() == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); } output_type = parsed.value(); } @@ -215,124 +211,6 @@ RPCHelpMan listaddressgroupings() }; } -RPCHelpMan addmultisigaddress() -{ - return RPCHelpMan{"addmultisigaddress", - "\nAdd an nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" - "Each key is a Bitcoin address or hex-encoded public key.\n" - "This functionality is only intended for use with non-watchonly addresses.\n" - "See `importaddress` for watchonly p2sh address support.\n" - "If 'label' is specified, assign address to that label.\n" - "Note: This command is only compatible with legacy wallets.\n", - { - {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys or addresses."}, - {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The bitcoin addresses or hex-encoded public keys", - { - {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address or hex-encoded public key"}, - }, - }, - {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A label to assign the addresses to."}, - {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {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::STR, "", ""}, - }}, - } - }, - RPCExamples{ - "\nAdd a multisig address from 2 addresses\n" - + HelpExampleCli("addmultisigaddress", "2 \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); - - LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); - - const std::string label{LabelFromValue(request.params[2])}; - - int required = request.params[0].getInt(); - - // Get the public keys - const UniValue& keys_or_addrs = request.params[1].get_array(); - std::vector pubkeys; - for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) { - if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { - pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); - } else { - pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str())); - } - } - - OutputType output_type = pwallet->m_default_address_type; - if (!request.params[3].isNull()) { - std::optional parsed = ParseOutputType(request.params[3].get_str()); - if (!parsed) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str())); - } else if (parsed.value() == OutputType::BECH32M) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m multisig addresses cannot be created with legacy wallets"); - } - output_type = parsed.value(); - } - - // Construct multisig scripts - FlatSigningProvider provider; - CScript inner; - CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, provider, inner); - - // Import scripts into the wallet - for (const auto& [id, script] : provider.scripts) { - // Due to a bug in the legacy wallet, the p2sh maximum script size limit is also imposed on 'p2sh-segwit' and 'bech32' redeem scripts. - // Even when redeem scripts over MAX_SCRIPT_ELEMENT_SIZE bytes are valid for segwit output types, we don't want to - // enable it because: - // 1) It introduces a compatibility-breaking change requiring downgrade protection; older wallets would be unable to interact with these "new" legacy wallets. - // 2) Considering the ongoing deprecation of the legacy spkm, this issue adds another good reason to transition towards descriptors. - if (script.size() > MAX_SCRIPT_ELEMENT_SIZE) throw JSONRPCError(RPC_WALLET_ERROR, "Unsupported multisig script size for legacy wallet. Upgrade to descriptors to overcome this limitation for p2sh-segwit or bech32 scripts"); - - if (!spk_man.AddCScript(script)) { - if (CScript inner_script; spk_man.GetCScript(CScriptID(script), inner_script)) { - CHECK_NONFATAL(inner_script == script); // Nothing to add, script already contained by the wallet - continue; - } - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error importing script into the wallet")); - } - } - - // Store destination in the addressbook - pwallet->SetAddressBook(dest, label, AddressPurpose::SEND); - - // Make the descriptor - std::unique_ptr descriptor = InferDescriptor(GetScriptForDestination(dest), spk_man); - - UniValue result(UniValue::VOBJ); - result.pushKV("address", EncodeDestination(dest)); - result.pushKV("redeemScript", HexStr(inner)); - result.pushKV("descriptor", descriptor->ToString()); - - UniValue warnings(UniValue::VARR); - if (descriptor->GetOutputType() != output_type) { - // Only warns if the user has explicitly chosen an address type we cannot generate - warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); - } - PushWarnings(warnings, result); - - return result; -}, - }; -} - RPCHelpMan keypoolrefill() { return RPCHelpMan{"keypoolrefill", @@ -351,10 +229,6 @@ RPCHelpMan keypoolrefill() std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; - if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } - LOCK(pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool @@ -377,38 +251,6 @@ RPCHelpMan keypoolrefill() }; } -RPCHelpMan newkeypool() -{ - return RPCHelpMan{"newkeypool", - "\nEntirely clears and refills the keypool.\n" - "WARNING: On non-HD wallets, this will require a new backup immediately, to include the new keys.\n" - "When restoring a backup of an HD wallet created before the newkeypool command is run, funds received to\n" - "new addresses may not appear automatically. They have not been lost, but the wallet may not find them.\n" - "This can be fixed by running the newkeypool command on the backup and then rescanning, so the wallet\n" - "re-generates the required keys." + - HELP_REQUIRING_PASSPHRASE, - {}, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{ - HelpExampleCli("newkeypool", "") - + HelpExampleRpc("newkeypool", "") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - LOCK(pwallet->cs_wallet); - - LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true); - spk_man.NewKeyPool(); - - return UniValue::VNULL; -}, - }; -} - - class DescribeWalletAddressVisitor { public: diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 39d8509007..d5b1ddb9fb 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -2,8 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include // IWYU pragma: keep - #include #include #include @@ -34,290 +32,8 @@ using interfaces::FoundBlock; -using util::SplitString; namespace wallet { -std::string static EncodeDumpString(const std::string &str) { - std::stringstream ret; - for (const unsigned char c : str) { - if (c <= 32 || c >= 128 || c == '%') { - ret << '%' << HexStr({&c, 1}); - } else { - ret << c; - } - } - return ret.str(); -} - -static std::string DecodeDumpString(const std::string &str) { - std::stringstream ret; - for (unsigned int pos = 0; pos < str.length(); pos++) { - unsigned char c = str[pos]; - if (c == '%' && pos+2 < str.length()) { - c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) | - ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15)); - pos += 2; - } - ret << c; - } - return ret.str(); -} - -static bool GetWalletAddressesForKey(const LegacyScriptPubKeyMan* spk_man, const CWallet& wallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) -{ - bool fLabelFound = false; - CKey key; - spk_man->GetKey(keyid, key); - for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) { - const auto* address_book_entry = wallet.FindAddressBookEntry(dest); - if (address_book_entry) { - if (!strAddr.empty()) { - strAddr += ","; - } - strAddr += EncodeDestination(dest); - strLabel = EncodeDumpString(address_book_entry->GetLabel()); - fLabelFound = true; - } - } - if (!fLabelFound) { - strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), wallet.m_default_address_type)); - } - return fLabelFound; -} - -static const int64_t TIMESTAMP_MIN = 0; - -static void RescanWallet(CWallet& wallet, const WalletRescanReserver& reserver, int64_t time_begin = TIMESTAMP_MIN, bool update = true) -{ - int64_t scanned_time = wallet.RescanFromTime(time_begin, reserver, update); - if (wallet.IsAbortingRescan()) { - throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); - } else if (scanned_time > time_begin) { - throw JSONRPCError(RPC_WALLET_ERROR, "Rescan was unable to fully rescan the blockchain. Some transactions may be missing."); - } -} - -static void EnsureBlockDataFromTime(const CWallet& wallet, int64_t timestamp) -{ - auto& chain{wallet.chain()}; - if (!chain.havePruned()) { - return; - } - - int height{0}; - const bool found{chain.findFirstBlockWithTimeAndHeight(timestamp - TIMESTAMP_WINDOW, 0, FoundBlock().height(height))}; - - uint256 tip_hash{WITH_LOCK(wallet.cs_wallet, return wallet.GetLastBlockHash())}; - if (found && !chain.hasBlocks(tip_hash, height)) { - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Pruned blocks from height %d required to import keys. Use RPC call getblockchaininfo to determine your pruned height.", height)); - } -} - -RPCHelpMan importprivkey() -{ - return RPCHelpMan{"importprivkey", - "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" - "Hint: use importmulti to import more than one private key.\n" - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" - "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" - "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" - "Note: Use \"getwalletinfo\" to query the scanning progress.\n" - "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"combo(X)\" for descriptor wallets.\n", - { - {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"}, - {"label", RPCArg::Type::STR, RPCArg::DefaultHint{"current label if address exists, otherwise \"\""}, "An optional label"}, - {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, - }, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{ - "\nDump a private key\n" - + HelpExampleCli("dumpprivkey", "\"myaddress\"") + - "\nImport the private key with rescan\n" - + HelpExampleCli("importprivkey", "\"mykey\"") + - "\nImport using a label and without rescan\n" - + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") + - "\nImport using default blank label and without rescan\n" - + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); - } - - EnsureLegacyScriptPubKeyMan(*pwallet, true); - - WalletRescanReserver reserver(*pwallet); - bool fRescan = true; - { - LOCK(pwallet->cs_wallet); - - EnsureWalletIsUnlocked(*pwallet); - - std::string strSecret = request.params[0].get_str(); - const std::string strLabel{LabelFromValue(request.params[1])}; - - // Whether to perform rescan after import - if (!request.params[2].isNull()) - fRescan = request.params[2].get_bool(); - - if (fRescan && pwallet->chain().havePruned()) { - // Exit early and print an error. - // If a block is pruned after this check, we will import the key(s), - // but fail the rescan with a generic error. - throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); - } - - if (fRescan && !reserver.reserve()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); - } - - CKey key = DecodeSecret(strSecret); - if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - - CPubKey pubkey = key.GetPubKey(); - CHECK_NONFATAL(key.VerifyPubKey(pubkey)); - CKeyID vchAddress = pubkey.GetID(); - { - pwallet->MarkDirty(); - - // We don't know which corresponding address will be used; - // label all new addresses, and label existing addresses if a - // label was passed. - for (const auto& dest : GetAllDestinationsForKey(pubkey)) { - if (!request.params[1].isNull() || !pwallet->FindAddressBookEntry(dest)) { - pwallet->SetAddressBook(dest, strLabel, AddressPurpose::RECEIVE); - } - } - - // Use timestamp of 1 to scan the whole chain - if (!pwallet->ImportPrivKeys({{vchAddress, key}}, 1)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - } - - // Add the wpkh script for this key if possible - if (pubkey.IsCompressed()) { - pwallet->ImportScripts({GetScriptForDestination(WitnessV0KeyHash(vchAddress))}, /*timestamp=*/0); - } - } - } - if (fRescan) { - RescanWallet(*pwallet, reserver); - } - - return UniValue::VNULL; -}, - }; -} - -RPCHelpMan importaddress() -{ - return RPCHelpMan{"importaddress", - "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" - "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" - "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" - "If you have the full public key, you should call importpubkey instead of this.\n" - "Hint: use importmulti to import more than one address.\n" - "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n" - "as change, and not show up in many RPCs.\n" - "Note: Use \"getwalletinfo\" to query the scanning progress.\n" - "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" for descriptor wallets.\n", - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"}, - {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"}, - {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, - {"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Add the P2SH version of the script as well"}, - }, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{ - "\nImport an address with rescan\n" - + HelpExampleCli("importaddress", "\"myaddress\"") + - "\nImport using a label without rescan\n" - + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - EnsureLegacyScriptPubKeyMan(*pwallet, true); - - const std::string strLabel{LabelFromValue(request.params[1])}; - - // Whether to perform rescan after import - bool fRescan = true; - if (!request.params[2].isNull()) - fRescan = request.params[2].get_bool(); - - if (fRescan && pwallet->chain().havePruned()) { - // Exit early and print an error. - // If a block is pruned after this check, we will import the key(s), - // but fail the rescan with a generic error. - throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); - } - - WalletRescanReserver reserver(*pwallet); - if (fRescan && !reserver.reserve()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); - } - - // Whether to import a p2sh version, too - bool fP2SH = false; - if (!request.params[3].isNull()) - fP2SH = request.params[3].get_bool(); - - { - LOCK(pwallet->cs_wallet); - - CTxDestination dest = DecodeDestination(request.params[0].get_str()); - if (IsValidDestination(dest)) { - if (fP2SH) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); - } - if (OutputTypeFromDestination(dest) == OutputType::BECH32M) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets"); - } - - pwallet->MarkDirty(); - - pwallet->ImportScriptPubKeys(strLabel, {GetScriptForDestination(dest)}, /*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1); - } else if (IsHex(request.params[0].get_str())) { - std::vector data(ParseHex(request.params[0].get_str())); - CScript redeem_script(data.begin(), data.end()); - - std::set scripts = {redeem_script}; - pwallet->ImportScripts(scripts, /*timestamp=*/0); - - if (fP2SH) { - scripts.insert(GetScriptForDestination(ScriptHash(redeem_script))); - } - - pwallet->ImportScriptPubKeys(strLabel, scripts, /*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); - } - } - if (fRescan) - { - RescanWallet(*pwallet, reserver); - pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); - } - - return UniValue::VNULL; -}, - }; -} - RPCHelpMan importprunedfunds() { return RPCHelpMan{"importprunedfunds", @@ -406,842 +122,6 @@ RPCHelpMan removeprunedfunds() }; } -RPCHelpMan importpubkey() -{ - return RPCHelpMan{"importpubkey", - "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" - "Hint: use importmulti to import more than one public key.\n" - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" - "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" - "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" - "Note: Use \"getwalletinfo\" to query the scanning progress.\n" - "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"combo(X)\" for descriptor wallets.\n", - { - {"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"}, - {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"}, - {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, - }, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{ - "\nImport a public key with rescan\n" - + HelpExampleCli("importpubkey", "\"mypubkey\"") + - "\nImport using a label without rescan\n" - + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - EnsureLegacyScriptPubKeyMan(*pwallet, true); - - const std::string strLabel{LabelFromValue(request.params[1])}; - - // Whether to perform rescan after import - bool fRescan = true; - if (!request.params[2].isNull()) - fRescan = request.params[2].get_bool(); - - if (fRescan && pwallet->chain().havePruned()) { - // Exit early and print an error. - // If a block is pruned after this check, we will import the key(s), - // but fail the rescan with a generic error. - throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); - } - - WalletRescanReserver reserver(*pwallet); - if (fRescan && !reserver.reserve()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); - } - - CPubKey pubKey = HexToPubKey(request.params[0].get_str()); - - { - LOCK(pwallet->cs_wallet); - - std::set script_pub_keys; - for (const auto& dest : GetAllDestinationsForKey(pubKey)) { - script_pub_keys.insert(GetScriptForDestination(dest)); - } - - pwallet->MarkDirty(); - - pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, /*have_solving_data=*/true, /*apply_label=*/true, /*timestamp=*/1); - - pwallet->ImportPubKeys({{pubKey.GetID(), false}}, {{pubKey.GetID(), pubKey}} , /*key_origins=*/{}, /*add_keypool=*/false, /*timestamp=*/1); - } - if (fRescan) - { - RescanWallet(*pwallet, reserver); - pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); - } - - return UniValue::VNULL; -}, - }; -} - - -RPCHelpMan importwallet() -{ - return RPCHelpMan{"importwallet", - "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n" - "Note: Blockchain and Mempool will be rescanned after a successful import. Use \"getwalletinfo\" to query the scanning progress.\n" - "Note: This command is only compatible with legacy wallets.\n", - { - {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"}, - }, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{ - "\nDump the wallet\n" - + HelpExampleCli("dumpwallet", "\"test\"") + - "\nImport the wallet\n" - + HelpExampleCli("importwallet", "\"test\"") + - "\nImport using the json rpc call\n" - + HelpExampleRpc("importwallet", "\"test\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - EnsureLegacyScriptPubKeyMan(*pwallet, true); - - WalletRescanReserver reserver(*pwallet); - if (!reserver.reserve()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); - } - - int64_t nTimeBegin = 0; - bool fGood = true; - { - LOCK(pwallet->cs_wallet); - - EnsureWalletIsUnlocked(*pwallet); - - std::ifstream file; - file.open(fs::u8path(request.params[0].get_str()), std::ios::in | std::ios::ate); - if (!file.is_open()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); - } - CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin))); - - int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); - file.seekg(0, file.beg); - - // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which - // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button. - pwallet->chain().showProgress(strprintf("%s %s", pwallet->GetDisplayName(), _("Importing…")), 0, false); // show progress dialog in GUI - std::vector> keys; - std::vector> scripts; - while (file.good()) { - pwallet->chain().showProgress("", std::max(1, std::min(50, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false); - std::string line; - std::getline(file, line); - if (line.empty() || line[0] == '#') - continue; - - std::vector vstr = SplitString(line, ' '); - if (vstr.size() < 2) - continue; - CKey key = DecodeSecret(vstr[0]); - if (key.IsValid()) { - int64_t nTime{ParseISO8601DateTime(vstr[1]).value_or(0)}; - std::string strLabel; - bool fLabel = true; - for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { - if (vstr[nStr].front() == '#') - break; - if (vstr[nStr] == "change=1") - fLabel = false; - if (vstr[nStr] == "reserve=1") - fLabel = false; - if (vstr[nStr].starts_with("label=")) { - strLabel = DecodeDumpString(vstr[nStr].substr(6)); - fLabel = true; - } - } - nTimeBegin = std::min(nTimeBegin, nTime); - keys.emplace_back(key, nTime, fLabel, strLabel); - } else if(IsHex(vstr[0])) { - std::vector vData(ParseHex(vstr[0])); - CScript script = CScript(vData.begin(), vData.end()); - int64_t birth_time{ParseISO8601DateTime(vstr[1]).value_or(0)}; - if (birth_time > 0) nTimeBegin = std::min(nTimeBegin, birth_time); - scripts.emplace_back(script, birth_time); - } - } - file.close(); - EnsureBlockDataFromTime(*pwallet, nTimeBegin); - // We now know whether we are importing private keys, so we can error if private keys are disabled - if (keys.size() > 0 && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI - throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when private keys are disabled"); - } - double total = (double)(keys.size() + scripts.size()); - double progress = 0; - for (const auto& key_tuple : keys) { - pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false); - const CKey& key = std::get<0>(key_tuple); - int64_t time = std::get<1>(key_tuple); - bool has_label = std::get<2>(key_tuple); - std::string label = std::get<3>(key_tuple); - - CPubKey pubkey = key.GetPubKey(); - CHECK_NONFATAL(key.VerifyPubKey(pubkey)); - CKeyID keyid = pubkey.GetID(); - - pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid))); - - if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) { - pwallet->WalletLogPrintf("Error importing key for %s\n", EncodeDestination(PKHash(keyid))); - fGood = false; - continue; - } - - if (has_label) - pwallet->SetAddressBook(PKHash(keyid), label, AddressPurpose::RECEIVE); - progress++; - } - for (const auto& script_pair : scripts) { - pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false); - const CScript& script = script_pair.first; - int64_t time = script_pair.second; - - if (!pwallet->ImportScripts({script}, time)) { - pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script)); - fGood = false; - continue; - } - - progress++; - } - pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI - } - pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI - RescanWallet(*pwallet, reserver, nTimeBegin, /*update=*/false); - pwallet->MarkDirty(); - - if (!fGood) - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet"); - - return UniValue::VNULL; -}, - }; -} - -RPCHelpMan dumpprivkey() -{ - return RPCHelpMan{"dumpprivkey", - "\nReveals the private key corresponding to 'address'.\n" - "Then the importprivkey can be used with this output\n" - "Note: This command is only compatible with legacy wallets.\n", - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for the private key"}, - }, - RPCResult{ - RPCResult::Type::STR, "key", "The private key" - }, - RPCExamples{ - HelpExampleCli("dumpprivkey", "\"myaddress\"") - + HelpExampleCli("importprivkey", "\"mykey\"") - + HelpExampleRpc("dumpprivkey", "\"myaddress\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - const std::shared_ptr pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(*pwallet); - - LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); - - EnsureWalletIsUnlocked(*pwallet); - - std::string strAddress = request.params[0].get_str(); - CTxDestination dest = DecodeDestination(strAddress); - if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); - } - auto keyid = GetKeyForDestination(spk_man, dest); - if (keyid.IsNull()) { - throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); - } - CKey vchSecret; - if (!spk_man.GetKey(keyid, vchSecret)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); - } - return EncodeSecret(vchSecret); -}, - }; -} - - -RPCHelpMan dumpwallet() -{ - return RPCHelpMan{"dumpwallet", - "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n" - "Imported scripts are included in the dumpfile, but corresponding BIP173 addresses, etc. may not be added automatically by importwallet.\n" - "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n" - "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n" - "Note: This command is only compatible with legacy wallets.\n", - { - {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (absolute path recommended)"}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "filename", "The filename with full absolute path"}, - } - }, - RPCExamples{ - HelpExampleCli("dumpwallet", "\"test\"") - + HelpExampleRpc("dumpwallet", "\"test\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - const std::shared_ptr pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return UniValue::VNULL; - - const CWallet& wallet = *pwallet; - const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(wallet); - - // Make sure the results are valid at least up to the most recent block - // the user could have gotten from another RPC command prior to now - wallet.BlockUntilSyncedToCurrentChain(); - - LOCK(wallet.cs_wallet); - - EnsureWalletIsUnlocked(wallet); - - fs::path filepath = fs::u8path(request.params[0].get_str()); - filepath = fs::absolute(filepath); - - /* Prevent arbitrary files from being overwritten. There have been reports - * that users have overwritten wallet files this way: - * https://github.com/bitcoin/bitcoin/issues/9934 - * It may also avoid other security issues. - */ - if (fs::exists(filepath)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.utf8string() + " already exists. If you are sure this is what you want, move it out of the way first"); - } - - std::ofstream file; - file.open(filepath); - if (!file.is_open()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); - - std::map mapKeyBirth; - wallet.GetKeyBirthTimes(mapKeyBirth); - - int64_t block_time = 0; - CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), FoundBlock().time(block_time))); - - // Note: To avoid a lock order issue, access to cs_main must be locked before cs_KeyStore. - // So we do the two things in this function that lock cs_main first: GetKeyBirthTimes, and findBlock. - LOCK(spk_man.cs_KeyStore); - - const std::map& mapKeyPool = spk_man.GetAllReserveKeys(); - std::set scripts = spk_man.GetCScripts(); - - // sort time/key pairs - std::vector > vKeyBirth; - vKeyBirth.reserve(mapKeyBirth.size()); - for (const auto& entry : mapKeyBirth) { - vKeyBirth.emplace_back(entry.second, entry.first); - } - mapKeyBirth.clear(); - std::sort(vKeyBirth.begin(), vKeyBirth.end()); - - // produce output - file << strprintf("# Wallet dump created by %s %s\n", CLIENT_NAME, FormatFullVersion()); - file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); - file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString()); - file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time)); - file << "\n"; - - // add the base58check encoded extended master if the wallet uses HD - CKeyID seed_id = spk_man.GetHDChain().seed_id; - if (!seed_id.IsNull()) - { - CKey seed; - if (spk_man.GetKey(seed_id, seed)) { - CExtKey masterKey; - masterKey.SetSeed(seed); - - file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n"; - } - } - for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { - const CKeyID &keyid = it->second; - std::string strTime = FormatISO8601DateTime(it->first); - std::string strAddr; - std::string strLabel; - CKey key; - if (spk_man.GetKey(keyid, key)) { - CKeyMetadata metadata; - const auto it{spk_man.mapKeyMetadata.find(keyid)}; - if (it != spk_man.mapKeyMetadata.end()) metadata = it->second; - file << strprintf("%s %s ", EncodeSecret(key), strTime); - if (GetWalletAddressesForKey(&spk_man, wallet, keyid, strAddr, strLabel)) { - file << strprintf("label=%s", strLabel); - } else if (keyid == seed_id) { - file << "hdseed=1"; - } else if (mapKeyPool.count(keyid)) { - file << "reserve=1"; - } else if (metadata.hdKeypath == "s") { - file << "inactivehdseed=1"; - } else { - file << "change=1"; - } - file << strprintf(" # addr=%s%s\n", strAddr, (metadata.has_key_origin ? " hdkeypath="+WriteHDKeypath(metadata.key_origin.path, /*apostrophe=*/true) : "")); - } - } - file << "\n"; - for (const CScriptID &scriptid : scripts) { - CScript script; - std::string create_time = "0"; - std::string address = EncodeDestination(ScriptHash(scriptid)); - // get birth times for scripts with metadata - auto it = spk_man.m_script_metadata.find(scriptid); - if (it != spk_man.m_script_metadata.end()) { - create_time = FormatISO8601DateTime(it->second.nCreateTime); - } - if(spk_man.GetCScript(scriptid, script)) { - file << strprintf("%s %s script=1", HexStr(script), create_time); - file << strprintf(" # addr=%s\n", address); - } - } - file << "\n"; - file << "# End of dump\n"; - file.close(); - - UniValue reply(UniValue::VOBJ); - reply.pushKV("filename", filepath.utf8string()); - - return reply; -}, - }; -} - -struct ImportData -{ - // Input data - std::unique_ptr redeemscript; //!< Provided redeemScript; will be moved to `import_scripts` if relevant. - std::unique_ptr witnessscript; //!< Provided witnessScript; will be moved to `import_scripts` if relevant. - - // Output data - std::set import_scripts; - std::map used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability) - std::map> key_origins; -}; - -enum class ScriptContext -{ - TOP, //!< Top-level scriptPubKey - P2SH, //!< P2SH redeemScript - WITNESS_V0, //!< P2WSH witnessScript -}; - -// Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used. -// Returns an error string, or the empty string for success. -// NOLINTNEXTLINE(misc-no-recursion) -static std::string RecurseImportData(const CScript& script, ImportData& import_data, const ScriptContext script_ctx) -{ - // Use Solver to obtain script type and parsed pubkeys or hashes: - std::vector> solverdata; - TxoutType script_type = Solver(script, solverdata); - - switch (script_type) { - case TxoutType::PUBKEY: { - CPubKey pubkey(solverdata[0]); - import_data.used_keys.emplace(pubkey.GetID(), false); - return ""; - } - case TxoutType::PUBKEYHASH: { - CKeyID id = CKeyID(uint160(solverdata[0])); - import_data.used_keys[id] = true; - return ""; - } - case TxoutType::SCRIPTHASH: { - if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH"); - if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH"); - CHECK_NONFATAL(script_ctx == ScriptContext::TOP); - CScriptID id = CScriptID(uint160(solverdata[0])); - auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later. - if (!subscript) return "missing redeemscript"; - if (CScriptID(*subscript) != id) return "redeemScript does not match the scriptPubKey"; - import_data.import_scripts.emplace(*subscript); - return RecurseImportData(*subscript, import_data, ScriptContext::P2SH); - } - case TxoutType::MULTISIG: { - for (size_t i = 1; i + 1< solverdata.size(); ++i) { - CPubKey pubkey(solverdata[i]); - import_data.used_keys.emplace(pubkey.GetID(), false); - } - return ""; - } - case TxoutType::WITNESS_V0_SCRIPTHASH: { - if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WSH inside another P2WSH"); - CScriptID id{RIPEMD160(solverdata[0])}; - auto subscript = std::move(import_data.witnessscript); // Remove redeemscript from import_data to check for superfluous script later. - if (!subscript) return "missing witnessscript"; - if (CScriptID(*subscript) != id) return "witnessScript does not match the scriptPubKey or redeemScript"; - if (script_ctx == ScriptContext::TOP) { - import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WSH requires the TOP script imported (see script/ismine.cpp) - } - import_data.import_scripts.emplace(*subscript); - return RecurseImportData(*subscript, import_data, ScriptContext::WITNESS_V0); - } - case TxoutType::WITNESS_V0_KEYHASH: { - if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WPKH inside P2WSH"); - CKeyID id = CKeyID(uint160(solverdata[0])); - import_data.used_keys[id] = true; - if (script_ctx == ScriptContext::TOP) { - import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WPKH requires the TOP script imported (see script/ismine.cpp) - } - return ""; - } - case TxoutType::NULL_DATA: - return "unspendable script"; - case TxoutType::NONSTANDARD: - case TxoutType::WITNESS_UNKNOWN: - case TxoutType::WITNESS_V1_TAPROOT: - case TxoutType::ANCHOR: - return "unrecognized script"; - } // no default case, so the compiler can warn about missing cases - NONFATAL_UNREACHABLE(); -} - -static UniValue ProcessImportLegacy(ImportData& import_data, std::map& pubkey_map, std::map& privkey_map, std::set& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector>& ordered_pubkeys) -{ - UniValue warnings(UniValue::VARR); - - // First ensure scriptPubKey has either a script or JSON with "address" string - const UniValue& scriptPubKey = data["scriptPubKey"]; - bool isScript = scriptPubKey.getType() == UniValue::VSTR; - if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string"); - } - const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); - - // Optional fields. - const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; - const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : ""; - const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); - const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); - const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; - const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; - - if (data.exists("range")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for a non-descriptor import"); - } - - // Generate the script and destination for the scriptPubKey provided - CScript script; - if (!isScript) { - CTxDestination dest = DecodeDestination(output); - if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); - } - if (OutputTypeFromDestination(dest) == OutputType::BECH32M) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets"); - } - script = GetScriptForDestination(dest); - } else { - if (!IsHex(output)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\""); - } - std::vector vData(ParseHex(output)); - script = CScript(vData.begin(), vData.end()); - CTxDestination dest; - if (!ExtractDestination(script, dest) && !internal) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports."); - } - } - script_pub_keys.emplace(script); - - // Parse all arguments - if (strRedeemScript.size()) { - if (!IsHex(strRedeemScript)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); - } - auto parsed_redeemscript = ParseHex(strRedeemScript); - import_data.redeemscript = std::make_unique(parsed_redeemscript.begin(), parsed_redeemscript.end()); - } - if (witness_script_hex.size()) { - if (!IsHex(witness_script_hex)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string"); - } - auto parsed_witnessscript = ParseHex(witness_script_hex); - import_data.witnessscript = std::make_unique(parsed_witnessscript.begin(), parsed_witnessscript.end()); - } - for (size_t i = 0; i < pubKeys.size(); ++i) { - CPubKey pubkey = HexToPubKey(pubKeys[i].get_str()); - pubkey_map.emplace(pubkey.GetID(), pubkey); - ordered_pubkeys.emplace_back(pubkey.GetID(), internal); - } - for (size_t i = 0; i < keys.size(); ++i) { - const auto& str = keys[i].get_str(); - CKey key = DecodeSecret(str); - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - } - CPubKey pubkey = key.GetPubKey(); - CKeyID id = pubkey.GetID(); - if (pubkey_map.count(id)) { - pubkey_map.erase(id); - } - privkey_map.emplace(id, key); - } - - - // Verify and process input data - have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size(); - if (have_solving_data) { - // Match up data in import_data with the scriptPubKey in script. - auto error = RecurseImportData(script, import_data, ScriptContext::TOP); - - // Verify whether the watchonly option corresponds to the availability of private keys. - bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair& used_key){ return privkey_map.count(used_key.first) > 0; }); - if (!watchOnly && !spendable) { - warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); - } - if (watchOnly && spendable) { - warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); - } - - // Check that all required keys for solvability are provided. - if (error.empty()) { - for (const auto& require_key : import_data.used_keys) { - if (!require_key.second) continue; // Not a required key - if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) { - error = "some required keys are missing"; - } - } - } - - if (!error.empty()) { - warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript."); - import_data = ImportData(); - pubkey_map.clear(); - privkey_map.clear(); - have_solving_data = false; - } else { - // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided. - if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script."); - if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script."); - for (auto it = privkey_map.begin(); it != privkey_map.end(); ) { - auto oldit = it++; - if (import_data.used_keys.count(oldit->first) == 0) { - warnings.push_back("Ignoring irrelevant private key."); - privkey_map.erase(oldit); - } - } - for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) { - auto oldit = it++; - auto key_data_it = import_data.used_keys.find(oldit->first); - if (key_data_it == import_data.used_keys.end() || !key_data_it->second) { - warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH."); - pubkey_map.erase(oldit); - } - } - } - } - - return warnings; -} - -static UniValue ProcessImportDescriptor(ImportData& import_data, std::map& pubkey_map, std::map& privkey_map, std::set& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector>& ordered_pubkeys) -{ - UniValue warnings(UniValue::VARR); - - const std::string& descriptor = data["desc"].get_str(); - FlatSigningProvider keys; - std::string error; - auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true); - if (parsed_descs.empty()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); - } - if (parsed_descs.at(0)->GetOutputType() == OutputType::BECH32M) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m descriptors cannot be imported into legacy wallets"); - } - - std::optional internal; - if (data.exists("internal")) { - if (parsed_descs.size() > 1) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'"); - } - internal = data["internal"].get_bool(); - } - - have_solving_data = parsed_descs.at(0)->IsSolvable(); - const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false; - - int64_t range_start = 0, range_end = 0; - if (!parsed_descs.at(0)->IsRange() && data.exists("range")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); - } else if (parsed_descs.at(0)->IsRange()) { - if (!data.exists("range")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range"); - } - std::tie(range_start, range_end) = ParseDescriptorRange(data["range"]); - } - - // Only single key descriptors are allowed to be imported to a legacy wallet's keypool - bool can_keypool = parsed_descs.at(0)->IsSingleKey(); - - const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); - - for (size_t j = 0; j < parsed_descs.size(); ++j) { - const auto& parsed_desc = parsed_descs.at(j); - bool desc_internal = internal.has_value() && internal.value(); - if (parsed_descs.size() == 2) { - desc_internal = j == 1; - } else if (parsed_descs.size() > 2) { - CHECK_NONFATAL(!desc_internal); - } - // Expand all descriptors to get public keys and scripts, and private keys if available. - for (int i = range_start; i <= range_end; ++i) { - FlatSigningProvider out_keys; - std::vector scripts_temp; - parsed_desc->Expand(i, keys, scripts_temp, out_keys); - std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end())); - if (can_keypool) { - for (const auto& key_pair : out_keys.pubkeys) { - ordered_pubkeys.emplace_back(key_pair.first, desc_internal); - } - } - - for (const auto& x : out_keys.scripts) { - import_data.import_scripts.emplace(x.second); - } - - parsed_desc->ExpandPrivate(i, keys, out_keys); - - std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); - std::copy(out_keys.keys.begin(), out_keys.keys.end(), std::inserter(privkey_map, privkey_map.end())); - import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end()); - } - } - - for (size_t i = 0; i < priv_keys.size(); ++i) { - const auto& str = priv_keys[i].get_str(); - CKey key = DecodeSecret(str); - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - } - CPubKey pubkey = key.GetPubKey(); - CKeyID id = pubkey.GetID(); - - // Check if this private key corresponds to a public key from the descriptor - if (!pubkey_map.count(id)) { - warnings.push_back("Ignoring irrelevant private key."); - } else { - privkey_map.emplace(id, key); - } - } - - // Check if all the public keys have corresponding private keys in the import for spendability. - // This does not take into account threshold multisigs which could be spendable without all keys. - // Thus, threshold multisigs without all keys will be considered not spendable here, even if they are, - // perhaps triggering a false warning message. This is consistent with the current wallet IsMine check. - bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(), - [&](const std::pair& used_key) { - return privkey_map.count(used_key.first) > 0; - }) && std::all_of(import_data.key_origins.begin(), import_data.key_origins.end(), - [&](const std::pair>& entry) { - return privkey_map.count(entry.first) > 0; - }); - if (!watch_only && !spendable) { - warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); - } - if (watch_only && spendable) { - warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); - } - - return warnings; -} - -static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) -{ - UniValue warnings(UniValue::VARR); - UniValue result(UniValue::VOBJ); - - try { - const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; - // Internal addresses should not have a label - if (internal && data.exists("label")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); - } - const std::string label{LabelFromValue(data["label"])}; - const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false; - - // Add to keypool only works with privkeys disabled - if (add_keypool && !wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled"); - } - - ImportData import_data; - std::map pubkey_map; - std::map privkey_map; - std::set script_pub_keys; - std::vector> ordered_pubkeys; - bool have_solving_data; - - if (data.exists("scriptPubKey") && data.exists("desc")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided."); - } else if (data.exists("scriptPubKey")) { - warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); - } else if (data.exists("desc")) { - warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided."); - } - - // If private keys are disabled, abort if private keys are being imported - if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); - } - - // Check whether we have any work to do - for (const CScript& script : script_pub_keys) { - if (wallet.IsMine(script) & ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script) + "\")"); - } - } - - // All good, time to import - wallet.MarkDirty(); - if (!wallet.ImportScripts(import_data.import_scripts, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); - } - if (!wallet.ImportPrivKeys(privkey_map, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - } - if (!wallet.ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - if (!wallet.ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - result.pushKV("success", UniValue(true)); - } catch (const UniValue& e) { - result.pushKV("success", UniValue(false)); - result.pushKV("error", e); - } catch (...) { - result.pushKV("success", UniValue(false)); - - result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); - } - PushWarnings(warnings, result); - return result; -} - static int64_t GetImportTimestamp(const UniValue& data, int64_t now) { if (data.exists("timestamp")) { @@ -1256,207 +136,6 @@ static int64_t GetImportTimestamp(const UniValue& data, int64_t now) throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key"); } -RPCHelpMan importmulti() -{ - return RPCHelpMan{"importmulti", - "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), optionally rescanning the blockchain from the earliest creation time of the imported scripts. Requires a new wallet backup.\n" - "If an address/script is imported without all of the private keys required to spend from that address, it will be watchonly. The 'watchonly' option must be set to true in this case or a warning will be returned.\n" - "Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n" - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" - "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" - "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" - "Note: Use \"getwalletinfo\" to query the scanning progress.\n" - "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" for descriptor wallets.\n", - { - {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", - { - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"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", - RPCArgOptions{.type_str={"\"