diff options
97 files changed, 1240 insertions, 1057 deletions
diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 3ca7818eca..0000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,63 +0,0 @@ -version: '{branch}.{build}' -skip_tags: true -image: Visual Studio 2019 -configuration: Release -platform: x64 -clone_depth: 5 -environment: - PATH: 'C:\Python37-x64;C:\Python37-x64\Scripts;%PATH%' - PYTHONUTF8: 1 - QT_DOWNLOAD_URL: 'https://github.com/sipsorcery/qt_win_binary/releases/download/qt51211x64_static_vs2019_16101/Qt5.12.11_x64_static_vs2019_16101.zip' - QT_DOWNLOAD_HASH: 'cf1b58107fadbf0d9a957d14dab16cde6b6eb6936a1908472da1f967dda34a3a' - QT_LOCAL_PATH: 'C:\Qt5.12.11_x64_static_vs2019_16101' - VCPKG_TAG: '75522bb1f2e7d863078bcd06322348f053a9e33f' -install: -# Disable zmq test for now since python zmq library on Windows would cause Access violation sometimes. -# - cmd: pip install zmq -# The powershell block below is to set up vcpkg to install the c++ dependencies. The pseudo code is: -# a. Checkout the vcpkg source (including port files) for the specific checkout and build the vcpkg binary, -# b. Append a setting to the vcpkg cmake config file to only do release builds of dependencies (skipping deubg builds saves ~5 mins). -# Note originally this block also installed the dependencies using 'vcpkg install'. Dependencies are now installed -# as part of the msbuild command using vcpkg mainfests. -- ps: | - cd c:\tools\vcpkg - $env:GIT_REDIRECT_STDERR = '2>&1' # git is writing non-errors to STDERR when doing git pull. Send to STDOUT instead. - git -c advice.detachedHead=false checkout $env:VCPKG_TAG - .\bootstrap-vcpkg.bat > $null - Add-Content "C:\tools\vcpkg\triplets\$env:PLATFORM-windows-static.cmake" "set(VCPKG_BUILD_TYPE release)" - cd "$env:APPVEYOR_BUILD_FOLDER" -before_build: -# Powershell block below is to download and extract the Qt static libraries. The pseudo code is: -# a. Download the zip file with the prebuilt Qt static libraries. -# b. Check that the downloaded file matches the expected hash. -# c. Extract the zip file to the specific destination path expected by the msbuild projects. -- ps: | - Write-Host "Downloading Qt binaries."; - Invoke-WebRequest -Uri $env:QT_DOWNLOAD_URL -Out qtdownload.zip; - Write-Host "Qt binaries successfully downloaded, checking hash against $env:QT_DOWNLOAD_HASH..."; - if((Get-FileHash qtdownload.zip).Hash -eq $env:QT_DOWNLOAD_HASH) { - Expand-Archive qtdownload.zip -DestinationPath $env:QT_LOCAL_PATH; - Write-Host "Qt binary download matched the expected hash."; - } - else { - Write-Host "ERROR: Qt binary download did not match the expected hash."; - Exit-AppveyorBuild; - } -- cmd: python build_msvc\msvc-autogen.py -build_script: -- cmd: msbuild /p:TrackFileAccess=false build_msvc\bitcoin.sln /m /v:q /nologo -after_build: -#- 7z a bitcoin-%APPVEYOR_BUILD_VERSION%.zip %APPVEYOR_BUILD_FOLDER%\build_msvc\%platform%\%configuration%\*.exe -test_script: -- cmd: src\test_bitcoin.exe -l test_suite -- cmd: src\bench_bitcoin.exe > NUL -- ps: python test\util\bitcoin-util-test.py -- cmd: python test\util\rpcauth-test.py -# Fee estimation test failing on appveyor with: WinError 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted. -# functional tests disabled for now. See -# https://github.com/bitcoin/bitcoin/pull/18626#issuecomment-613396202 -# https://github.com/bitcoin/bitcoin/issues/18623 -# - cmd: python test\functional\test_runner.py --ci --quiet --combinedlogslen=4000 --failfast --exclude feature_fee_estimation -artifacts: -#- path: bitcoin-%APPVEYOR_BUILD_VERSION%.zip -deploy: off diff --git a/.cirrus.yml b/.cirrus.yml index 4a7c4eaf55..392babc556 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,6 +1,4 @@ -### Global defaults - -env: +env: # Global defaults PACKAGE_MANAGER_INSTALL: "apt-get update && apt-get install -y" MAKEJOBS: "-j4" TEST_RUNNER_PORT_MIN: "14000" # Must be larger than 12321, which is used for the http cache. See https://cirrus-ci.org/guide/writing-tasks/#http-cache @@ -18,8 +16,12 @@ persistent_worker_template: &PERSISTENT_WORKER_TEMPLATE persistent_worker: {} # https://cirrus-ci.org/guide/persistent-workers/ # https://cirrus-ci.org/guide/tips-and-tricks/#sharing-configuration-between-tasks -base_template: &BASE_TEMPLATE +filter_template: &FILTER_TEMPLATE skip: $CIRRUS_REPO_FULL_NAME == "bitcoin-core/gui" && $CIRRUS_PR == "" # No need to run on the read-only mirror, unless it is a PR. https://cirrus-ci.org/guide/writing-tasks/#conditional-task-execution + stateful: false # https://cirrus-ci.org/guide/writing-tasks/#stateful-tasks + +base_template: &BASE_TEMPLATE + << : *FILTER_TEMPLATE merge_base_script: # Unconditionally install git (used in fingerprint_script) and set the # default git author name (used in verify-commits.py) @@ -29,7 +31,6 @@ base_template: &BASE_TEMPLATE - if [ "$CIRRUS_PR" = "" ]; then exit 0; fi - git fetch $CIRRUS_REPO_CLONE_URL $CIRRUS_BASE_BRANCH - git merge FETCH_HEAD # Merge base to detect silent merge conflicts - stateful: false # https://cirrus-ci.org/guide/writing-tasks/#stateful-tasks main_template: &MAIN_TEMPLATE timeout_in: 120m # https://cirrus-ci.org/faq/#instance-timed-out @@ -59,22 +60,6 @@ compute_credits_template: &CREDITS_TEMPLATE # Only use credits for pull requests to the main repo use_compute_credits: $CIRRUS_REPO_FULL_NAME == 'bitcoin/bitcoin' && $CIRRUS_PR != "" -#task: -# name: "Windows" -# windows_container: -# image: cirrusci/windowsservercore:2019 -# env: -# CIRRUS_SHELL: powershell -# PATH: 'C:\Python37;C:\Python37\Scripts;%PATH%' -# PYTHONUTF8: 1 -# QT_DOWNLOAD_URL: 'https://github.com/sipsorcery/qt_win_binary/releases/download/v1.6/Qt5.9.8_x64_static_vs2019.zip' -# QT_DOWNLOAD_HASH: '9a8c6eb20967873785057fdcd329a657c7f922b0af08c5fde105cc597dd37e21' -# QT_LOCAL_PATH: 'C:\Qt5.9.8_x64_static_vs2019' -# VCPKG_INSTALL_PATH: 'C:\tools\vcpkg\installed' -# VCPKG_COMMIT_ID: 'ed0df8ecc4ed7e755ea03e18aaf285fd9b4b4a74' -# install_script: -# - choco install python --version=3.7.7 -y - task: name: 'lint [bionic]' << : *BASE_TEMPLATE @@ -90,6 +75,80 @@ task: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV task: + name: "Win64 native [unit tests, no functional tests] [msvc]" + << : *FILTER_TEMPLATE + windows_container: + cpu: 4 + memory: 16G + image: cirrusci/windowsservercore:visualstudio2019 + timeout_in: 120m + env: + PATH: 'C:\jom;C:\Python39;C:\Python39\Scripts;C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin;%PATH%' + PYTHONUTF8: 1 + VCPKG_TAG: '75522bb1f2e7d863078bcd06322348f053a9e33f' + VCPKG_FEATURE_FLAGS: 'manifests' + QT_DOWNLOAD_URL: 'https://download.qt.io/official_releases/qt/5.12/5.12.11/single/qt-everywhere-src-5.12.11.zip' + QT_LOCAL_PATH: 'C:\qt-everywhere-src-5.12.11.zip' + QT_SOURCE_DIR: 'C:\qt-everywhere-src-5.12.11' + QTBASEDIR: 'C:\Qt5.12.11_x64_static_vs2019_160900' + x64_NATIVE_TOOLS: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"' + IgnoreWarnIntDirInTempDetected: 'true' + merge_script: + - git config --global user.email "ci@ci.ci" + - git config --global user.name "ci" + # Windows filesystem loses the executable bit, and all of the executable + # files are considered "modified" now. It will break the following `git merge` + # command. The next two commands make git ignore this issue. + - git config core.filemode false + - git reset --hard + - PowerShell -NoLogo -Command if ($env:CIRRUS_PR -ne $null) { git fetch $env:CIRRUS_REPO_CLONE_URL $env:CIRRUS_BASE_BRANCH; git merge FETCH_HEAD; } + msvc_qt_built_cache: + folder: "%QTBASEDIR%" + reupload_on_changes: false + fingerprint_script: + - echo %QT_DOWNLOAD_URL% + - msbuild -version + populate_script: + - curl -L -o C:\jom.zip http://download.qt.io/official_releases/jom/jom.zip + - mkdir C:\jom + - tar -xf C:\jom.zip -C C:\jom + - curl -L -o %QT_LOCAL_PATH% %QT_DOWNLOAD_URL% + - tar -xf %QT_LOCAL_PATH% -C C:\ + - '%x64_NATIVE_TOOLS%' + - cd %QT_SOURCE_DIR% + - mkdir build + - cd build + - ..\configure -release -silent -opensource -confirm-license -opengl desktop -no-shared -static -static-runtime -mp -qt-zlib -qt-pcre -qt-libpng -no-libjpeg -nomake examples -nomake tests -nomake tools -no-dbus -no-libudev -no-icu -no-gtk -no-opengles3 -no-angle -no-sql-sqlite -no-sql-odbc -no-sqlite -no-libudev -no-vulkan -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcanvas3d -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdeclarative -skip qtdoc -skip qtgamepad -skip qtgraphicaleffects -skip qtimageformats -skip qtlocation -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth -skip qtpurchasing -skip qtquickcontrols -skip qtquickcontrols2 -skip qtscript -skip qtscxml -skip qtsensors -skip qtserialbus -skip qtserialport -skip qtspeech -skip qtvirtualkeyboard -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtx11extras -skip qtxmlpatterns -no-openssl -no-feature-sql -no-feature-sqlmodel -prefix %QTBASEDIR% + - jom + - jom install + vcpkg_cache: + folder: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\archives' + install_python_script: + - choco install --yes --no-progress python3 --version=3.9.6 + - pip install zmq + - python -VV + install_vcpkg_script: + - cd .. + - git clone --quiet https://github.com/microsoft/vcpkg.git + - cd vcpkg + - git -c advice.detachedHead=false checkout %VCPKG_TAG% + - .\bootstrap-vcpkg -disableMetrics + - echo set(VCPKG_BUILD_TYPE release) >> triplets\x64-windows-static.cmake + - .\vcpkg integrate install + - .\vcpkg version + build_script: + - cd %CIRRUS_WORKING_DIR% + - python build_msvc\msvc-autogen.py + - msbuild build_msvc\bitcoin.sln -property:Configuration=Release -maxCpuCount -verbosity:minimal -noLogo + unit_tests_script: + - src\test_bitcoin.exe -l test_suite + - src\bench_bitcoin.exe > NUL + - python test\util\test_runner.py + - python test\util\rpcauth-test.py + functional_tests_script: + - python test\functional\test_runner.py --ci --quiet --combinedlogslen=4000 --jobs=4 --timeout-factor=8 rpc_help feature_config_args rpc_signer feature_presegwit_node_upgrade "tool_wallet.py --descriptors" --failfast # TODO enable '--extended' and remove cherry-picked test list + +task: name: 'ARM [unit tests, no functional tests] [buster]' << : *GLOBAL_TASK_TEMPLATE arm_container: diff --git a/.editorconfig b/.editorconfig index 4967e675f6..ae7e92d1c8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ trim_trailing_whitespace = true [*.{h,cpp,py,sh}] indent_size = 4 -# .cirrus.yml, .appveyor.yml, .fuzzbuzz.yml, etc. +# .cirrus.yml, .fuzzbuzz.yml, etc. [*.yml] indent_size = 2 diff --git a/Makefile.am b/Makefile.am index 79c294fd15..ce66331910 100644 --- a/Makefile.am +++ b/Makefile.am @@ -286,7 +286,7 @@ EXTRA_DIST += \ test/fuzz EXTRA_DIST += \ - test/util/bitcoin-util-test.py \ + test/util/test_runner.py \ test/util/data/bitcoin-util-test.json \ test/util/data/blanktxv1.hex \ test/util/data/blanktxv1.json \ diff --git a/build_msvc/README.md b/build_msvc/README.md index 88a05644a7..c3705f6b03 100644 --- a/build_msvc/README.md +++ b/build_msvc/README.md @@ -39,7 +39,7 @@ In order to build Bitcoin Core a static build of Qt is required. The runtime lib Some prebuilt x64 versions of Qt can be downloaded from [here](https://github.com/sipsorcery/qt_win_binary/releases). Please be aware these downloads are NOT officially sanctioned by Bitcoin Core and are provided for developer convenience only. They should NOT be used for builds that will be used in a production environment or with real funds. -To determine which Qt prebuilt version to download open the `.appveyor.yml` file and note the `QT_DOWNLOAD_URL`. When extracting the zip file the destination path must be set to `C:\`. This is due to the way that Qt includes, libraries and tools use internal paths. +To determine which Qt prebuilt version to download open the `.cirrus.yml` file and note the `QT_DOWNLOAD_URL`. When extracting the zip file the destination path must be set to `C:\`. This is due to the way that Qt includes, libraries and tools use internal paths. To build Bitcoin Core without Qt unload or disable the `bitcoin-qt`, `libbitcoin_qt` and `test_bitcoin-qt` projects. @@ -65,17 +65,6 @@ msbuild /m bitcoin.sln /p:Platform=x64 /p:Configuration=Release /t:build - Alternatively, open the `build_msvc/bitcoin.sln` file in Visual Studio 2019. -AppVeyor ---------------------- -The .appveyor.yml in the root directory is suitable to perform builds on [AppVeyor](https://www.appveyor.com/) Continuous Integration servers. The simplest way to perform an AppVeyor build is to fork Bitcoin Core and then configure a new AppVeyor Project pointing to the forked repository. - -For safety reasons the Bitcoin Core .appveyor.yml file has the artifact options disabled. The build will be performed but no executable files will be available. To enable artifacts on a forked repository uncomment the lines shown below: - -``` - #- 7z a bitcoin-%APPVEYOR_BUILD_VERSION%.zip %APPVEYOR_BUILD_FOLDER%\build_msvc\%platform%\%configuration%\*.exe - #- path: bitcoin-%APPVEYOR_BUILD_VERSION%.zip -``` - Security --------------------- [Base address randomization](https://docs.microsoft.com/en-us/cpp/build/reference/dynamicbase-use-address-space-layout-randomization?view=msvc-160) is used to make Bitcoin Core more secure. When building Bitcoin using the `build_msvc` process base address randomization can be disabled by editing `common.init.vcproj` to change `RandomizedBaseAddress` from `true` to `false` and then rebuilding the project. diff --git a/build_msvc/bitcoind/bitcoind.vcxproj b/build_msvc/bitcoind/bitcoind.vcxproj index c2c32af838..d56c359fe0 100644 --- a/build_msvc/bitcoind/bitcoind.vcxproj +++ b/build_msvc/bitcoind/bitcoind.vcxproj @@ -58,6 +58,8 @@ <ReplaceInFile FilePath="$(ConfigIniOut)" Replace="@PACKAGE_NAME@" By="Bitcoin Core"></ReplaceInFile> <ReplaceInFile FilePath="$(ConfigIniOut)" + Replace="@PACKAGE_BUGREPORT@" By="https://github.com/bitcoin/bitcoin/issues"></ReplaceInFile> + <ReplaceInFile FilePath="$(ConfigIniOut)" Replace="@abs_top_srcdir@" By="..\.." ToFullPath="true"></ReplaceInFile> <ReplaceInFile FilePath="$(ConfigIniOut)" Replace="@abs_top_builddir@" By="..\.." ToFullPath="true"></ReplaceInFile> @@ -72,11 +74,15 @@ <ReplaceInFile FilePath="$(ConfigIniOut)" Replace="@BUILD_BITCOIN_CLI_TRUE@" By=""></ReplaceInFile> <ReplaceInFile FilePath="$(ConfigIniOut)" + Replace="@BUILD_BITCOIN_WALLET_TRUE@" By=""></ReplaceInFile> + <ReplaceInFile FilePath="$(ConfigIniOut)" Replace="@BUILD_BITCOIND_TRUE@" By=""></ReplaceInFile> <ReplaceInFile FilePath="$(ConfigIniOut)" Replace="@ENABLE_FUZZ_TRUE@" By=""></ReplaceInFile> <ReplaceInFile FilePath="$(ConfigIniOut)" Replace="@ENABLE_ZMQ_TRUE@" By=""></ReplaceInFile> + <ReplaceInFile FilePath="$(ConfigIniOut)" + Replace="@ENABLE_EXTERNAL_SIGNER_TRUE@" By=""></ReplaceInFile> </Target> <Import Project="..\common.vcxproj" /> </Project> diff --git a/build_msvc/common.qt.init.vcxproj b/build_msvc/common.qt.init.vcxproj index ce66a7ab34..df2fd2fb49 100644 --- a/build_msvc/common.qt.init.vcxproj +++ b/build_msvc/common.qt.init.vcxproj @@ -2,7 +2,7 @@ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Label="QtGlobals"> - <QtBaseDir>C:\Qt5.12.11_x64_static_vs2019_16101</QtBaseDir> + <QtBaseDir>C:\Qt5.12.11_x64_static_vs2019_160900</QtBaseDir> <QtPluginsLibraryDir>$(QtBaseDir)\plugins</QtPluginsLibraryDir> <QtLibraryDir>$(QtBaseDir)\lib</QtLibraryDir> <QtIncludeDir>$(QtBaseDir)\include</QtIncludeDir> diff --git a/build_msvc/msvc-autogen.py b/build_msvc/msvc-autogen.py index d99b17d381..a1ed935996 100755 --- a/build_msvc/msvc-autogen.py +++ b/build_msvc/msvc-autogen.py @@ -9,7 +9,7 @@ import argparse from shutil import copyfile SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')) -DEFAULT_PLATFORM_TOOLSET = R'v141' +DEFAULT_PLATFORM_TOOLSET = R'v142' libs = [ 'libbitcoin_cli', diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh index 2c63a9efac..5587618f2d 100755 --- a/ci/lint/04_install.sh +++ b/ci/lint/04_install.sh @@ -13,7 +13,6 @@ update-alternatives --install /usr/bin/clang-format-diff clang-format-diff $(whi ${CI_RETRY_EXE} pip3 install codespell==2.0.0 ${CI_RETRY_EXE} pip3 install flake8==3.8.3 -${CI_RETRY_EXE} pip3 install yq ${CI_RETRY_EXE} pip3 install mypy==0.781 ${CI_RETRY_EXE} pip3 install vulture==2.3 diff --git a/ci/test/00_setup_env_native_fuzz.sh b/ci/test/00_setup_env_native_fuzz.sh index 58388fa928..b8ac691346 100755 --- a/ci/test/00_setup_env_native_fuzz.sh +++ b/ci/test/00_setup_env_native_fuzz.sh @@ -14,5 +14,5 @@ export RUN_UNIT_TESTS=false export RUN_FUNCTIONAL_TESTS=false export RUN_FUZZ_TESTS=true export GOAL="install" -export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,address,undefined,integer CC=clang CXX=clang++" +export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,address,undefined,integer CC='clang -ftrivial-auto-var-init=pattern' CXX='clang++ -ftrivial-auto-var-init=pattern'" export CCACHE_SIZE=200M diff --git a/configure.ac b/configure.ac index 6c867986c9..e50d53f6dd 100644 --- a/configure.ac +++ b/configure.ac @@ -705,6 +705,33 @@ case $host in if $BREW list --versions qt5 >/dev/null; then export PKG_CONFIG_PATH="$($BREW --prefix qt5 2>/dev/null)/lib/pkgconfig:$PKG_CONFIG_PATH" fi + + case $host in + *aarch64*) + dnl The preferred Homebrew prefix for Apple Silicon is /opt/homebrew. + dnl Therefore, as we do not use pkg-config to detect miniupnpc and libnatpmp + dnl packages, we should set the CPPFLAGS and LDFLAGS variables for them + dnl explicitly. + if test "x$use_upnp" != xno && $BREW list --versions miniupnpc >/dev/null; then + miniupnpc_prefix=$($BREW --prefix miniupnpc 2>/dev/null) + if test "x$suppress_external_warnings" != xno; then + CPPFLAGS="$CPPFLAGS -isystem $miniupnpc_prefix/include" + else + CPPFLAGS="$CPPFLAGS -I$miniupnpc_prefix/include" + fi + LDFLAGS="$LDFLAGS -L$miniupnpc_prefix/lib" + fi + if test "x$use_natpmp" != xno && $BREW list --versions libnatpmp >/dev/null; then + libnatpmp_prefix=$($BREW --prefix libnatpmp 2>/dev/null) + if test "x$suppress_external_warnings" != xno; then + CPPFLAGS="$CPPFLAGS -isystem $libnatpmp_prefix/include" + else + CPPFLAGS="$CPPFLAGS -I$libnatpmp_prefix/include" + fi + LDFLAGS="$LDFLAGS -L$libnatpmp_prefix/lib" + fi + ;; + esac fi else case $build_os in @@ -1874,7 +1901,7 @@ AC_CONFIG_LINKS([contrib/devtools/test-symbol-check.py:contrib/devtools/test-sym AC_CONFIG_LINKS([contrib/filter-lcov.py:contrib/filter-lcov.py]) AC_CONFIG_LINKS([test/functional/test_runner.py:test/functional/test_runner.py]) AC_CONFIG_LINKS([test/fuzz/test_runner.py:test/fuzz/test_runner.py]) -AC_CONFIG_LINKS([test/util/bitcoin-util-test.py:test/util/bitcoin-util-test.py]) +AC_CONFIG_LINKS([test/util/test_runner.py:test/util/test_runner.py]) AC_CONFIG_LINKS([test/util/rpcauth-test.py:test/util/rpcauth-test.py]) dnl boost's m4 checks do something really nasty: they export these vars. As a diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 583c50a763..3e13adeec0 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -89,6 +89,10 @@ code. - Class member variables have a `m_` prefix. - Global variables have a `g_` prefix. - Constant names are all uppercase, and use `_` to separate words. + - Enumerator constants may be `snake_case`, `PascalCase` or `ALL_CAPS`. + This is a more tolerant policy than the [C++ Core + Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Renum-caps), + which recommend using `snake_case`. Please use what seems appropriate. - Class names, function names, and method names are UpperCamelCase (PascalCase). Do not prefix class names with `C`. - Test suite naming convention: The Boost test suite in file @@ -669,19 +673,19 @@ Foo(vec); ```cpp enum class Tabs { - INFO, - CONSOLE, - GRAPH, - PEERS + info, + console, + network_graph, + peers }; int GetInt(Tabs tab) { switch (tab) { - case Tabs::INFO: return 0; - case Tabs::CONSOLE: return 1; - case Tabs::GRAPH: return 2; - case Tabs::PEERS: return 3; + case Tabs::info: return 0; + case Tabs::console: return 1; + case Tabs::network_graph: return 2; + case Tabs::peers: return 3; } // no default case, so the compiler can warn about missing cases assert(false); } diff --git a/doc/fuzzing.md b/doc/fuzzing.md index 6605749557..ee9c65d4d4 100644 --- a/doc/fuzzing.md +++ b/doc/fuzzing.md @@ -16,6 +16,9 @@ $ FUZZ=process_message src/test/fuzz/fuzz # abort fuzzing using ctrl-c ``` +There is also a runner script to execute all fuzz targets. Refer to +`./test/fuzz/test_runner.py --help` for more details. + ## Fuzzing harnesses and output [`process_message`](https://github.com/bitcoin/bitcoin/blob/master/src/test/fuzz/process_message.cpp) is a fuzzing harness for the [`ProcessMessage(...)` function (`net_processing`)](https://github.com/bitcoin/bitcoin/blob/master/src/net_processing.cpp). The available fuzzing harnesses are found in [`src/test/fuzz/`](https://github.com/bitcoin/bitcoin/tree/master/src/test/fuzz). diff --git a/src/.clang-tidy b/src/.clang-tidy new file mode 100644 index 0000000000..27616ad072 --- /dev/null +++ b/src/.clang-tidy @@ -0,0 +1,2 @@ +Checks: '-*,bugprone-argument-comment' +WarningsAsErrors: bugprone-argument-comment diff --git a/src/Makefile.am b/src/Makefile.am index a8d6591e98..6f8245de8a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -548,6 +548,7 @@ libbitcoin_common_a_SOURCES = \ key.cpp \ key_io.cpp \ merkleblock.cpp \ + net_types.cpp \ netaddress.cpp \ netbase.cpp \ net_permissions.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 6af5ead443..a85a359960 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -337,8 +337,8 @@ bitcoin_test_clean : FORCE check-local: $(BITCOIN_TESTS:.cpp=.cpp.test) if BUILD_BITCOIN_TX - @echo "Running test/util/bitcoin-util-test.py..." - $(PYTHON) $(top_builddir)/test/util/bitcoin-util-test.py + @echo "Running test/util/test_runner.py..." + $(PYTHON) $(top_builddir)/test/util/test_runner.py endif @echo "Running test/util/rpcauth-test.py..." $(PYTHON) $(top_builddir)/test/util/rpcauth-test.py diff --git a/src/addrdb.cpp b/src/addrdb.cpp index a5383be7cf..856f318961 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -19,64 +19,7 @@ #include <util/settings.h> #include <util/system.h> -CBanEntry::CBanEntry(const UniValue& json) - : nVersion(json["version"].get_int()), nCreateTime(json["ban_created"].get_int64()), - nBanUntil(json["banned_until"].get_int64()) -{ -} - -UniValue CBanEntry::ToJson() const -{ - UniValue json(UniValue::VOBJ); - json.pushKV("version", nVersion); - json.pushKV("ban_created", nCreateTime); - json.pushKV("banned_until", nBanUntil); - return json; -} - namespace { - -static const char* BANMAN_JSON_ADDR_KEY = "address"; - -/** - * Convert a `banmap_t` object to a JSON array. - * @param[in] bans Bans list to convert. - * @return a JSON array, similar to the one returned by the `listbanned` RPC. Suitable for - * passing to `BanMapFromJson()`. - */ -UniValue BanMapToJson(const banmap_t& bans) -{ - UniValue bans_json(UniValue::VARR); - for (const auto& it : bans) { - const auto& address = it.first; - const auto& ban_entry = it.second; - UniValue j = ban_entry.ToJson(); - j.pushKV(BANMAN_JSON_ADDR_KEY, address.ToString()); - bans_json.push_back(j); - } - return bans_json; -} - -/** - * Convert a JSON array to a `banmap_t` object. - * @param[in] bans_json JSON to convert, must be as returned by `BanMapToJson()`. - * @param[out] bans Bans list to create from the JSON. - * @throws std::runtime_error if the JSON does not have the expected fields or they contain - * unparsable values. - */ -void BanMapFromJson(const UniValue& bans_json, banmap_t& bans) -{ - for (const auto& ban_entry_json : bans_json.getValues()) { - CSubNet subnet; - const auto& subnet_str = ban_entry_json[BANMAN_JSON_ADDR_KEY].get_str(); - if (!LookupSubNet(subnet_str, subnet)) { - throw std::runtime_error( - strprintf("Cannot parse banned address or subnet: %s", subnet_str)); - } - bans.insert_or_assign(subnet, CBanEntry{ban_entry_json}); - } -} - template <typename Stream, typename Data> bool SerializeDB(Stream& stream, const Data& data) { @@ -227,22 +170,19 @@ bool CBanDB::Read(banmap_t& banSet) return true; } -CAddrDB::CAddrDB() -{ - pathAddr = gArgs.GetDataDirNet() / "peers.dat"; -} - -bool CAddrDB::Write(const CAddrMan& addr) +bool DumpPeerAddresses(const ArgsManager& args, const CAddrMan& addr) { + const auto pathAddr = args.GetDataDirNet() / "peers.dat"; return SerializeFileDB("peers", pathAddr, addr, CLIENT_VERSION); } -bool CAddrDB::Read(CAddrMan& addr) +bool ReadPeerAddresses(const ArgsManager& args, CAddrMan& addr) { + const auto pathAddr = args.GetDataDirNet() / "peers.dat"; return DeserializeFileDB(pathAddr, addr, CLIENT_VERSION); } -bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers) +bool ReadFromStream(CAddrMan& addr, CDataStream& ssPeers) { return DeserializeDB(ssPeers, addr, false); } diff --git a/src/addrdb.h b/src/addrdb.h index 26400ee0b6..c31c126ee3 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -12,48 +12,15 @@ #include <vector> -class CAddress; +class ArgsManager; class CAddrMan; +class CAddress; class CDataStream; -class CBanEntry -{ -public: - static constexpr int CURRENT_VERSION{1}; - int nVersion{CBanEntry::CURRENT_VERSION}; - int64_t nCreateTime{0}; - int64_t nBanUntil{0}; - - CBanEntry() {} - - explicit CBanEntry(int64_t nCreateTimeIn) - : nCreateTime{nCreateTimeIn} {} - - /** - * Create a ban entry from JSON. - * @param[in] json A JSON representation of a ban entry, as created by `ToJson()`. - * @throw std::runtime_error if the JSON does not have the expected fields. - */ - explicit CBanEntry(const UniValue& json); - - /** - * Generate a JSON representation of this ban entry. - * @return JSON suitable for passing to the `CBanEntry(const UniValue&)` constructor. - */ - UniValue ToJson() const; -}; - -/** Access to the (IP) address database (peers.dat) */ -class CAddrDB -{ -private: - fs::path pathAddr; -public: - CAddrDB(); - bool Write(const CAddrMan& addr); - bool Read(CAddrMan& addr); - static bool Read(CAddrMan& addr, CDataStream& ssPeers); -}; +bool DumpPeerAddresses(const ArgsManager& args, const CAddrMan& addr); +bool ReadPeerAddresses(const ArgsManager& args, CAddrMan& addr); +/** Only used by tests. */ +bool ReadFromStream(CAddrMan& addr, CDataStream& ssPeers); /** Access to the banlist database (banlist.json) */ class CBanDB diff --git a/src/addrman.cpp b/src/addrman.cpp index 48e79c64ed..174b3a654c 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -5,10 +5,12 @@ #include <addrman.h> +#include <clientversion.h> #include <hash.h> #include <logging.h> #include <netaddress.h> #include <serialize.h> +#include <streams.h> #include <cmath> #include <optional> @@ -98,10 +100,11 @@ double CAddrInfo::GetChance(int64_t nNow) const return fChance; } -CAddrMan::CAddrMan(bool deterministic, int32_t consistency_check_ratio) +CAddrMan::CAddrMan(std::vector<bool> asmap, bool deterministic, int32_t consistency_check_ratio) : insecure_rand{deterministic} , nKey{deterministic ? uint256{1} : insecure_rand.rand256()} , m_consistency_check_ratio{consistency_check_ratio} + , m_asmap{std::move(asmap)} { for (auto& bucket : vvNew) { for (auto& entry : bucket) { @@ -242,9 +245,9 @@ void CAddrMan::Unserialize(Stream& s_) const uint8_t lowest_compatible = compat - INCOMPATIBILITY_BASE; if (lowest_compatible > FILE_FORMAT) { throw std::ios_base::failure(strprintf( - "Unsupported format of addrman database: %u. It is compatible with formats >=%u, " - "but the maximum supported by this version of %s is %u.", - format, lowest_compatible, PACKAGE_NAME, static_cast<uint8_t>(FILE_FORMAT))); + "Unsupported format of addrman database: %u. It is compatible with formats >=%u, " + "but the maximum supported by this version of %s is %u.", + uint8_t{format}, uint8_t{lowest_compatible}, PACKAGE_NAME, uint8_t{FILE_FORMAT})); } s >> nKey; diff --git a/src/addrman.h b/src/addrman.h index 2548b891ba..14cc113f94 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -6,23 +6,16 @@ #ifndef BITCOIN_ADDRMAN_H #define BITCOIN_ADDRMAN_H -#include <clientversion.h> -#include <config/bitcoin-config.h> #include <fs.h> -#include <hash.h> +#include <logging.h> #include <netaddress.h> #include <protocol.h> -#include <random.h> -#include <streams.h> #include <sync.h> #include <timedata.h> -#include <tinyformat.h> -#include <util/system.h> -#include <iostream> +#include <cstdint> #include <optional> #include <set> -#include <stdint.h> #include <unordered_map> #include <vector> @@ -149,22 +142,6 @@ static constexpr int ADDRMAN_BUCKET_SIZE{1 << ADDRMAN_BUCKET_SIZE_LOG2}; class CAddrMan { public: - // Compressed IP->ASN mapping, loaded from a file when a node starts. - // Should be always empty if no file was provided. - // This mapping is then used for bucketing nodes in Addrman. - // - // If asmap is provided, nodes will be bucketed by - // AS they belong to, in order to make impossible for a node - // to connect to several nodes hosted in a single AS. - // This is done in response to Erebus attack, but also to generally - // diversify the connections every node creates, - // especially useful when a large fraction of nodes - // operate under a couple of cloud providers. - // - // If a new asmap was provided, the existing records - // would be re-bucketed accordingly. - std::vector<bool> m_asmap; - // Read asmap from provided binary file static std::vector<bool> DecodeAsmap(fs::path path); @@ -174,7 +151,7 @@ public: template <typename Stream> void Unserialize(Stream& s_) EXCLUSIVE_LOCKS_REQUIRED(!cs); - explicit CAddrMan(bool deterministic, int32_t consistency_check_ratio); + explicit CAddrMan(std::vector<bool> asmap, bool deterministic, int32_t consistency_check_ratio); ~CAddrMan() { @@ -296,6 +273,8 @@ public: Check(); } + const std::vector<bool>& GetAsmap() const { return m_asmap; } + private: //! A mutex to protect the inner data structures. mutable Mutex cs; @@ -363,6 +342,22 @@ private: /** Perform consistency checks every m_consistency_check_ratio operations (if non-zero). */ const int32_t m_consistency_check_ratio; + // Compressed IP->ASN mapping, loaded from a file when a node starts. + // Should be always empty if no file was provided. + // This mapping is then used for bucketing nodes in Addrman. + // + // If asmap is provided, nodes will be bucketed by + // AS they belong to, in order to make impossible for a node + // to connect to several nodes hosted in a single AS. + // This is done in response to Erebus attack, but also to generally + // diversify the connections every node creates, + // especially useful when a large fraction of nodes + // operate under a couple of cloud providers. + // + // If a new asmap was provided, the existing records + // would be re-bucketed accordingly. + const std::vector<bool> m_asmap; + //! Find an entry. CAddrInfo* Find(const CNetAddr& addr, int *pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp index d69a651811..8fbb68c04c 100644 --- a/src/bench/addrman.cpp +++ b/src/bench/addrman.cpp @@ -73,14 +73,14 @@ static void AddrManAdd(benchmark::Bench& bench) CreateAddresses(); bench.run([&] { - CAddrMan addrman{/* deterministic */ false, /* consistency_check_ratio */ 0}; + CAddrMan addrman{/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0}; AddAddressesToAddrMan(addrman); }); } static void AddrManSelect(benchmark::Bench& bench) { - CAddrMan addrman(/* deterministic */ false, /* consistency_check_ratio */ 0); + CAddrMan addrman(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0); FillAddrMan(addrman); @@ -92,7 +92,7 @@ static void AddrManSelect(benchmark::Bench& bench) static void AddrManGetAddr(benchmark::Bench& bench) { - CAddrMan addrman(/* deterministic */ false, /* consistency_check_ratio */ 0); + CAddrMan addrman(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0); FillAddrMan(addrman); @@ -114,7 +114,7 @@ static void AddrManGood(benchmark::Bench& bench) std::vector<std::unique_ptr<CAddrMan>> addrmans(addrman_count); for (size_t i{0}; i < addrman_count; ++i) { - addrmans[i] = std::make_unique<CAddrMan>(/* deterministic */ false, /* consistency_check_ratio */ 0); + addrmans[i] = std::make_unique<CAddrMan>(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0); FillAddrMan(*addrmans[i]); } diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 5beb833b48..934b574f8b 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -6,6 +6,7 @@ #include <interfaces/chain.h> #include <node/context.h> #include <wallet/coinselection.h> +#include <wallet/spend.h> #include <wallet/wallet.h> #include <set> @@ -17,7 +18,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<st tx.nLockTime = nextLockTime++; // so all transactions get different hashes tx.vout.resize(1); tx.vout[0].nValue = nValue; - wtxs.push_back(std::make_unique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx)))); + wtxs.push_back(std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx)))); } // Simple benchmark for wallet coin selection. Note that it maybe be necessary @@ -45,18 +46,18 @@ static void CoinSelection(benchmark::Bench& bench) // Create coins std::vector<COutput> coins; for (const auto& wtx : wtxs) { - coins.emplace_back(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */); + coins.emplace_back(wallet, *wtx, 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */); } const CoinEligibilityFilter filter_standard(1, 6, 0); const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34, /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), - /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); + /* tx_noinputs_size= */ 0, /* avoid_partial= */ false); bench.run([&] { std::set<CInputCoin> setCoinsRet; CAmount nValueRet; - bool success = wallet.AttemptSelection(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params); + bool success = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params); assert(success); assert(nValueRet == 1003 * COIN); assert(setCoinsRet.size() == 2); @@ -75,9 +76,9 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup> CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; - std::unique_ptr<CWalletTx> wtx = std::make_unique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx))); + std::unique_ptr<CWalletTx> wtx = std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx))); set.emplace_back(); - set.back().Insert(COutput(wtx.get(), nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0, false); + set.back().Insert(COutput(testWallet, *wtx, nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0, false); wtxn.emplace_back(std::move(wtx)); } // Copied from src/wallet/test/coinselector_tests.cpp diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index 362b7c1e15..a205d8b6e7 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -9,6 +9,7 @@ #include <test/util/setup_common.h> #include <test/util/wallet.h> #include <validationinterface.h> +#include <wallet/receive.h> #include <wallet/wallet.h> #include <optional> @@ -35,11 +36,11 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b } SyncWithValidationInterfaceQueue(); - auto bal = wallet.GetBalance(); // Cache + auto bal = GetBalance(wallet); // Cache bench.run([&] { if (set_dirty) wallet.MarkDirty(); - bal = wallet.GetBalance(); + bal = GetBalance(wallet); if (add_mine) assert(bal.m_mine_trusted > 0); if (add_watchonly) assert(bal.m_watchonly_trusted > 0); }); diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 34785bf6a4..297f3066ff 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -386,7 +386,9 @@ private: bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; } bool m_is_asmap_on{false}; size_t m_max_addr_length{0}; - size_t m_max_age_length{3}; + size_t m_max_addr_processed_length{5}; + size_t m_max_addr_rate_limited_length{6}; + size_t m_max_age_length{5}; size_t m_max_id_length{2}; struct Peer { std::string addr; @@ -396,6 +398,8 @@ private: std::string age; double min_ping; double ping; + int64_t addr_processed; + int64_t addr_rate_limited; int64_t last_blck; int64_t last_recv; int64_t last_send; @@ -403,6 +407,7 @@ private: int id; int mapped_as; int version; + bool is_addr_relay_enabled; bool is_bip152_hb_from; bool is_bip152_hb_to; bool is_block_relay; @@ -483,6 +488,8 @@ public: const int peer_id{peer["id"].get_int()}; const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].get_int()}; const int version{peer["version"].get_int()}; + const int64_t addr_processed{peer["addr_processed"].isNull() ? 0 : peer["addr_processed"].get_int64()}; + const int64_t addr_rate_limited{peer["addr_rate_limited"].isNull() ? 0 : peer["addr_rate_limited"].get_int64()}; const int64_t conn_time{peer["conntime"].get_int64()}; const int64_t last_blck{peer["last_block"].get_int64()}; const int64_t last_recv{peer["lastrecv"].get_int64()}; @@ -493,10 +500,13 @@ public: const std::string addr{peer["addr"].get_str()}; const std::string age{conn_time == 0 ? "" : ToString((m_time_now - conn_time) / 60)}; const std::string sub_version{peer["subver"].get_str()}; + const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()}; const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()}; const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()}; - m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_bip152_hb_from, is_bip152_hb_to, is_block_relay, is_outbound}); + m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_block_relay, is_outbound}); m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length); + m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length); + m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length); m_max_age_length = std::max(age.length(), m_max_age_length); m_max_id_length = std::max(ToString(peer_id).length(), m_max_id_length); m_is_asmap_on |= (mapped_as != 0); @@ -504,39 +514,46 @@ public: } // Generate report header. - std::string result{strprintf("%s %s%s - %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].get_int(), networkinfo["subversion"].get_str())}; + std::string result{strprintf("%s client %s%s - server %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].get_int(), networkinfo["subversion"].get_str())}; // Report detailed peer connections list sorted by direction and minimum ping time. if (DetailsRequested() && !m_peers.empty()) { std::sort(m_peers.begin(), m_peers.end()); - result += strprintf("<-> type net mping ping send recv txn blk hb %*s ", m_max_age_length, "age"); + result += strprintf("<-> type net mping ping send recv txn blk hb %*s%*s%*s ", + m_max_addr_processed_length, "addrp", + m_max_addr_rate_limited_length, "addrl", + m_max_age_length, "age"); if (m_is_asmap_on) result += " asmap "; result += strprintf("%*s %-*s%s\n", m_max_id_length, "id", IsAddressSelected() ? m_max_addr_length : 0, IsAddressSelected() ? "address" : "", IsVersionSelected() ? "version" : ""); for (const Peer& peer : m_peers) { std::string version{ToString(peer.version) + peer.sub_version}; result += strprintf( - "%3s %6s %5s%7s%7s%5s%5s%5s%5s %2s %*s%*i %*s %-*s%s\n", + "%3s %6s %5s%7s%7s%5s%5s%5s%5s %2s %*s%*s%*s%*i %*s %-*s%s\n", peer.is_outbound ? "out" : "in", ConnectionTypeForNetinfo(peer.conn_type), peer.network, PingTimeToString(peer.min_ping), PingTimeToString(peer.ping), - peer.last_send == 0 ? "" : ToString(m_time_now - peer.last_send), - peer.last_recv == 0 ? "" : ToString(m_time_now - peer.last_recv), - peer.last_trxn == 0 ? "" : ToString((m_time_now - peer.last_trxn) / 60), - peer.last_blck == 0 ? "" : ToString((m_time_now - peer.last_blck) / 60), + peer.last_send ? ToString(m_time_now - peer.last_send) : "", + peer.last_recv ? ToString(m_time_now - peer.last_recv) : "", + peer.last_trxn ? ToString((m_time_now - peer.last_trxn) / 60) : peer.is_block_relay ? "*" : "", + peer.last_blck ? ToString((m_time_now - peer.last_blck) / 60) : "", strprintf("%s%s", peer.is_bip152_hb_to ? "." : " ", peer.is_bip152_hb_from ? "*" : " "), + m_max_addr_processed_length, // variable spacing + peer.addr_processed ? ToString(peer.addr_processed) : peer.is_addr_relay_enabled ? "" : ".", + m_max_addr_rate_limited_length, // variable spacing + peer.addr_rate_limited ? ToString(peer.addr_rate_limited) : "", m_max_age_length, // variable spacing peer.age, m_is_asmap_on ? 7 : 0, // variable spacing - m_is_asmap_on && peer.mapped_as != 0 ? ToString(peer.mapped_as) : "", + m_is_asmap_on && peer.mapped_as ? ToString(peer.mapped_as) : "", m_max_id_length, // variable spacing peer.id, IsAddressSelected() ? m_max_addr_length : 0, // variable spacing IsAddressSelected() ? peer.addr : "", IsVersionSelected() && version != "0" ? version : ""); } - result += strprintf(" ms ms sec sec min min %*s\n\n", m_max_age_length, "min"); + result += strprintf(" ms ms sec sec min min %*s\n\n", m_max_age_length, "min"); } // Report peer connection totals by type. @@ -610,10 +627,14 @@ public: " send Time since last message sent to the peer, in seconds\n" " recv Time since last message received from the peer, in seconds\n" " txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n" + " \"*\" - the peer requested we not relay transactions to it (relaytxes is false)\n" " blk Time since last novel block passing initial validity checks received from the peer, in minutes\n" " hb High-bandwidth BIP152 compact block relay\n" " \".\" (to) - we selected the peer as a high-bandwidth peer\n" " \"*\" (from) - the peer selected us as a high-bandwidth peer\n" + " addrp Total number of addresses processed, excluding those dropped due to rate limiting\n" + " \".\" - we do not relay addresses to this peer (addr_relay_enabled is false)\n" + " addrl Total number of addresses dropped due to rate limiting\n" " age Duration of connection to the peer, in minutes\n" " asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n" " peer selection (only displayed if the -asmap config option is set)\n" diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index cd2cdf01f2..58c51bd8e0 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -772,9 +772,7 @@ static std::string readStdin() if (ferror(stdin)) throw std::runtime_error("error reading stdin"); - boost::algorithm::trim_right(ret); - - return ret; + return TrimString(ret); } static int CommandLineRawTx(int argc, char* argv[]) diff --git a/src/httprpc.cpp b/src/httprpc.cpp index e11e4acb5c..9ae592be79 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -10,6 +10,7 @@ #include <rpc/protocol.h> #include <rpc/server.h> #include <util/strencodings.h> +#include <util/string.h> #include <util/system.h> #include <util/translation.h> #include <walletinitinterface.h> @@ -22,7 +23,7 @@ #include <set> #include <string> -#include <boost/algorithm/string.hpp> // boost::trim +#include <boost/algorithm/string.hpp> /** WWW-Authenticate to present with 401 Unauthorized response */ static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\""; @@ -130,8 +131,7 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna return false; if (strAuth.substr(0, 6) != "Basic ") return false; - std::string strUserPass64 = strAuth.substr(6); - boost::trim(strUserPass64); + std::string strUserPass64 = TrimString(strAuth.substr(6)); std::string strUserPass = DecodeBase64(strUserPass64); if (strUserPass.find(':') != std::string::npos) diff --git a/src/init.cpp b/src/init.cpp index d4a3c891b1..2869cf4daf 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1171,23 +1171,51 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) fDiscover = args.GetBoolArg("-discover", true); const bool ignores_incoming_txs{args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)}; - assert(!node.addrman); - auto check_addrman = std::clamp<int32_t>(args.GetArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000); - node.addrman = std::make_unique<CAddrMan>(/* deterministic */ false, /* consistency_check_ratio */ check_addrman); { + // Initialize addrman + assert(!node.addrman); + + // Read asmap file if configured + std::vector<bool> asmap; + if (args.IsArgSet("-asmap")) { + fs::path asmap_path = fs::path(args.GetArg("-asmap", "")); + if (asmap_path.empty()) { + asmap_path = DEFAULT_ASMAP_FILENAME; + } + if (!asmap_path.is_absolute()) { + asmap_path = gArgs.GetDataDirNet() / asmap_path; + } + if (!fs::exists(asmap_path)) { + InitError(strprintf(_("Could not find asmap file %s"), asmap_path)); + return false; + } + asmap = CAddrMan::DecodeAsmap(asmap_path); + if (asmap.size() == 0) { + InitError(strprintf(_("Could not parse asmap file %s"), asmap_path)); + return false; + } + const uint256 asmap_version = SerializeHash(asmap); + LogPrintf("Using asmap version %s for IP bucketing\n", asmap_version.ToString()); + } else { + LogPrintf("Using /16 prefix for IP bucketing\n"); + } + + auto check_addrman = std::clamp<int32_t>(args.GetArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000); + node.addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman); + // Load addresses from peers.dat uiInterface.InitMessage(_("Loading P2P addresses…").translated); int64_t nStart = GetTimeMillis(); - CAddrDB adb; - if (adb.Read(*node.addrman)) { + if (ReadPeerAddresses(args, *node.addrman)) { LogPrintf("Loaded %i addresses from peers.dat %dms\n", node.addrman->size(), GetTimeMillis() - nStart); } else { // Addrman can be in an inconsistent state after failure, reset it - node.addrman = std::make_unique<CAddrMan>(/* deterministic */ false, /* consistency_check_ratio */ check_addrman); + node.addrman = std::make_unique<CAddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman); LogPrintf("Recreating peers.dat\n"); - adb.Write(*node.addrman); + DumpPeerAddresses(args, *node.addrman); } } + assert(!node.banman); node.banman = std::make_unique<BanMan>(gArgs.GetDataDirNet() / "banlist", &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!node.connman); @@ -1292,31 +1320,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return InitError(ResolveErrMsg("externalip", strAddr)); } - // Read asmap file if configured - if (args.IsArgSet("-asmap")) { - fs::path asmap_path = fs::path(args.GetArg("-asmap", "")); - if (asmap_path.empty()) { - asmap_path = DEFAULT_ASMAP_FILENAME; - } - if (!asmap_path.is_absolute()) { - asmap_path = gArgs.GetDataDirNet() / asmap_path; - } - if (!fs::exists(asmap_path)) { - InitError(strprintf(_("Could not find asmap file %s"), asmap_path)); - return false; - } - std::vector<bool> asmap = CAddrMan::DecodeAsmap(asmap_path); - if (asmap.size() == 0) { - InitError(strprintf(_("Could not parse asmap file %s"), asmap_path)); - return false; - } - const uint256 asmap_version = SerializeHash(asmap); - node.connman->SetAsmap(std::move(asmap)); - LogPrintf("Using asmap version %s for IP bucketing\n", asmap_version.ToString()); - } else { - LogPrintf("Using /16 prefix for IP bucketing\n"); - } - #if ENABLE_ZMQ g_zmq_notification_interface = CZMQNotificationInterface::Create(); diff --git a/src/logging.cpp b/src/logging.cpp index b456108b61..eb2c750296 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -159,6 +159,7 @@ const CLogCategoryDesc LogCategories[] = {BCLog::VALIDATION, "validation"}, {BCLog::I2P, "i2p"}, {BCLog::IPC, "ipc"}, + {BCLog::LOCK, "lock"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; diff --git a/src/logging.h b/src/logging.h index 38d73863e7..53a89d28bd 100644 --- a/src/logging.h +++ b/src/logging.h @@ -59,6 +59,7 @@ namespace BCLog { VALIDATION = (1 << 21), I2P = (1 << 22), IPC = (1 << 23), + LOCK = (1 << 24), ALL = ~(uint32_t)0, }; diff --git a/src/logging/timer.h b/src/logging/timer.h index 159920e397..647e3fa30e 100644 --- a/src/logging/timer.h +++ b/src/logging/timer.h @@ -58,12 +58,14 @@ public: return strprintf("%s: %s", m_prefix, msg); } - std::string units = ""; + if (std::is_same<TimeType, std::chrono::microseconds>::value) { + return strprintf("%s: %s (%iμs)", m_prefix, msg, end_time.count()); + } + + std::string units; float divisor = 1; - if (std::is_same<TimeType, std::chrono::microseconds>::value) { - units = "μs"; - } else if (std::is_same<TimeType, std::chrono::milliseconds>::value) { + if (std::is_same<TimeType, std::chrono::milliseconds>::value) { units = "ms"; divisor = 1000.; } else if (std::is_same<TimeType, std::chrono::seconds>::value) { @@ -93,6 +95,8 @@ private: } // namespace BCLog +#define LOG_TIME_MICROS_WITH_CATEGORY(end_msg, log_category) \ + BCLog::Timer<std::chrono::microseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, log_category) #define LOG_TIME_MILLIS_WITH_CATEGORY(end_msg, log_category) \ BCLog::Timer<std::chrono::milliseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, log_category) #define LOG_TIME_SECONDS(end_msg) \ diff --git a/src/net.cpp b/src/net.cpp index 9b1e17c587..c31b2d899e 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -9,6 +9,7 @@ #include <net.h> +#include <addrdb.h> #include <banman.h> #include <clientversion.h> #include <compat.h> @@ -24,6 +25,7 @@ #include <scheduler.h> #include <util/sock.h> #include <util/strencodings.h> +#include <util/system.h> #include <util/thread.h> #include <util/trace.h> #include <util/translation.h> @@ -552,14 +554,14 @@ Network CNode::ConnectedThroughNetwork() const #undef X #define X(name) stats.name = name -void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) +void CNode::CopyStats(CNodeStats& stats, const std::vector<bool>& asmap) { stats.nodeid = this->GetId(); X(nServices); X(addr); X(addrBind); stats.m_network = ConnectedThroughNetwork(); - stats.m_mapped_as = addr.GetMappedAS(m_asmap); + stats.m_mapped_as = addr.GetMappedAS(asmap); if (m_tx_relay != nullptr) { LOCK(m_tx_relay->cs_filter); stats.fRelayTxes = m_tx_relay->fRelayTxes; @@ -1747,8 +1749,7 @@ void CConnman::DumpAddresses() { int64_t nStart = GetTimeMillis(); - CAddrDB adb; - adb.Write(addrman); + DumpPeerAddresses(::gArgs, addrman); LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n", addrman.size(), GetTimeMillis() - nStart); @@ -1921,7 +1922,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) case ConnectionType::BLOCK_RELAY: case ConnectionType::ADDR_FETCH: case ConnectionType::FEELER: - setConnected.insert(pnode->addr.GetGroup(addrman.m_asmap)); + setConnected.insert(pnode->addr.GetGroup(addrman.GetAsmap())); } // no default case, so the compiler can warn about missing cases } } @@ -1995,7 +1996,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) m_anchors.pop_back(); if (!addr.IsValid() || IsLocal(addr) || !IsReachable(addr) || !HasAllDesirableServiceFlags(addr.nServices) || - setConnected.count(addr.GetGroup(addrman.m_asmap))) continue; + setConnected.count(addr.GetGroup(addrman.GetAsmap()))) continue; addrConnect = addr; LogPrint(BCLog::NET, "Trying to make an anchor connection to %s\n", addrConnect.ToString()); break; @@ -2035,7 +2036,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) } // Require outbound connections, other than feelers, to be to distinct network groups - if (!fFeeler && setConnected.count(addr.GetGroup(addrman.m_asmap))) { + if (!fFeeler && setConnected.count(addr.GetGroup(addrman.GetAsmap()))) { break; } @@ -2804,7 +2805,7 @@ void CConnman::GetNodeStats(std::vector<CNodeStats>& vstats) const vstats.reserve(vNodes.size()); for (CNode* pnode : vNodes) { vstats.emplace_back(); - pnode->copyStats(vstats.back(), addrman.m_asmap); + pnode->CopyStats(vstats.back(), addrman.GetAsmap()); } } @@ -3067,7 +3068,7 @@ CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const uint64_t CConnman::CalculateKeyedNetGroup(const CAddress& ad) const { - std::vector<unsigned char> vchNetGroup(ad.GetGroup(addrman.m_asmap)); + std::vector<unsigned char> vchNetGroup(ad.GetGroup(addrman.GetAsmap())); return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP).Write(vchNetGroup.data(), vchNetGroup.size()).Finalize(); } @@ -6,7 +6,6 @@ #ifndef BITCOIN_NET_H #define BITCOIN_NET_H -#include <addrdb.h> #include <addrman.h> #include <amount.h> #include <bloom.h> @@ -652,7 +651,7 @@ public: void CloseSocketDisconnect(); - void copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap); + void CopyStats(CNodeStats& stats, const std::vector<bool>& asmap); ServiceFlags GetLocalServices() const { @@ -943,8 +942,6 @@ public: */ std::chrono::microseconds PoissonNextSendInbound(std::chrono::microseconds now, std::chrono::seconds average_interval); - void SetAsmap(std::vector<bool> asmap) { addrman.m_asmap = std::move(asmap); } - /** Return true if we should disconnect the peer for failing an inactivity check. */ bool ShouldRunInactivityChecks(const CNode& node, std::optional<int64_t> now=std::nullopt) const; diff --git a/src/net_types.cpp b/src/net_types.cpp new file mode 100644 index 0000000000..c8f57fe6c6 --- /dev/null +++ b/src/net_types.cpp @@ -0,0 +1,65 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <net_types.h> + +#include <netaddress.h> +#include <netbase.h> +#include <univalue.h> + +CBanEntry::CBanEntry(const UniValue& json) + : nVersion(json["version"].get_int()), nCreateTime(json["ban_created"].get_int64()), + nBanUntil(json["banned_until"].get_int64()) +{ +} + +UniValue CBanEntry::ToJson() const +{ + UniValue json(UniValue::VOBJ); + json.pushKV("version", nVersion); + json.pushKV("ban_created", nCreateTime); + json.pushKV("banned_until", nBanUntil); + return json; +} + +static const char* BANMAN_JSON_ADDR_KEY = "address"; + +/** + * Convert a `banmap_t` object to a JSON array. + * @param[in] bans Bans list to convert. + * @return a JSON array, similar to the one returned by the `listbanned` RPC. Suitable for + * passing to `BanMapFromJson()`. + */ +UniValue BanMapToJson(const banmap_t& bans) +{ + UniValue bans_json(UniValue::VARR); + for (const auto& it : bans) { + const auto& address = it.first; + const auto& ban_entry = it.second; + UniValue j = ban_entry.ToJson(); + j.pushKV(BANMAN_JSON_ADDR_KEY, address.ToString()); + bans_json.push_back(j); + } + return bans_json; +} + +/** + * Convert a JSON array to a `banmap_t` object. + * @param[in] bans_json JSON to convert, must be as returned by `BanMapToJson()`. + * @param[out] bans Bans list to create from the JSON. + * @throws std::runtime_error if the JSON does not have the expected fields or they contain + * unparsable values. + */ +void BanMapFromJson(const UniValue& bans_json, banmap_t& bans) +{ + for (const auto& ban_entry_json : bans_json.getValues()) { + CSubNet subnet; + const auto& subnet_str = ban_entry_json[BANMAN_JSON_ADDR_KEY].get_str(); + if (!LookupSubNet(subnet_str, subnet)) { + throw std::runtime_error( + strprintf("Cannot parse banned address or subnet: %s", subnet_str)); + } + bans.insert_or_assign(subnet, CBanEntry{ban_entry_json}); + } +} diff --git a/src/net_types.h b/src/net_types.h index d55a8cde6c..ffdc24c772 100644 --- a/src/net_types.h +++ b/src/net_types.h @@ -5,11 +5,56 @@ #ifndef BITCOIN_NET_TYPES_H #define BITCOIN_NET_TYPES_H +#include <cstdint> #include <map> -class CBanEntry; class CSubNet; +class UniValue; + +class CBanEntry +{ +public: + static constexpr int CURRENT_VERSION{1}; + int nVersion{CBanEntry::CURRENT_VERSION}; + int64_t nCreateTime{0}; + int64_t nBanUntil{0}; + + CBanEntry() {} + + explicit CBanEntry(int64_t nCreateTimeIn) + : nCreateTime{nCreateTimeIn} {} + + /** + * Create a ban entry from JSON. + * @param[in] json A JSON representation of a ban entry, as created by `ToJson()`. + * @throw std::runtime_error if the JSON does not have the expected fields. + */ + explicit CBanEntry(const UniValue& json); + + /** + * Generate a JSON representation of this ban entry. + * @return JSON suitable for passing to the `CBanEntry(const UniValue&)` constructor. + */ + UniValue ToJson() const; +}; using banmap_t = std::map<CSubNet, CBanEntry>; +/** + * Convert a `banmap_t` object to a JSON array. + * @param[in] bans Bans list to convert. + * @return a JSON array, similar to the one returned by the `listbanned` RPC. Suitable for + * passing to `BanMapFromJson()`. + */ +UniValue BanMapToJson(const banmap_t& bans); + +/** + * Convert a JSON array to a `banmap_t` object. + * @param[in] bans_json JSON to convert, must be as returned by `BanMapToJson()`. + * @param[out] bans Bans list to create from the JSON. + * @throws std::runtime_error if the JSON does not have the expected fields or they contain + * unparsable values. + */ +void BanMapFromJson(const UniValue& bans_json, banmap_t& bans); + #endif // BITCOIN_NET_TYPES_H diff --git a/src/qt/bantablemodel.h b/src/qt/bantablemodel.h index 57f559fc14..4b5b38e43f 100644 --- a/src/qt/bantablemodel.h +++ b/src/qt/bantablemodel.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_QT_BANTABLEMODEL_H #define BITCOIN_QT_BANTABLEMODEL_H +#include <addrdb.h> #include <net.h> #include <memory> diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 55eba60bcd..862bdd3bfe 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -676,8 +676,8 @@ void BitcoinGUI::addWallet(WalletModel* walletModel) { if (!walletFrame) return; - WalletView* wallet_view = new WalletView(platformStyle, walletFrame); - if (!walletFrame->addWallet(walletModel, wallet_view)) return; + WalletView* wallet_view = new WalletView(walletModel, platformStyle, walletFrame); + if (!walletFrame->addView(wallet_view)) return; rpcConsole->addWallet(walletModel); if (m_wallet_selector->count() == 0) { diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 022f367422..f4d561286e 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -112,7 +112,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) WalletContext& context = *node.walletClient().context(); AddWallet(context, wallet); WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get()); - RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt); + RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt); EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); editAddressDialog.setModel(walletModel.getAddressTableModel()); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 1976bee74b..89f2258c0d 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -167,7 +167,7 @@ void TestGUI(interfaces::Node& node) WalletContext& context = *node.walletClient().context(); AddWallet(context, wallet); WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get()); - RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt); + RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt); sendCoinsDialog.setModel(&walletModel); transactionView.setModel(&walletModel); diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 30c29eb356..5eeb2d5308 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -11,6 +11,7 @@ #include <qt/psbtoperationsdialog.h> #include <qt/walletmodel.h> #include <qt/walletview.h> +#include <util/system.h> #include <cassert> @@ -64,14 +65,13 @@ void WalletFrame::setClientModel(ClientModel *_clientModel) } } -bool WalletFrame::addWallet(WalletModel* walletModel, WalletView* walletView) +bool WalletFrame::addView(WalletView* walletView) { - if (!clientModel || !walletModel) return false; + if (!clientModel) return false; - if (mapWalletViews.count(walletModel) > 0) return false; + if (mapWalletViews.count(walletView->getWalletModel()) > 0) return false; walletView->setClientModel(clientModel); - walletView->setWalletModel(walletModel); walletView->showOutOfSyncWarning(bOutOfSync); WalletView* current_wallet_view = currentWalletView(); @@ -82,7 +82,7 @@ bool WalletFrame::addWallet(WalletModel* walletModel, WalletView* walletView) } walletStack->addWidget(walletView); - mapWalletViews[walletModel] = walletView; + mapWalletViews[walletView->getWalletModel()] = walletView; return true; } diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index cbf6af95ec..cfca5c4c5c 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -35,7 +35,7 @@ public: void setClientModel(ClientModel *clientModel); - bool addWallet(WalletModel* walletModel, WalletView* walletView); + bool addView(WalletView* walletView); void setCurrentWallet(WalletModel* wallet_model); void removeWallet(WalletModel* wallet_model); void removeAllWallets(); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 7e96e85c0c..309806a1c4 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -30,19 +30,24 @@ #include <QPushButton> #include <QVBoxLayout> -WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent): - QStackedWidget(parent), - clientModel(nullptr), - walletModel(nullptr), - platformStyle(_platformStyle) +WalletView::WalletView(WalletModel* wallet_model, const PlatformStyle* _platformStyle, QWidget* parent) + : QStackedWidget(parent), + clientModel(nullptr), + walletModel(wallet_model), + platformStyle(_platformStyle) { + assert(walletModel); + // Create tabs overviewPage = new OverviewPage(platformStyle); + overviewPage->setWalletModel(walletModel); transactionsPage = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(); QHBoxLayout *hbox_buttons = new QHBoxLayout(); transactionView = new TransactionView(platformStyle, this); + transactionView->setModel(walletModel); + vbox->addWidget(transactionView); QPushButton *exportButton = new QPushButton(tr("&Export"), this); exportButton->setToolTip(tr("Export the data in the current tab to a file")); @@ -55,10 +60,16 @@ WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent): transactionsPage->setLayout(vbox); receiveCoinsPage = new ReceiveCoinsDialog(platformStyle); + receiveCoinsPage->setModel(walletModel); + sendCoinsPage = new SendCoinsDialog(platformStyle); + sendCoinsPage->setModel(walletModel); usedSendingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this); + usedSendingAddressesPage->setModel(walletModel->getAddressTableModel()); + usedReceivingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this); + usedReceivingAddressesPage->setModel(walletModel->getAddressTableModel()); addWidget(overviewPage); addWidget(transactionsPage); @@ -84,6 +95,21 @@ WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent): connect(transactionView, &TransactionView::message, this, &WalletView::message); connect(this, &WalletView::setPrivacy, overviewPage, &OverviewPage::setPrivacy); + + // Receive and pass through messages from wallet model + connect(walletModel, &WalletModel::message, this, &WalletView::message); + + // Handle changes in encryption status + connect(walletModel, &WalletModel::encryptionStatusChanged, this, &WalletView::encryptionStatusChanged); + + // Balloon pop-up for new transaction + connect(walletModel->getTransactionTableModel(), &TransactionTableModel::rowsInserted, this, &WalletView::processNewTransaction); + + // Ask for passphrase if needed + connect(walletModel, &WalletModel::requireUnlock, this, &WalletView::unlockWallet); + + // Show progress dialog + connect(walletModel, &WalletModel::showProgress, this, &WalletView::showProgress); } WalletView::~WalletView() @@ -96,45 +122,15 @@ void WalletView::setClientModel(ClientModel *_clientModel) overviewPage->setClientModel(_clientModel); sendCoinsPage->setClientModel(_clientModel); - if (walletModel) walletModel->setClientModel(_clientModel); -} - -void WalletView::setWalletModel(WalletModel *_walletModel) -{ - this->walletModel = _walletModel; - - // Put transaction list in tabs - transactionView->setModel(_walletModel); - overviewPage->setWalletModel(_walletModel); - receiveCoinsPage->setModel(_walletModel); - sendCoinsPage->setModel(_walletModel); - usedReceivingAddressesPage->setModel(_walletModel ? _walletModel->getAddressTableModel() : nullptr); - usedSendingAddressesPage->setModel(_walletModel ? _walletModel->getAddressTableModel() : nullptr); - - if (_walletModel) - { - // Receive and pass through messages from wallet model - connect(_walletModel, &WalletModel::message, this, &WalletView::message); - - // Handle changes in encryption status - connect(_walletModel, &WalletModel::encryptionStatusChanged, this, &WalletView::encryptionStatusChanged); - - // Balloon pop-up for new transaction - connect(_walletModel->getTransactionTableModel(), &TransactionTableModel::rowsInserted, this, &WalletView::processNewTransaction); - - // Ask for passphrase if needed - connect(_walletModel, &WalletModel::requireUnlock, this, &WalletView::unlockWallet); - - // Show progress dialog - connect(_walletModel, &WalletModel::showProgress, this, &WalletView::showProgress); - } + walletModel->setClientModel(_clientModel); } void WalletView::processNewTransaction(const QModelIndex& parent, int start, int /*end*/) { // Prevent balloon-spam when initial block download is in progress - if (!walletModel || !clientModel || clientModel->node().isInitialBlockDownload()) + if (!clientModel || clientModel->node().isInitialBlockDownload()) { return; + } TransactionTableModel *ttm = walletModel->getTransactionTableModel(); if (!ttm || ttm->processingQueuedTransactions()) @@ -209,8 +205,6 @@ void WalletView::showOutOfSyncWarning(bool fShow) void WalletView::encryptWallet() { - if(!walletModel) - return; AskPassphraseDialog dlg(AskPassphraseDialog::Encrypt, this); dlg.setModel(walletModel); dlg.exec(); @@ -247,8 +241,6 @@ void WalletView::changePassphrase() void WalletView::unlockWallet() { - if(!walletModel) - return; // Unlock wallet when requested by wallet model if (walletModel->getEncryptionStatus() == WalletModel::Locked) { @@ -260,17 +252,11 @@ void WalletView::unlockWallet() void WalletView::usedSendingAddresses() { - if(!walletModel) - return; - GUIUtil::bringToFront(usedSendingAddressesPage); } void WalletView::usedReceivingAddresses() { - if(!walletModel) - return; - GUIUtil::bringToFront(usedReceivingAddressesPage); } diff --git a/src/qt/walletview.h b/src/qt/walletview.h index bb6ad0f69e..eebc163624 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -35,19 +35,14 @@ class WalletView : public QStackedWidget Q_OBJECT public: - explicit WalletView(const PlatformStyle *platformStyle, QWidget *parent); + explicit WalletView(WalletModel* wallet_model, const PlatformStyle* platformStyle, QWidget* parent); ~WalletView(); /** Set the client model. The client model represents the part of the core that communicates with the P2P network, and is wallet-agnostic. */ void setClientModel(ClientModel *clientModel); - WalletModel *getWalletModel() { return walletModel; } - /** Set the wallet model. - The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending - functionality. - */ - void setWalletModel(WalletModel *walletModel); + WalletModel* getWalletModel() const noexcept { return walletModel; } bool handlePaymentRequest(const SendCoinsRecipient& recipient); @@ -55,7 +50,12 @@ public: private: ClientModel *clientModel; - WalletModel *walletModel; + + //! + //! The wallet model represents a bitcoin wallet, and offers access to + //! the list of transactions, address book and sending functionality. + //! + WalletModel* const walletModel; OverviewPage *overviewPage; QWidget *transactionsPage; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 692096367c..27cbb3a702 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1089,7 +1089,8 @@ static RPCHelpMan estimatesmartfee() "have been observed to make an estimate for any number of blocks."}, }}, RPCExamples{ - HelpExampleCli("estimatesmartfee", "6") + HelpExampleCli("estimatesmartfee", "6") + + HelpExampleRpc("estimatesmartfee", "6") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 1a94abf6d3..e5804211f3 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -109,7 +109,7 @@ static RPCHelpMan createmultisig() "\nCreate a multisig address from 2 public keys\n" + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + "\nAs a JSON-RPC call\n" - + HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -158,6 +158,8 @@ static RPCHelpMan createmultisig() static RPCHelpMan getdescriptorinfo() { + const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)"; + return RPCHelpMan{"getdescriptorinfo", {"\nAnalyses a descriptor.\n"}, { @@ -175,7 +177,8 @@ static RPCHelpMan getdescriptorinfo() }, RPCExamples{ "Analyse a descriptor\n" + - HelpExampleCli("getdescriptorinfo", "\"wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)\"") + HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") + + HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -201,6 +204,8 @@ static RPCHelpMan getdescriptorinfo() static RPCHelpMan deriveaddresses() { + const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu"; + return RPCHelpMan{"deriveaddresses", {"\nDerives one or more addresses corresponding to an output descriptor.\n" "Examples of output descriptors are:\n" @@ -223,7 +228,8 @@ static RPCHelpMan deriveaddresses() }, RPCExamples{ "First three native segwit receive addresses\n" + - HelpExampleCli("deriveaddresses", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu\" \"[0,2]\"") + HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") + + HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { diff --git a/src/script/script.h b/src/script/script.h index 974cde4984..8cd1cc3855 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_SCRIPT_H #define BITCOIN_SCRIPT_SCRIPT_H +#include <attributes.h> #include <crypto/common.h> #include <prevector.h> #include <serialize.h> @@ -438,9 +439,9 @@ public: /** Delete non-existent operator to defend against future introduction */ CScript& operator<<(const CScript& b) = delete; - CScript& operator<<(int64_t b) { return push_int64(b); } + CScript& operator<<(int64_t b) LIFETIMEBOUND { return push_int64(b); } - CScript& operator<<(opcodetype opcode) + CScript& operator<<(opcodetype opcode) LIFETIMEBOUND { if (opcode < 0 || opcode > 0xff) throw std::runtime_error("CScript::operator<<(): invalid opcode"); @@ -448,13 +449,13 @@ public: return *this; } - CScript& operator<<(const CScriptNum& b) + CScript& operator<<(const CScriptNum& b) LIFETIMEBOUND { *this << b.getvch(); return *this; } - CScript& operator<<(const std::vector<unsigned char>& b) + CScript& operator<<(const std::vector<unsigned char>& b) LIFETIMEBOUND { if (b.size() < OP_PUSHDATA1) { diff --git a/src/signet.cpp b/src/signet.cpp index 1ba8502287..aafd1999ee 100644 --- a/src/signet.cpp +++ b/src/signet.cpp @@ -141,7 +141,7 @@ bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& cons PrecomputedTransactionData txdata; txdata.Init(signet_txs->m_to_sign, {signet_txs->m_to_spend.vout[0]}); - TransactionSignatureChecker sigcheck(&signet_txs->m_to_sign, /*nIn=*/ 0, /*amount=*/ signet_txs->m_to_spend.vout[0].nValue, txdata, MissingDataBehavior::ASSERT_FAIL); + TransactionSignatureChecker sigcheck(&signet_txs->m_to_sign, /* nInIn= */ 0, /* amountIn= */ signet_txs->m_to_spend.vout[0].nValue, txdata, MissingDataBehavior::ASSERT_FAIL); if (!VerifyScript(scriptSig, signet_txs->m_to_spend.vout[0].scriptPubKey, &witness, BLOCK_SCRIPT_VERIFY_FLAGS, sigcheck)) { LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution invalid)\n"); diff --git a/src/sync.cpp b/src/sync.cpp index a2b62c2286..eace86d9dd 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -9,6 +9,7 @@ #include <sync.h> #include <logging.h> +#include <logging/timer.h> #include <tinyformat.h> #include <util/strencodings.h> #include <util/threadnames.h> @@ -23,16 +24,10 @@ #include <utility> #include <vector> -#ifdef DEBUG_LOCKCONTENTION -#if !defined(HAVE_THREAD_LOCAL) -static_assert(false, "thread_local is not supported"); -#endif -void PrintLockContention(const char* pszName, const char* pszFile, int nLine) +void LockContention(const char* pszName, const char* pszFile, int nLine) { - LogPrintf("LOCKCONTENTION: %s\n", pszName); - LogPrintf("Locker: %s:%d\n", pszFile, nLine); + LOG_TIME_MICROS_WITH_CATEGORY(strprintf("%s, %s:%d", pszName, pszFile, nLine), BCLog::LOCK); } -#endif /* DEBUG_LOCKCONTENTION */ #ifdef DEBUG_LOCKORDER // diff --git a/src/sync.h b/src/sync.h index 146c228592..bf15c0b4eb 100644 --- a/src/sync.h +++ b/src/sync.h @@ -126,9 +126,8 @@ using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>; /** Wrapped mutex: supports waiting but not recursive locking */ typedef AnnotatedMixin<std::mutex> Mutex; -#ifdef DEBUG_LOCKCONTENTION -void PrintLockContention(const char* pszName, const char* pszFile, int nLine); -#endif +/** Prints a lock contention to the log */ +void LockContention(const char* pszName, const char* pszFile, int nLine); /** Wrapper around std::unique_lock style lock for Mutex. */ template <typename Mutex, typename Base = typename Mutex::UniqueLock> @@ -138,22 +137,18 @@ private: void Enter(const char* pszName, const char* pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, Base::mutex()); -#ifdef DEBUG_LOCKCONTENTION - if (!Base::try_lock()) { - PrintLockContention(pszName, pszFile, nLine); -#endif - Base::lock(); -#ifdef DEBUG_LOCKCONTENTION - } -#endif + if (Base::try_lock()) return; + LockContention(pszName, pszFile, nLine); // log the contention + Base::lock(); } bool TryEnter(const char* pszName, const char* pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, Base::mutex(), true); Base::try_lock(); - if (!Base::owns_lock()) + if (!Base::owns_lock()) { LeaveCritical(); + } return Base::owns_lock(); } diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index cd5dc2370f..835b18d42e 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -5,13 +5,14 @@ #include <addrdb.h> #include <addrman.h> #include <chainparams.h> +#include <clientversion.h> +#include <hash.h> +#include <netbase.h> +#include <random.h> #include <test/data/asmap.raw.h> #include <test/util/setup_common.h> #include <util/asmap.h> #include <util/string.h> -#include <hash.h> -#include <netbase.h> -#include <random.h> #include <boost/test/unit_test.hpp> @@ -26,7 +27,7 @@ public: virtual void Serialize(CDataStream& s) const = 0; CAddrManSerializationMock() - : CAddrMan(/* deterministic */ true, /* consistency_check_ratio */ 100) + : CAddrMan(/* asmap */ std::vector<bool>(), /* deterministic */ true, /* consistency_check_ratio */ 100) {} }; @@ -82,10 +83,9 @@ private: public: explicit CAddrManTest(bool makeDeterministic = true, std::vector<bool> asmap = std::vector<bool>()) - : CAddrMan(makeDeterministic, /* consistency_check_ratio */ 100) + : CAddrMan(asmap, makeDeterministic, /* consistency_check_ratio */ 100) { deterministic = makeDeterministic; - m_asmap = asmap; } CAddrInfo* Find(const CNetAddr& addr, int* pnId = nullptr) @@ -1003,7 +1003,7 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } -BOOST_AUTO_TEST_CASE(caddrdb_read) +BOOST_AUTO_TEST_CASE(load_addrman) { CAddrManUncorrupted addrmanUncorrupted; @@ -1024,7 +1024,7 @@ BOOST_AUTO_TEST_CASE(caddrdb_read) // Test that the de-serialization does not throw an exception. CDataStream ssPeers1 = AddrmanToStream(addrmanUncorrupted); bool exceptionThrown = false; - CAddrMan addrman1(/* deterministic */ false, /* consistency_check_ratio */ 100); + CAddrMan addrman1(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 100); BOOST_CHECK(addrman1.size() == 0); try { @@ -1038,24 +1038,24 @@ BOOST_AUTO_TEST_CASE(caddrdb_read) BOOST_CHECK(addrman1.size() == 3); BOOST_CHECK(exceptionThrown == false); - // Test that CAddrDB::Read creates an addrman with the correct number of addrs. + // Test that ReadFromStream creates an addrman with the correct number of addrs. CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted); - CAddrMan addrman2(/* deterministic */ false, /* consistency_check_ratio */ 100); + CAddrMan addrman2(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 100); BOOST_CHECK(addrman2.size() == 0); - BOOST_CHECK(CAddrDB::Read(addrman2, ssPeers2)); + BOOST_CHECK(ReadFromStream(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 3); } -BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) +BOOST_AUTO_TEST_CASE(load_addrman_corrupted) { CAddrManCorrupted addrmanCorrupted; // Test that the de-serialization of corrupted addrman throws an exception. CDataStream ssPeers1 = AddrmanToStream(addrmanCorrupted); bool exceptionThrown = false; - CAddrMan addrman1(/* deterministic */ false, /* consistency_check_ratio */ 100); + CAddrMan addrman1(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 100); BOOST_CHECK(addrman1.size() == 0); try { unsigned char pchMsgTmp[4]; @@ -1064,16 +1064,16 @@ BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) } catch (const std::exception&) { exceptionThrown = true; } - // Even through de-serialization failed addrman is not left in a clean state. + // Even though de-serialization failed addrman is not left in a clean state. BOOST_CHECK(addrman1.size() == 1); BOOST_CHECK(exceptionThrown); - // Test that CAddrDB::Read fails if peers.dat is corrupt + // Test that ReadFromStream fails if peers.dat is corrupt CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted); - CAddrMan addrman2(/* deterministic */ false, /* consistency_check_ratio */ 100); + CAddrMan addrman2(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 100); BOOST_CHECK(addrman2.size() == 0); - BOOST_CHECK(!CAddrDB::Read(addrman2, ssPeers2)); + BOOST_CHECK(!ReadFromStream(addrman2, ssPeers2)); } diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 95aa53bff4..e95126a80f 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -28,17 +28,11 @@ class CAddrManDeterministic : public CAddrMan public: FuzzedDataProvider& m_fuzzed_data_provider; - explicit CAddrManDeterministic(FuzzedDataProvider& fuzzed_data_provider) - : CAddrMan(/* deterministic */ true, /* consistency_check_ratio */ 0) + explicit CAddrManDeterministic(std::vector<bool> asmap, FuzzedDataProvider& fuzzed_data_provider) + : CAddrMan(std::move(asmap), /* deterministic */ true, /* consistency_check_ratio */ 0) , m_fuzzed_data_provider(fuzzed_data_provider) { WITH_LOCK(cs, insecure_rand = FastRandomContext{ConsumeUInt256(fuzzed_data_provider)}); - if (fuzzed_data_provider.ConsumeBool()) { - m_asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); - if (!SanityCheckASMap(m_asmap)) { - m_asmap.clear(); - } - } } /** @@ -224,11 +218,19 @@ public: } }; +[[nodiscard]] inline std::vector<bool> ConsumeAsmap(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); + if (!SanityCheckASMap(asmap)) asmap.clear(); + return asmap; +} + FUZZ_TARGET_INIT(addrman, initialize_addrman) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); SetMockTime(ConsumeTime(fuzzed_data_provider)); - auto addr_man_ptr = std::make_unique<CAddrManDeterministic>(fuzzed_data_provider); + std::vector<bool> asmap = ConsumeAsmap(fuzzed_data_provider); + auto addr_man_ptr = std::make_unique<CAddrManDeterministic>(asmap, fuzzed_data_provider); if (fuzzed_data_provider.ConsumeBool()) { const std::vector<uint8_t> serialized_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)}; CDataStream ds(serialized_data, SER_DISK, INIT_PROTO_VERSION); @@ -237,7 +239,7 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) try { ds >> *addr_man_ptr; } catch (const std::ios_base::failure&) { - addr_man_ptr = std::make_unique<CAddrManDeterministic>(fuzzed_data_provider); + addr_man_ptr = std::make_unique<CAddrManDeterministic>(asmap, fuzzed_data_provider); } } CAddrManDeterministic& addr_man = *addr_man_ptr; @@ -306,9 +308,9 @@ FUZZ_TARGET_INIT(addrman_serdeser, initialize_addrman) FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); SetMockTime(ConsumeTime(fuzzed_data_provider)); - CAddrManDeterministic addr_man1{fuzzed_data_provider}; - CAddrManDeterministic addr_man2{fuzzed_data_provider}; - addr_man2.m_asmap = addr_man1.m_asmap; + std::vector<bool> asmap = ConsumeAsmap(fuzzed_data_provider); + CAddrManDeterministic addr_man1{asmap, fuzzed_data_provider}; + CAddrManDeterministic addr_man2{asmap, fuzzed_data_provider}; CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index 0e323ddc20..01741103e4 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -25,7 +25,7 @@ FUZZ_TARGET_INIT(connman, initialize_connman) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; SetMockTime(ConsumeTime(fuzzed_data_provider)); - CAddrMan addrman(/* deterministic */ false, /* consistency_check_ratio */ 0); + CAddrMan addrman(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0); CConnman connman{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>(), addrman, fuzzed_data_provider.ConsumeBool()}; CNetAddr random_netaddr; CNode random_node = ConsumeNode(fuzzed_data_provider); @@ -104,12 +104,6 @@ FUZZ_TARGET_INIT(connman, initialize_connman) connman.RemoveAddedNode(random_string); }, [&] { - const std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); - if (SanityCheckASMap(asmap)) { - connman.SetAsmap(asmap); - } - }, - [&] { connman.SetNetworkActive(fuzzed_data_provider.ConsumeBool()); }, [&] { diff --git a/src/test/fuzz/data_stream.cpp b/src/test/fuzz/data_stream.cpp index 53400082ab..08e9c91ba3 100644 --- a/src/test/fuzz/data_stream.cpp +++ b/src/test/fuzz/data_stream.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <addrdb.h> #include <addrman.h> #include <net.h> #include <test/fuzz/FuzzedDataProvider.h> @@ -21,6 +22,6 @@ FUZZ_TARGET_INIT(data_stream_addr_man, initialize_data_stream_addr_man) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; CDataStream data_stream = ConsumeDataStream(fuzzed_data_provider); - CAddrMan addr_man(/* deterministic */ false, /* consistency_check_ratio */ 0); - CAddrDB::Read(addr_man, data_stream); + CAddrMan addr_man(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0); + ReadFromStream(addr_man, data_stream); } diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index cfbbe77311..83ae1680e3 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -188,7 +188,7 @@ FUZZ_TARGET_DESERIALIZE(blockmerkleroot, { BlockMerkleRoot(block, &mutated); }) FUZZ_TARGET_DESERIALIZE(addrman_deserialize, { - CAddrMan am(/* deterministic */ false, /* consistency_check_ratio */ 0); + CAddrMan am(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0); DeserializeFromFuzzingInput(buffer, am); }) FUZZ_TARGET_DESERIALIZE(blockheader_deserialize, { diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index 5d26529837..5a732aeeff 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -23,6 +23,7 @@ #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> #include <uint256.h> +#include <univalue.h> #include <util/check.h> #include <util/moneystr.h> #include <util/strencodings.h> diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index 9a579c053f..ff0259c182 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -43,7 +43,7 @@ FUZZ_TARGET_INIT(net, initialize_net) return; } CNodeStats stats; - node.copyStats(stats, asmap); + node.CopyStats(stats, asmap); }, [&] { const CNode* add_ref_node = node.AddRef(); diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index 9186821836..73a7d24971 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -6,6 +6,7 @@ #include <chainparams.h> #include <consensus/params.h> #include <primitives/block.h> +#include <util/system.h> #include <versionbits.h> #include <test/fuzz/FuzzedDataProvider.h> diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp index e99c6e0fc8..e2e31c62d7 100644 --- a/src/test/logging_tests.cpp +++ b/src/test/logging_tests.cpp @@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(logging_timer) SetMockTime(1); auto micro_timer = BCLog::Timer<std::chrono::microseconds>("tests", "end_msg"); SetMockTime(2); - BOOST_CHECK_EQUAL(micro_timer.LogMsg("test micros"), "tests: test micros (1000000.00μs)"); + BOOST_CHECK_EQUAL(micro_timer.LogMsg("test micros"), "tests: test micros (1000000μs)"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 20e26d1c35..24029ea02e 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -765,95 +765,89 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) key.MakeNewKey(true); t.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - std::string reason; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + constexpr auto CheckIsStandard = [](const auto& t) { + std::string reason; + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK(reason.empty()); + }; + constexpr auto CheckIsNotStandard = [](const auto& t, const std::string& reason_in) { + std::string reason; + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason_in, reason); + }; + + CheckIsStandard(t); // Check dust with default relay fee: - CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK()/1000; + CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK() / 1000; BOOST_CHECK_EQUAL(nDustThreshold, 546); // dust: t.vout[0].nValue = nDustThreshold - 1; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "dust"); + CheckIsNotStandard(t, "dust"); // not dust: t.vout[0].nValue = nDustThreshold; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); // Disallowed nVersion t.nVersion = -1; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "version"); + CheckIsNotStandard(t, "version"); t.nVersion = 0; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "version"); + CheckIsNotStandard(t, "version"); t.nVersion = 3; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "version"); + CheckIsNotStandard(t, "version"); // Allowed nVersion t.nVersion = 1; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); t.nVersion = 2; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); // Check dust with odd relay fee to verify rounding: // nDustThreshold = 182 * 3702 / 1000 dustRelayFee = CFeeRate(3702); // dust: t.vout[0].nValue = 673 - 1; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "dust"); + CheckIsNotStandard(t, "dust"); // not dust: t.vout[0].nValue = 673; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); t.vout[0].scriptPubKey = CScript() << OP_1; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "scriptpubkey"); + CheckIsNotStandard(t, "scriptpubkey"); // MAX_OP_RETURN_RELAY-byte TxoutType::NULL_DATA (standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY, t.vout[0].scriptPubKey.size()); - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); // MAX_OP_RETURN_RELAY+1-byte TxoutType::NULL_DATA (non-standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800"); BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY + 1, t.vout[0].scriptPubKey.size()); - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "scriptpubkey"); + CheckIsNotStandard(t, "scriptpubkey"); // Data payload can be encoded in any way... t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex(""); - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("00") << ParseHex("01"); - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); // OP_RESERVED *is* considered to be a PUSHDATA type opcode by IsPushOnly()! t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RESERVED << -1 << 0 << ParseHex("01") << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 15 << 16; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); t.vout[0].scriptPubKey = CScript() << OP_RETURN << 0 << ParseHex("01") << 2 << ParseHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); // ...so long as it only contains PUSHDATA's t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RETURN; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "scriptpubkey"); + CheckIsNotStandard(t, "scriptpubkey"); // TxoutType::NULL_DATA w/o PUSHDATA t.vout.resize(1); t.vout[0].scriptPubKey = CScript() << OP_RETURN; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); // Only one TxoutType::NULL_DATA permitted in all cases t.vout.resize(2); @@ -861,21 +855,15 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout[0].nValue = 0; t.vout[1].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[1].nValue = 0; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "multi-op-return"); + CheckIsNotStandard(t, "multi-op-return"); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[1].scriptPubKey = CScript() << OP_RETURN; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "multi-op-return"); + CheckIsNotStandard(t, "multi-op-return"); t.vout[0].scriptPubKey = CScript() << OP_RETURN; t.vout[1].scriptPubKey = CScript() << OP_RETURN; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "multi-op-return"); + CheckIsNotStandard(t, "multi-op-return"); // Check large scriptSig (non-standard if size is >1650 bytes) t.vout.resize(1); @@ -883,12 +871,10 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); // OP_PUSHDATA2 with len (3 bytes) + data (1647 bytes) = 1650 bytes t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(1647, 0); // 1650 - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(1648, 0); // 1651 - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "scriptsig-size"); + CheckIsNotStandard(t, "scriptsig-size"); // Check scriptSig format (non-standard if there are any other ops than just PUSHs) t.vin[0].scriptSig = CScript() @@ -897,7 +883,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) << std::vector<unsigned char>(235, 0) // OP_PUSHDATA1 x [...x bytes...] << std::vector<unsigned char>(1234, 0) // OP_PUSHDATA2 x [...x bytes...] << OP_9; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); const std::vector<unsigned char> non_push_ops = { // arbitrary set of non-push operations OP_NOP, OP_VERIFY, OP_IF, OP_ROT, OP_3DUP, OP_SIZE, OP_EQUAL, OP_ADD, OP_SUB, @@ -917,11 +903,10 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) // replace current push-op with each non-push-op for (auto op : non_push_ops) { t.vin[0].scriptSig[index] = op; - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "scriptsig-not-pushonly"); + CheckIsNotStandard(t, "scriptsig-not-pushonly"); } t.vin[0].scriptSig[index] = orig_op; // restore op - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); } // Check tx-size (non-standard if transaction weight is > MAX_STANDARD_TX_WEIGHT) @@ -934,53 +919,46 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) // =============================== // total: 400000 vbytes BOOST_CHECK_EQUAL(GetTransactionWeight(CTransaction(t)), 400000); - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); // increase output size by one byte, so we end up with 400004 vbytes t.vout[0].scriptPubKey = CScript() << OP_RETURN << std::vector<unsigned char>(20, 0); // output size: 31 bytes BOOST_CHECK_EQUAL(GetTransactionWeight(CTransaction(t)), 400004); - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "tx-size"); + CheckIsNotStandard(t, "tx-size"); // Check bare multisig (standard if policy flag fIsBareMultisigStd is set) fIsBareMultisigStd = true; t.vout[0].scriptPubKey = GetScriptForMultisig(1, {key.GetPubKey()}); // simple 1-of-1 t.vin.resize(1); t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(65, 0); - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); fIsBareMultisigStd = false; - reason.clear(); - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "bare-multisig"); + CheckIsNotStandard(t, "bare-multisig"); fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG; // Check P2WPKH outputs dust threshold t.vout[0].scriptPubKey = CScript() << OP_0 << ParseHex("ffffffffffffffffffffffffffffffffffffffff"); t.vout[0].nValue = 294; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); t.vout[0].nValue = 293; - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "dust"); + CheckIsNotStandard(t, "dust"); // Check P2WSH outputs dust threshold t.vout[0].scriptPubKey = CScript() << OP_0 << ParseHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); t.vout[0].nValue = 330; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); t.vout[0].nValue = 329; - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "dust"); + CheckIsNotStandard(t, "dust"); // Check future Witness Program versions dust threshold for (int op = OP_2; op <= OP_16; op += 1) { t.vout[0].scriptPubKey = CScript() << (opcodetype)op << ParseHex("ffff"); t.vout[0].nValue = 240; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + CheckIsStandard(t); t.vout[0].nValue = 239; - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); - BOOST_CHECK_EQUAL(reason, "dust"); + CheckIsNotStandard(t, "dust"); } } diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index c9bb863a7b..ba6b3e32ea 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -193,7 +193,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString())); } - m_node.addrman = std::make_unique<CAddrMan>(/* deterministic */ false, /* consistency_check_ratio */ 0); + m_node.addrman = std::make_unique<CAddrMan>(/* asmap */ std::vector<bool>(), /* deterministic */ false, /* consistency_check_ratio */ 0); m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); m_node.connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); // Deterministic randomness for tests. m_node.peerman = PeerManager::make(chainparams, *m_node.connman, *m_node.addrman, diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 4f9d10cf1d..a62abf9b9c 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -173,6 +173,22 @@ BOOST_AUTO_TEST_CASE(util_Join) BOOST_CHECK_EQUAL(Join<std::string>({"foo", "bar"}, ", ", op_upper), "FOO, BAR"); } +BOOST_AUTO_TEST_CASE(util_TrimString) +{ + BOOST_CHECK_EQUAL(TrimString(" foo bar "), "foo bar"); + BOOST_CHECK_EQUAL(TrimString("\t \n \n \f\n\r\t\v\tfoo \n \f\n\r\t\v\tbar\t \n \f\n\r\t\v\t\n "), "foo \n \f\n\r\t\v\tbar"); + BOOST_CHECK_EQUAL(TrimString("\t \n foo \n\tbar\t \n "), "foo \n\tbar"); + BOOST_CHECK_EQUAL(TrimString("\t \n foo \n\tbar\t \n ", "fobar"), "\t \n foo \n\tbar\t \n "); + BOOST_CHECK_EQUAL(TrimString("foo bar"), "foo bar"); + BOOST_CHECK_EQUAL(TrimString("foo bar", "fobar"), " "); + BOOST_CHECK_EQUAL(TrimString(std::string("\0 foo \0 ", 8)), std::string("\0 foo \0", 7)); + BOOST_CHECK_EQUAL(TrimString(std::string(" foo ", 5)), std::string("foo", 3)); + BOOST_CHECK_EQUAL(TrimString(std::string("\t\t\0\0\n\n", 6)), std::string("\0\0", 2)); + BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6)), std::string("\x05\x04\x03\x02\x01\x00", 6)); + BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01", 5)), std::string("\0", 1)); + BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01\x00", 6)), ""); +} + BOOST_AUTO_TEST_CASE(util_FormatParseISO8601DateTime) { BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z"); diff --git a/src/util/getuniquepath.cpp b/src/util/getuniquepath.cpp index 9839d2f624..6776e7785b 100644 --- a/src/util/getuniquepath.cpp +++ b/src/util/getuniquepath.cpp @@ -1,3 +1,7 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + #include <random.h> #include <fs.h> #include <util/strencodings.h> diff --git a/src/util/settings.cpp b/src/util/settings.cpp index b92b1d30c3..846b34089d 100644 --- a/src/util/settings.cpp +++ b/src/util/settings.cpp @@ -60,9 +60,15 @@ bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& va values.clear(); errors.clear(); + // Ok for file to not exist + if (!fs::exists(path)) return true; + fsbridge::ifstream file; file.open(path); - if (!file.is_open()) return true; // Ok for file not to exist. + if (!file.is_open()) { + errors.emplace_back(strprintf("%s. Please check permissions.", path.string())); + return false; + } SettingsValue in; if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) { diff --git a/src/util/system.cpp b/src/util/system.cpp index 4e16a83c87..08f62f1da7 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1306,7 +1306,7 @@ void SetupEnvironment() #endif // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale // may be invalid, in which case the "C.UTF-8" locale is used as fallback. -#if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) +#if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) try { std::locale(""); // Raises a runtime error if current locale is invalid } catch (const std::runtime_error&) { diff --git a/src/validation.cpp b/src/validation.cpp index 753b824167..7e3663c465 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -415,7 +415,7 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS } // Call CheckInputScripts() to cache signature and script validity against current tip consensus rules. - return CheckInputScripts(tx, state, view, flags, /* cacheSigStore = */ true, /* cacheFullSciptStore = */ true, txdata); + return CheckInputScripts(tx, state, view, flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata); } namespace { diff --git a/src/validation.h b/src/validation.h index ef48156309..d4fcac1d48 100644 --- a/src/validation.h +++ b/src/validation.h @@ -929,7 +929,7 @@ public: CChainState& InitializeChainstate( CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash = std::nullopt) - EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + LIFETIMEBOUND EXCLUSIVE_LOCKS_REQUIRED(::cs_main); //! Get all chainstates currently being used. std::vector<CChainState*> GetAll(); diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 30fef50c3b..f2de68295e 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -12,6 +12,8 @@ #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/fees.h> +#include <wallet/receive.h> +#include <wallet/spend.h> #include <wallet/wallet.h> //! Check whether transaction has descendant in wallet or mempool, or has been @@ -30,7 +32,7 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet } } - if (wtx.GetDepthInMainChain() != 0) { + if (wallet.GetTxDepthInMainChain(wtx) != 0) { errors.push_back(Untranslated("Transaction has been mined, or is conflicted with a mined transaction")); return feebumper::Result::WALLET_ERROR; } @@ -48,7 +50,7 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet // 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; - if (!wallet.IsAllFromMe(*wtx.tx, filter)) { + if (!AllInputsMine(wallet, *wtx.tx, filter)) { errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet")); return feebumper::Result::WALLET_ERROR; } @@ -81,7 +83,7 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt // Given old total fee and transaction size, calculate the old feeRate isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; - CAmount old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut(); + CAmount old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut(); const int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); CFeeRate nOldFeeRate(old_fee, txSize); // Min total fee is old fee + relay fee @@ -174,7 +176,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo // Fill in recipients(and preserve a single change key if there is one) std::vector<CRecipient> recipients; for (const auto& output : wtx.tx->vout) { - if (!wallet.IsChange(output)) { + if (!OutputIsChange(wallet, output)) { CRecipient recipient = {output.scriptPubKey, output.nValue, false}; recipients.push_back(recipient); } else { @@ -185,7 +187,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo } isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; - old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut(); + old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut(); if (coin_control.m_feerate) { // The user provided a feeRate argument. @@ -220,7 +222,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo int change_pos_in_out = -1; // No requested location for change bilingual_str fail_reason; FeeCalculation fee_calc_out; - if (!wallet.CreateTransaction(recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) { + if (!CreateTransaction(wallet, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) { errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason); return Result::WALLET_ERROR; } diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 0d4b98ecaf..9a8c1e3c02 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -23,7 +23,9 @@ #include <wallet/fees.h> #include <wallet/ismine.h> #include <wallet/load.h> +#include <wallet/receive.h> #include <wallet/rpcwallet.h> +#include <wallet/spend.h> #include <wallet/wallet.h> #include <memory> @@ -55,7 +57,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) result.tx = wtx.tx; result.txin_is_mine.reserve(wtx.tx->vin.size()); for (const auto& txin : wtx.tx->vin) { - result.txin_is_mine.emplace_back(wallet.IsMine(txin)); + result.txin_is_mine.emplace_back(InputIsMine(wallet, txin)); } result.txout_is_mine.reserve(wtx.tx->vout.size()); result.txout_address.reserve(wtx.tx->vout.size()); @@ -67,9 +69,9 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) wallet.IsMine(result.txout_address.back()) : ISMINE_NO); } - result.credit = wtx.GetCredit(ISMINE_ALL); - result.debit = wtx.GetDebit(ISMINE_ALL); - result.change = wtx.GetChange(); + result.credit = CachedTxGetCredit(wallet, wtx, ISMINE_ALL); + result.debit = CachedTxGetDebit(wallet, wtx, ISMINE_ALL); + result.change = CachedTxGetChange(wallet, wtx); result.time = wtx.GetTxTime(); result.value_map = wtx.mapValue; result.is_coinbase = wtx.IsCoinBase(); @@ -81,15 +83,15 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) { WalletTxStatus result; result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max(); - result.blocks_to_maturity = wtx.GetBlocksToMaturity(); - result.depth_in_main_chain = wtx.GetDepthInMainChain(); + result.blocks_to_maturity = wallet.GetTxBlocksToMaturity(wtx); + result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = wallet.chain().checkFinalTx(*wtx.tx); - result.is_trusted = wtx.IsTrusted(); + result.is_trusted = CachedTxIsTrusted(wallet, wtx); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); - result.is_in_main_chain = wtx.IsInMainChain(); + result.is_in_main_chain = wallet.IsTxInMainChain(wtx); return result; } @@ -242,7 +244,7 @@ public: LOCK(m_wallet->cs_wallet); CTransactionRef tx; FeeCalculation fee_calc_out; - if (!m_wallet->CreateTransaction(recipients, tx, fee, change_pos, + if (!CreateTransaction(*m_wallet, recipients, tx, fee, change_pos, fail_reason, coin_control, fee_calc_out, sign)) { return {}; } @@ -358,7 +360,7 @@ public: } WalletBalances getBalances() override { - const auto bal = m_wallet->GetBalance(); + const auto bal = GetBalance(*m_wallet); WalletBalances result; result.balance = bal.m_mine_trusted; result.unconfirmed_balance = bal.m_mine_untrusted_pending; @@ -381,15 +383,15 @@ public: balances = getBalances(); return true; } - CAmount getBalance() override { return m_wallet->GetBalance().m_mine_trusted; } + CAmount getBalance() override { return GetBalance(*m_wallet).m_mine_trusted; } CAmount getAvailableBalance(const CCoinControl& coin_control) override { - return m_wallet->GetAvailableBalance(&coin_control); + return GetAvailableBalance(*m_wallet, &coin_control); } isminetype txinIsMine(const CTxIn& txin) override { LOCK(m_wallet->cs_wallet); - return m_wallet->IsMine(txin); + return InputIsMine(*m_wallet, txin); } isminetype txoutIsMine(const CTxOut& txout) override { @@ -404,13 +406,13 @@ public: CAmount getCredit(const CTxOut& txout, isminefilter filter) override { LOCK(m_wallet->cs_wallet); - return m_wallet->GetCredit(txout, filter); + return OutputGetCredit(*m_wallet, txout, filter); } CoinsList listCoins() override { LOCK(m_wallet->cs_wallet); CoinsList result; - for (const auto& entry : m_wallet->ListCoins()) { + for (const auto& entry : ListCoins(*m_wallet)) { auto& group = result[entry.first]; for (const auto& coin : entry.second) { group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), @@ -428,7 +430,7 @@ public: result.emplace_back(); auto it = m_wallet->mapWallet.find(output.hash); if (it != m_wallet->mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = m_wallet->GetTxDepthInMainChain(it->second); if (depth >= 0) { result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth); } diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 85cdbb67c9..1b841026b8 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -13,6 +13,7 @@ #include <util/system.h> #include <util/translation.h> #include <wallet/context.h> +#include <wallet/spend.h> #include <wallet/wallet.h> #include <wallet/walletdb.h> @@ -164,7 +165,7 @@ void UnloadWallets(WalletContext& context) auto wallet = wallets.back(); wallets.pop_back(); std::vector<bilingual_str> warnings; - RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt, warnings); + RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt, warnings); UnloadWallet(std::move(wallet)); } } diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index de81dbf324..98706dcdf8 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -7,27 +7,27 @@ #include <wallet/transaction.h> #include <wallet/wallet.h> -isminetype CWallet::IsMine(const CTxIn &txin) const +isminetype InputIsMine(const CWallet& wallet, const CTxIn &txin) { - AssertLockHeld(cs_wallet); - std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash); - if (mi != mapWallet.end()) + AssertLockHeld(wallet.cs_wallet); + std::map<uint256, CWalletTx>::const_iterator mi = wallet.mapWallet.find(txin.prevout.hash); + if (mi != wallet.mapWallet.end()) { const CWalletTx& prev = (*mi).second; if (txin.prevout.n < prev.tx->vout.size()) - return IsMine(prev.tx->vout[txin.prevout.n]); + return wallet.IsMine(prev.tx->vout[txin.prevout.n]); } return ISMINE_NO; } -bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const +bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter) { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); for (const CTxIn& txin : tx.vin) { - auto mi = mapWallet.find(txin.prevout.hash); - if (mi == mapWallet.end()) + auto mi = wallet.mapWallet.find(txin.prevout.hash); + if (mi == wallet.mapWallet.end()) return false; // any unknown inputs can't be from us const CWalletTx& prev = (*mi).second; @@ -35,33 +35,33 @@ bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) co if (txin.prevout.n >= prev.tx->vout.size()) return false; // invalid input! - if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter)) + if (!(wallet.IsMine(prev.tx->vout[txin.prevout.n]) & filter)) return false; } return true; } -CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const +CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter) { if (!MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); - LOCK(cs_wallet); - return ((IsMine(txout) & filter) ? txout.nValue : 0); + LOCK(wallet.cs_wallet); + return ((wallet.IsMine(txout) & filter) ? txout.nValue : 0); } -CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const +CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter) { CAmount nCredit = 0; for (const CTxOut& txout : tx.vout) { - nCredit += GetCredit(txout, filter); + nCredit += OutputGetCredit(wallet, txout, filter); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } return nCredit; } -bool CWallet::IsChange(const CScript& script) const +bool ScriptIsChange(const CWallet& wallet, const CScript& script) { // TODO: fix handling of 'change' outputs. The assumption is that any // payment to a script that is ours, but is not in the address book @@ -70,179 +70,177 @@ bool CWallet::IsChange(const CScript& script) const // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). - AssertLockHeld(cs_wallet); - if (IsMine(script)) + AssertLockHeld(wallet.cs_wallet); + if (wallet.IsMine(script)) { CTxDestination address; if (!ExtractDestination(script, address)) return true; - if (!FindAddressBookEntry(address)) { + if (!wallet.FindAddressBookEntry(address)) { return true; } } return false; } -bool CWallet::IsChange(const CTxOut& txout) const +bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) { - return IsChange(txout.scriptPubKey); + return ScriptIsChange(wallet, txout.scriptPubKey); } -CAmount CWallet::GetChange(const CTxOut& txout) const +CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); if (!MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); - return (IsChange(txout) ? txout.nValue : 0); + return (OutputIsChange(wallet, txout) ? txout.nValue : 0); } -CAmount CWallet::GetChange(const CTransaction& tx) const +CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx) { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); CAmount nChange = 0; for (const CTxOut& txout : tx.vout) { - nChange += GetChange(txout); + nChange += OutputGetChange(wallet, txout); if (!MoneyRange(nChange)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } return nChange; } -CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate) const +static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, const isminefilter& filter, bool recalculate = false) { - auto& amount = m_amounts[type]; + auto& amount = wtx.m_amounts[type]; if (recalculate || !amount.m_cached[filter]) { - amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter)); - m_is_cache_empty = false; + amount.Set(filter, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter) : TxGetCredit(wallet, *wtx.tx, filter)); + wtx.m_is_cache_empty = false; } return amount.m_value[filter]; } -CAmount CWalletTx::GetCredit(const isminefilter& filter) const +CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (wallet.IsTxImmatureCoinBase(wtx)) return 0; CAmount credit = 0; if (filter & ISMINE_SPENDABLE) { // GetBalance can assume transactions in mapWallet won't change - credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE); + credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_SPENDABLE); } if (filter & ISMINE_WATCH_ONLY) { - credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY); + credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_WATCH_ONLY); } return credit; } -CAmount CWalletTx::GetDebit(const isminefilter& filter) const +CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { - if (tx->vin.empty()) + if (wtx.tx->vin.empty()) return 0; CAmount debit = 0; if (filter & ISMINE_SPENDABLE) { - debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE); + debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_SPENDABLE); } if (filter & ISMINE_WATCH_ONLY) { - debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY); + debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_WATCH_ONLY); } return debit; } -CAmount CWalletTx::GetChange() const +CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx) { - if (fChangeCached) - return nChangeCached; - nChangeCached = pwallet->GetChange(*tx); - fChangeCached = true; - return nChangeCached; + if (wtx.fChangeCached) + return wtx.nChangeCached; + wtx.nChangeCached = TxGetChange(wallet, *wtx.tx); + wtx.fChangeCached = true; + return wtx.nChangeCached; } -CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache) { - if (IsImmatureCoinBase() && IsInMainChain()) { - return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); + if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { + return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); } return 0; } -CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const +CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache) { - if (IsImmatureCoinBase() && IsInMainChain()) { - return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); + if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { + return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); } return 0; } -CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache, const isminefilter& filter) { - if (pwallet == nullptr) - return 0; - // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future). bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (wallet.IsTxImmatureCoinBase(wtx)) return 0; - if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { - return m_amounts[AVAILABLE_CREDIT].m_value[filter]; + if (fUseCache && allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) { + return wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_value[filter]; } - bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); + bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); CAmount nCredit = 0; - uint256 hashTx = GetHash(); - for (unsigned int i = 0; i < tx->vout.size(); i++) + uint256 hashTx = wtx.GetHash(); + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { - if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) { - const CTxOut &txout = tx->vout[i]; - nCredit += pwallet->GetCredit(txout, filter); + if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) { + const CTxOut &txout = wtx.tx->vout[i]; + nCredit += OutputGetCredit(wallet, txout, filter); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + " : value out of range"); } } if (allow_cache) { - m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); - m_is_cache_empty = false; + wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].Set(filter, nCredit); + wtx.m_is_cache_empty = false; } return nCredit; } -void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, - std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const +void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, + std::list<COutputEntry>& listReceived, + std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) { nFee = 0; listReceived.clear(); listSent.clear(); // Compute fee: - CAmount nDebit = GetDebit(filter); + CAmount nDebit = CachedTxGetDebit(wallet, wtx, filter); if (nDebit > 0) // debit>0 means we signed/sent this transaction { - CAmount nValueOut = tx->GetValueOut(); + CAmount nValueOut = wtx.tx->GetValueOut(); nFee = nDebit - nValueOut; } - LOCK(pwallet->cs_wallet); + LOCK(wallet.cs_wallet); // Sent/received. - for (unsigned int i = 0; i < tx->vout.size(); ++i) + for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { - const CTxOut& txout = tx->vout[i]; - isminetype fIsMine = pwallet->IsMine(txout); + const CTxOut& txout = wtx.tx->vout[i]; + isminetype fIsMine = wallet.IsMine(txout); // Only need to handle txouts if AT LEAST one of these is true: // 1) they debit from us (sent) // 2) the output is to us (received) if (nDebit > 0) { // Don't report 'change' txouts - if (pwallet->IsChange(txout)) + if (OutputIsChange(wallet, txout)) continue; } else if (!(fIsMine & filter)) @@ -253,8 +251,8 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) { - pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", - this->GetHash().ToString()); + wallet.WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", + wtx.GetHash().ToString()); address = CNoDestination(); } @@ -271,16 +269,21 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, } -bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const +bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) +{ + return (CachedTxGetDebit(wallet, wtx, filter) > 0); +} + +bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); // Quick answer in most cases - if (!chain().checkFinalTx(*wtx.tx)) return false; - int nDepth = wtx.GetDepthInMainChain(); + if (!wallet.chain().checkFinalTx(*wtx.tx)) return false; + int nDepth = wallet.GetTxDepthInMainChain(wtx); if (nDepth >= 1) return true; if (nDepth < 0) return false; // using wtx's cached debit - if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) return false; + if (!wallet.m_spend_zero_conf_change || !CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)) return false; // Don't trust unconfirmed transactions from us unless they are in the mempool. if (!wtx.InMempool()) return false; @@ -289,41 +292,41 @@ bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents for (const CTxIn& txin : wtx.tx->vin) { // Transactions not sent by us: not trusted - const CWalletTx* parent = GetWalletTx(txin.prevout.hash); + const CWalletTx* parent = wallet.GetWalletTx(txin.prevout.hash); if (parent == nullptr) return false; const CTxOut& parentOut = parent->tx->vout[txin.prevout.n]; // Check that this specific input being spent is trusted - if (IsMine(parentOut) != ISMINE_SPENDABLE) return false; + if (wallet.IsMine(parentOut) != ISMINE_SPENDABLE) return false; // If we've already trusted this parent, continue if (trusted_parents.count(parent->GetHash())) continue; // Recurse to check that the parent is also trusted - if (!IsTrusted(*parent, trusted_parents)) return false; + if (!CachedTxIsTrusted(wallet, *parent, trusted_parents)) return false; trusted_parents.insert(parent->GetHash()); } return true; } -bool CWalletTx::IsTrusted() const +bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx) { std::set<uint256> trusted_parents; - LOCK(pwallet->cs_wallet); - return pwallet->IsTrusted(*this, trusted_parents); + LOCK(wallet.cs_wallet); + return CachedTxIsTrusted(wallet, wtx, trusted_parents); } -CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const +Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse) { Balance ret; isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); std::set<uint256> trusted_parents; - for (const auto& entry : mapWallet) + for (const auto& entry : wallet.mapWallet) { const CWalletTx& wtx = entry.second; - const bool is_trusted{IsTrusted(wtx, trusted_parents)}; - const int tx_depth{wtx.GetDepthInMainChain()}; - const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; - const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; + const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)}; + const int tx_depth{wallet.GetTxDepthInMainChain(wtx)}; + const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; + const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; if (is_trusted && tx_depth >= min_depth) { ret.m_mine_trusted += tx_credit_mine; ret.m_watchonly_trusted += tx_credit_watchonly; @@ -332,43 +335,43 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons ret.m_mine_untrusted_pending += tx_credit_mine; ret.m_watchonly_untrusted_pending += tx_credit_watchonly; } - ret.m_mine_immature += wtx.GetImmatureCredit(); - ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(); + ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx); + ret.m_watchonly_immature += CachedTxGetImmatureWatchOnlyCredit(wallet, wtx); } } return ret; } -std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const +std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet) { std::map<CTxDestination, CAmount> balances; { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); std::set<uint256> trusted_parents; - for (const auto& walletEntry : mapWallet) + for (const auto& walletEntry : wallet.mapWallet) { const CWalletTx& wtx = walletEntry.second; - if (!IsTrusted(wtx, trusted_parents)) + if (!CachedTxIsTrusted(wallet, wtx, trusted_parents)) continue; - if (wtx.IsImmatureCoinBase()) + if (wallet.IsTxImmatureCoinBase(wtx)) continue; - int nDepth = wtx.GetDepthInMainChain(); - if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) + int nDepth = wallet.GetTxDepthInMainChain(wtx); + if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1)) continue; for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { CTxDestination addr; - if (!IsMine(wtx.tx->vout[i])) + if (!wallet.IsMine(wtx.tx->vout[i])) continue; if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; + CAmount n = wallet.IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; balances[addr] += n; } } @@ -377,13 +380,13 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const return balances; } -std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const +std::set< std::set<CTxDestination> > GetAddressGroupings(const CWallet& wallet) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); std::set< std::set<CTxDestination> > groupings; std::set<CTxDestination> grouping; - for (const auto& walletEntry : mapWallet) + for (const auto& walletEntry : wallet.mapWallet) { const CWalletTx& wtx = walletEntry.second; @@ -394,9 +397,9 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const for (const CTxIn& txin : wtx.tx->vin) { CTxDestination address; - if(!IsMine(txin)) /* If this input isn't mine, ignore it */ + if(!InputIsMine(wallet, txin)) /* If this input isn't mine, ignore it */ continue; - if(!ExtractDestination(mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address)) + if(!ExtractDestination(wallet.mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address)) continue; grouping.insert(address); any_mine = true; @@ -406,7 +409,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const if (any_mine) { for (const CTxOut& txout : wtx.tx->vout) - if (IsChange(txout)) + if (OutputIsChange(wallet, txout)) { CTxDestination txoutAddr; if(!ExtractDestination(txout.scriptPubKey, txoutAddr)) @@ -423,7 +426,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const // group lone addrs by themselves for (const auto& txout : wtx.tx->vout) - if (IsMine(txout)) + if (wallet.IsMine(txout)) { CTxDestination address; if(!ExtractDestination(txout.scriptPubKey, address)) diff --git a/src/wallet/receive.h b/src/wallet/receive.h index 8eead32413..b4b311636b 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -10,11 +10,55 @@ #include <wallet/transaction.h> #include <wallet/wallet.h> +isminetype InputIsMine(const CWallet& wallet, const CTxIn& txin) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +/** Returns whether all of the inputs match the filter */ +bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter); + +CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter); +CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter); + +bool ScriptIsChange(const CWallet& wallet, const CScript& script) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx); + +CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); +//! filter decides which addresses will count towards the debit +CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); +CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx); +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true); +CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache = true); +// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct +// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The +// annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid +// having to resolve the issue of member access into incomplete type CWallet. +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) NO_THREAD_SAFETY_ANALYSIS; struct COutputEntry { CTxDestination destination; CAmount amount; int vout; }; +void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, + std::list<COutputEntry>& listReceived, + std::list<COutputEntry>& listSent, + CAmount& nFee, const isminefilter& filter); +bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); +bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx); + +struct Balance { + CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more + CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) + CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain + CAmount m_watchonly_trusted{0}; + CAmount m_watchonly_untrusted_pending{0}; + CAmount m_watchonly_immature{0}; +}; +Balance GetBalance(const CWallet& wallet, int min_depth = 0, bool avoid_reuse = true); + +std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet); +std::set<std::set<CTxDestination>> GetAddressGroupings(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); #endif // BITCOIN_WALLET_RECEIVE_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 916f811f9b..ff9e10c5ad 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -31,7 +31,9 @@ #include <wallet/context.h> #include <wallet/feebumper.h> #include <wallet/load.h> +#include <wallet/receive.h> #include <wallet/rpcwallet.h> +#include <wallet/spend.h> #include <wallet/wallet.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> @@ -147,9 +149,10 @@ LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_cr return *spk_man; } -static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniValue& entry) +static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry) { - int confirms = wtx.GetDepthInMainChain(); + interfaces::Chain& chain = wallet.chain(); + int confirms = wallet.GetTxDepthInMainChain(wtx); entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) entry.pushKV("generated", true); @@ -162,12 +165,12 @@ static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniVa CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time))); entry.pushKV("blocktime", block_time); } else { - entry.pushKV("trusted", wtx.IsTrusted()); + entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx)); } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); UniValue conflicts(UniValue::VARR); - for (const uint256& conflict : wtx.GetConflicts()) + for (const uint256& conflict : wallet.GetTxConflicts(wtx)) conflicts.push_back(conflict.GetHex()); entry.pushKV("walletconflicts", conflicts); entry.pushKV("time", wtx.GetTxTime()); @@ -423,7 +426,7 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto bilingual_str error; CTransactionRef tx; FeeCalculation fee_calc_out; - const bool fCreated = wallet.CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); + const bool fCreated = CreateTransaction(wallet, recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); if (!fCreated) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); } @@ -576,8 +579,8 @@ static RPCHelpMan listaddressgroupings() LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); - for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) { + std::map<CTxDestination, CAmount> balances = GetAddressBalances(*pwallet); + for (const std::set<CTxDestination>& grouping : GetAddressGroupings(*pwallet)) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination& address : grouping) { @@ -686,7 +689,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b CAmount amount = 0; for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) { const CWalletTx& wtx = wtx_pair.second; - if (wtx.IsCoinBase() || !wallet.chain().checkFinalTx(*wtx.tx) || wtx.GetDepthInMainChain() < min_depth) { + if (wtx.IsCoinBase() || !wallet.chain().checkFinalTx(*wtx.tx) || wallet.GetTxDepthInMainChain(wtx) < min_depth) { continue; } @@ -826,7 +829,7 @@ static RPCHelpMan getbalance() bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]); - const auto bal = pwallet->GetBalance(min_depth, avoid_reuse); + const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse); return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); }, @@ -851,7 +854,7 @@ static RPCHelpMan getunconfirmedbalance() LOCK(pwallet->cs_wallet); - return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending); + return ValueFromAmount(GetBalance(*pwallet).m_mine_untrusted_pending); }, }; } @@ -1085,7 +1088,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, bool continue; } - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wallet.GetTxDepthInMainChain(wtx); if (nDepth < nMinDepth) continue; @@ -1310,9 +1313,9 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM std::list<COutputEntry> listReceived; std::list<COutputEntry> listSent; - wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine); + CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine); - bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); + bool involvesWatchonly = CachedTxIsFromMe(wallet, wtx, ISMINE_WATCH_ONLY); // Sent if (!filter_label) @@ -1333,14 +1336,14 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) - WalletTxToJSON(wallet.chain(), wtx, entry); + WalletTxToJSON(wallet, wtx, entry); entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } } // Received - if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) { + if (listReceived.size() > 0 && wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) { for (const COutputEntry& r : listReceived) { std::string label; @@ -1358,9 +1361,9 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { - if (wtx.GetDepthInMainChain() < 1) + if (wallet.GetTxDepthInMainChain(wtx) < 1) entry.pushKV("category", "orphan"); - else if (wtx.IsImmatureCoinBase()) + else if (wallet.IsTxImmatureCoinBase(wtx)) entry.pushKV("category", "immature"); else entry.pushKV("category", "generate"); @@ -1375,7 +1378,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM } entry.pushKV("vout", r.vout); if (fLong) - WalletTxToJSON(wallet.chain(), wtx, entry); + WalletTxToJSON(wallet, wtx, entry); ret.push_back(entry); } } @@ -1615,7 +1618,7 @@ static RPCHelpMan listsinceblock() for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) { const CWalletTx& tx = pairWtx.second; - if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) { + if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) { ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } @@ -1736,16 +1739,16 @@ static RPCHelpMan gettransaction() } const CWalletTx& wtx = it->second; - CAmount nCredit = wtx.GetCredit(filter); - CAmount nDebit = wtx.GetDebit(filter); + CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, filter); + CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, filter); CAmount nNet = nCredit - nDebit; - CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); + CAmount nFee = (CachedTxIsFromMe(*pwallet, wtx, filter) ? wtx.tx->GetValueOut() - nDebit : 0); entry.pushKV("amount", ValueFromAmount(nNet - nFee)); - if (wtx.IsFromMe(filter)) + if (CachedTxIsFromMe(*pwallet, wtx, filter)) entry.pushKV("fee", ValueFromAmount(nFee)); - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(*pwallet, wtx, entry); UniValue details(UniValue::VARR); ListTransactions(*pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); @@ -2384,7 +2387,7 @@ static RPCHelpMan getbalances() LOCK(wallet.cs_wallet); - const auto bal = wallet.GetBalance(); + const auto bal = GetBalance(wallet); UniValue balances{UniValue::VOBJ}; { UniValue balances_mine{UniValue::VOBJ}; @@ -2394,7 +2397,7 @@ static RPCHelpMan getbalances() if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { // If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get // the total balance, and then subtract bal to get the reused address balance. - const auto full_bal = wallet.GetBalance(0, false); + const auto full_bal = GetBalance(wallet, 0, false); balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending)); } balances.pushKV("mine", balances_mine); @@ -2462,7 +2465,7 @@ static RPCHelpMan getwalletinfo() UniValue obj(UniValue::VOBJ); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); - const auto bal = pwallet->GetBalance(); + const auto bal = GetBalance(*pwallet); int64_t kp_oldest = pwallet->GetOldestKeyPoolTime(); obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); @@ -3058,7 +3061,7 @@ static RPCHelpMan listunspent() cctl.m_max_depth = nMaxDepth; cctl.m_include_unsafe_inputs = include_unsafe; LOCK(pwallet->cs_wallet); - pwallet->AvailableCoins(vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); + AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); } LOCK(pwallet->cs_wallet); @@ -3274,7 +3277,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, bilingual_str error; - if (!wallet.FundTransaction(tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + if (!FundTransaction(wallet, tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, error.original); } } @@ -3959,7 +3962,7 @@ RPCHelpMan getaddressinfo() UniValue detail = DescribeWalletAddress(*pwallet, dest); ret.pushKVs(detail); - ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); + ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey)); ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey); if (spk_man) { diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 928335da2b..4a7a268982 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -21,6 +21,11 @@ using interfaces::FoundBlock; static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100}; +int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig) +{ + return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig); +} + std::string COutput::ToString() const { return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); @@ -64,33 +69,33 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig); } -void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const +void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); vCoins.clear(); CAmount nTotal = 0; // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses - bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); + bool allow_used_addresses = !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH}; const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH}; const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true}; std::set<uint256> trusted_parents; - for (const auto& entry : mapWallet) + for (const auto& entry : wallet.mapWallet) { const uint256& wtxid = entry.first; const CWalletTx& wtx = entry.second; - if (!chain().checkFinalTx(*wtx.tx)) { + if (!wallet.chain().checkFinalTx(*wtx.tx)) { continue; } - if (wtx.IsImmatureCoinBase()) + if (wallet.IsTxImmatureCoinBase(wtx)) continue; - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wallet.GetTxDepthInMainChain(wtx); if (nDepth < 0) continue; @@ -99,7 +104,7 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* c if (nDepth == 0 && !wtx.InMempool()) continue; - bool safeTx = IsTrusted(wtx, trusted_parents); + bool safeTx = CachedTxIsTrusted(wallet, wtx, trusted_parents); // We should not consider coins from transactions that are replacing // other transactions. @@ -152,28 +157,28 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* c if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) continue; - if (IsLockedCoin(entry.first, i)) + if (wallet.IsLockedCoin(entry.first, i)) continue; - if (IsSpent(wtxid, i)) + if (wallet.IsSpent(wtxid, i)) continue; - isminetype mine = IsMine(wtx.tx->vout[i]); + isminetype mine = wallet.IsMine(wtx.tx->vout[i]); if (mine == ISMINE_NO) { continue; } - if (!allow_used_addresses && IsSpentKey(wtxid, i)) { + if (!allow_used_addresses && wallet.IsSpentKey(wtxid, i)) { continue; } - std::unique_ptr<SigningProvider> provider = GetSolvingProvider(wtx.tx->vout[i].scriptPubKey); + std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(wtx.tx->vout[i].scriptPubKey); bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); + vCoins.push_back(COutput(wallet, wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { @@ -192,13 +197,13 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* c } } -CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const +CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl) { - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); CAmount balance = 0; std::vector<COutput> vCoins; - AvailableCoins(vCoins, coinControl); + AvailableCoins(wallet, vCoins, coinControl); for (const COutput& out : vCoins) { if (out.fSpendable) { balance += out.tx->tx->vout[out.i].nValue; @@ -207,16 +212,16 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const return balance; } -const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const +const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); const CTransaction* ptx = &tx; int n = output; - while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { + while (OutputIsChange(wallet, ptx->vout[n]) && ptx->vin.size() > 0) { const COutPoint& prevout = ptx->vin[0].prevout; - auto it = mapWallet.find(prevout.hash); - if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n || - !IsMine(it->second.tx->vout[prevout.n])) { + auto it = wallet.mapWallet.find(prevout.hash); + if (it == wallet.mapWallet.end() || it->second.tx->vout.size() <= prevout.n || + !wallet.IsMine(it->second.tx->vout[prevout.n])) { break; } ptx = it->second.tx.get(); @@ -225,39 +230,39 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out return ptx->vout[n]; } -std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const +std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; - AvailableCoins(availableCoins); + AvailableCoins(wallet, availableCoins); for (const COutput& coin : availableCoins) { CTxDestination address; - if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && - ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { + if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && + ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) { result[address].emplace_back(std::move(coin)); } } std::vector<COutPoint> lockedCoins; - ListLockedCoins(lockedCoins); + wallet.ListLockedCoins(lockedCoins); // Include watch-only for LegacyScriptPubKeyMan wallets without private keys - const bool include_watch_only = GetLegacyScriptPubKeyMan() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + const bool include_watch_only = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; for (const COutPoint& output : lockedCoins) { - auto it = mapWallet.find(output.hash); - if (it != mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + auto it = wallet.mapWallet.find(output.hash); + if (it != wallet.mapWallet.end()) { + int depth = wallet.GetTxDepthInMainChain(it->second); if (depth >= 0 && output.n < it->second.tx->vout.size() && - IsMine(it->second.tx->vout[output.n]) == is_mine_filter + wallet.IsMine(it->second.tx->vout[output.n]) == is_mine_filter ) { CTxDestination address; - if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { + if (ExtractDestination(FindNonChangeParentOutput(wallet, *it->second.tx, output.n).scriptPubKey, address)) { result[address].emplace_back( - &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + wallet, it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); } } } @@ -266,7 +271,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const return result; } -std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only) const +std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only) { std::vector<OutputGroup> groups_out; @@ -277,12 +282,12 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu if (!output.fSpendable) continue; size_t ancestors, descendants; - chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); + wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); CInputCoin input_coin = output.GetInputCoin(); // Make an OutputGroup containing just this output OutputGroup group{coin_sel_params}; - group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); + group.Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only); // Check the OutputGroup's eligibility. Only add the eligible ones. if (positive_only && group.GetSelectionAmount() <= 0) continue; @@ -303,7 +308,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu if (!output.fSpendable) continue; size_t ancestors, descendants; - chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); + wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); CInputCoin input_coin = output.GetInputCoin(); CScript spk = input_coin.txout.scriptPubKey; @@ -327,7 +332,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu } // Add the input_coin to group - group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); + group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only); } // Now we go through the entire map and pull out the OutputGroups @@ -352,8 +357,8 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu return groups_out; } -bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, - std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const +bool AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, + std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) { setCoinsRet.clear(); nValueRet = 0; @@ -363,7 +368,7 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit std::vector<std::tuple<CAmount, std::set<CInputCoin>, CAmount>> results; // Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output. - std::vector<OutputGroup> positive_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, true /* positive_only */); + std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */); std::set<CInputCoin> bnb_coins; CAmount bnb_value; if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, bnb_coins, bnb_value)) { @@ -372,7 +377,7 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit } // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here. - std::vector<OutputGroup> all_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, false /* positive_only */); + std::vector<OutputGroup> all_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */); // While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output. // So we need to include that for KnapsackSolver as well, as we are expecting to create a change output. std::set<CInputCoin> knapsack_coins; @@ -397,7 +402,7 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit return true; } -bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) const +bool SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) { std::vector<COutput> vCoins(vAvailableCoins); CAmount value_to_select = nTargetValue; @@ -423,8 +428,8 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm coin_control.ListSelected(vPresetInputs); for (const COutPoint& outpoint : vPresetInputs) { - std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); - if (it != mapWallet.end()) + std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash); + if (it != wallet.mapWallet.end()) { const CWalletTx& wtx = it->second; // Clearly invalid input, fail @@ -432,7 +437,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm return false; } // Just to calculate the marginal byte size - CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false)); + CInputCoin coin(wtx.tx, outpoint.n, GetTxSpendSize(wallet, wtx, outpoint.n, false)); nValueFromPresetInputs += coin.txout.nValue; if (coin.m_input_bytes <= 0) { return false; // Not solvable, can't estimate size for fee @@ -460,7 +465,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm unsigned int limit_ancestor_count = 0; unsigned int limit_descendant_count = 0; - chain().getPackageLimits(limit_ancestor_count, limit_descendant_count); + wallet.chain().getPackageLimits(limit_ancestor_count, limit_descendant_count); const size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count); const size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count); const bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); @@ -483,32 +488,32 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six // confirmations on outputs received from other wallets and only spend confirmed change. - if (AttemptSelection(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; - if (AttemptSelection(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; + if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; + if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; // Fall back to using zero confirmation change (but with as few ancestors in the mempool as // possible) if we cannot fund the transaction otherwise. - if (m_spend_zero_conf_change) { - if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; - if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), + if (wallet.m_spend_zero_conf_change) { + if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; + if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params)) { return true; } - if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), + if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) { return true; } // If partial groups are allowed, relax the requirement of spending OutputGroups (groups // of UTXOs sent to the same address, which are obviously controlled by a single wallet) // in their entirety. - if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), + if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params)) { return true; } // Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs // received from other wallets. if (coin_control.m_include_unsafe_inputs - && AttemptSelection(value_to_select, + && AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params)) { return true; @@ -516,7 +521,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm // Try with unlimited ancestors/descendants. The transaction will still need to meet // mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but // OutputGroups use heuristics that may overestimate ancestor/descendant counts. - if (!fRejectLongChains && AttemptSelection(value_to_select, + if (!fRejectLongChains && AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params)) { return true; @@ -595,7 +600,8 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin return locktime; } -bool CWallet::CreateTransactionInternal( +static bool CreateTransactionInternal( + CWallet& wallet, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, @@ -603,22 +609,22 @@ bool CWallet::CreateTransactionInternal( bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, - bool sign) + bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { - AssertLockHeld(cs_wallet); + AssertLockHeld(wallet.cs_wallet); CMutableTransaction txNew; // The resulting transaction that we make - txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight()); + txNew.nLockTime = GetLocktimeForNewTransaction(wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight()); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends; // Set the long term feerate estimate to the wallet's consolidate feerate - coin_selection_params.m_long_term_feerate = m_consolidate_feerate; + coin_selection_params.m_long_term_feerate = wallet.m_consolidate_feerate; CAmount recipients_sum = 0; - const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); - ReserveDestination reservedest(this, change_type); + const OutputType change_type = wallet.TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type, vecSend); + ReserveDestination reservedest(&wallet, change_type); unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from for (const auto& recipient : vecSend) { recipients_sum += recipient.nAmount; @@ -662,7 +668,7 @@ bool CWallet::CreateTransactionInternal( coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); // Get size of spending the change output - int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this); + int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, &wallet); // If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh // as lower-bound to allow BnB to do it's thing if (change_spend_size == -1) { @@ -672,18 +678,18 @@ bool CWallet::CreateTransactionInternal( } // Set discard feerate - coin_selection_params.m_discard_feerate = GetDiscardRate(*this); + coin_selection_params.m_discard_feerate = GetDiscardRate(wallet); // Get the fee rate to use effective values in coin selection FeeCalculation feeCalc; - coin_selection_params.m_effective_feerate = GetMinimumFeeRate(*this, coin_control, &feeCalc); + coin_selection_params.m_effective_feerate = GetMinimumFeeRate(wallet, coin_control, &feeCalc); // Do not, ever, assume that it's fine to change the fee rate if the user has explicitly // provided one if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) { error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB)); return false; } - if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { + if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) { // eventually allow a fallback fee error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); return false; @@ -710,7 +716,7 @@ bool CWallet::CreateTransactionInternal( coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); } - if (IsDust(txout, chain().relayDustFee())) + if (IsDust(txout, wallet.chain().relayDustFee())) { error = _("Transaction amount too small"); return false; @@ -724,12 +730,12 @@ bool CWallet::CreateTransactionInternal( // Get available coins std::vector<COutput> vAvailableCoins; - AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); + AvailableCoins(wallet, vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); // Choose coins to use CAmount inputs_sum = 0; std::set<CInputCoin> setCoins; - if (!SelectCoins(vAvailableCoins, /* nTargetValue */ selection_target, setCoins, inputs_sum, coin_control, coin_selection_params)) + if (!SelectCoins(wallet, vAvailableCoins, /* nTargetValue */ selection_target, setCoins, inputs_sum, coin_control, coin_selection_params)) { error = _("Insufficient funds"); return false; @@ -767,13 +773,13 @@ bool CWallet::CreateTransactionInternal( // to avoid conflicting with other possible uses of nSequence, // and in the spirit of "smallest possible change from prior // behavior." - const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1); + const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1); for (const auto& coin : selected_coins) { txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); } // Calculate the transaction fee - TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly); int nBytes = tx_sizes.vsize; if (nBytes < 0) { error = _("Signing transaction failed"); @@ -798,7 +804,7 @@ bool CWallet::CreateTransactionInternal( txNew.vout.erase(change_position); // Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those - tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly); nBytes = tx_sizes.vsize; fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes); } @@ -835,7 +841,7 @@ bool CWallet::CreateTransactionInternal( } // Error if this output is reduced to be below dust - if (IsDust(txout, chain().relayDustFee())) { + if (IsDust(txout, wallet.chain().relayDustFee())) { if (txout.nValue < 0) { error = _("The transaction amount is too small to pay the fee"); } else { @@ -854,7 +860,7 @@ bool CWallet::CreateTransactionInternal( return false; } - if (sign && !SignTransaction(txNew)) { + if (sign && !wallet.SignTransaction(txNew)) { error = _("Signing transaction failed"); return false; } @@ -870,14 +876,14 @@ bool CWallet::CreateTransactionInternal( return false; } - if (nFeeRet > m_default_max_tx_fee) { + if (nFeeRet > wallet.m_default_max_tx_fee) { error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); return false; } if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits - if (!chain().checkChainLimits(tx)) { + if (!wallet.chain().checkChainLimits(tx)) { error = _("Transaction has too long of a mempool chain"); return false; } @@ -888,7 +894,7 @@ bool CWallet::CreateTransactionInternal( reservedest.KeepDestination(); fee_calc_out = feeCalc; - WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", + wallet.WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, feeCalc.est.pass.start, feeCalc.est.pass.end, (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0, @@ -899,7 +905,8 @@ bool CWallet::CreateTransactionInternal( return true; } -bool CWallet::CreateTransaction( +bool CreateTransaction( + CWallet& wallet, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, @@ -919,23 +926,23 @@ bool CWallet::CreateTransaction( return false; } - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); int nChangePosIn = nChangePosInOut; Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr) - bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign); + bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign); // try with avoidpartialspends unless it's enabled already - if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { + if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { CCoinControl tmp_cc = coin_control; tmp_cc.m_avoid_partial_spends = true; CAmount nFeeRet2; CTransactionRef tx2; int nChangePosInOut2 = nChangePosIn; bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results - if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) { + if (CreateTransactionInternal(wallet, vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) { // if fee of this alternative one is within the range of the max fee, we use this one - const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee; - WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); + const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee; + wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); if (use_aps) { tx = tx2; nFeeRet = nFeeRet2; @@ -946,7 +953,7 @@ bool CWallet::CreateTransaction( return res; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) +bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) { std::vector<CRecipient> vecSend; @@ -965,11 +972,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC // Acquire the locks to prevent races to the new locked unspents between the // CreateTransaction call and LockCoin calls (when lockUnspents is true). - LOCK(cs_wallet); + LOCK(wallet.cs_wallet); CTransactionRef tx_new; FeeCalculation fee_calc_out; - if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) { + if (!CreateTransaction(wallet, vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) { return false; } @@ -990,7 +997,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC } if (lockUnspents) { - LockCoin(txin.prevout); + wallet.LockCoin(txin.prevout); } } diff --git a/src/wallet/spend.h b/src/wallet/spend.h index 03f9a7c2b5..e39f134dc3 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -9,6 +9,9 @@ #include <wallet/transaction.h> #include <wallet/wallet.h> +/** Get the marginal bytes if spending the specified output from this transaction */ +int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false); + class COutput { public: @@ -43,13 +46,13 @@ public: */ bool fSafe; - COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) + COutput(const CWallet& wallet, const CWalletTx& wtx, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) { - tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; + tx = &wtx; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; // If known and signable by the given wallet, compute nInputBytes // Failure will keep this value -1 - if (fSpendable && tx) { - nInputBytes = tx->GetSpendSize(i, use_max_sig); + if (fSpendable) { + nInputBytes = GetTxSpendSize(wallet, wtx, i, use_max_sig); } } @@ -61,4 +64,76 @@ public: } }; +//Get the marginal bytes of spending the specified output +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false); + +struct TxSize { + int64_t vsize{-1}; + int64_t weight{-1}; +}; + +/** Calculate the size of the transaction assuming all signatures are max size +* Use DummySignatureCreator, which inserts 71 byte signatures everywhere. +* NOTE: this requires that all inputs must be in mapWallet (eg the tx should +* be AllInputsMine). */ +TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); +TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); + +/** + * populate vCoins with vector of available COutputs. + */ +void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr); + +/** + * Find non-change parent output. + */ +const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +/** + * Return list of available coins and locked coins grouped by non-change output address. + */ +std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only); + +/** + * Shuffle and select coins until nTargetValue is reached while avoiding + * small change; This method is stochastic for some inputs and upon + * completion the coin set and corresponding actual target value is + * assembled + * param@[in] coins Set of UTXOs to consider. These will be categorized into + * OutputGroups and filtered using eligibility_filter before + * selecting coins. + * param@[out] setCoinsRet Populated with the coins selected if successful. + * param@[out] nValueRet Used to return the total value of selected coins. + */ +bool AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, + std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params); + +/** + * Select a set of coins such that nValueRet >= nTargetValue and at least + * all coins from coin_control are selected; never select unconfirmed coins if they are not ours + * param@[out] setCoinsRet Populated with inputs including pre-selected inputs from + * coin_control and Coin Selection if successful. + * param@[out] nValueRet Total value of selected coins including pre-selected ones + * from coin_control and Coin Selection if successful. + */ +bool SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, + const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +/** + * Create a new transaction paying the recipients with a set of coins + * selected by SelectCoins(); Also create the change output, when needed + * @note passing nChangePosInOut as -1 will result in setting a random position + */ +bool CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true); + +/** + * Insert additional inputs into the transaction by + * calling CreateTransaction(); + */ +bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); + #endif // BITCOIN_WALLET_SPEND_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 7b2169a5b6..5d51809241 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -10,6 +10,7 @@ #include <util/translation.h> #include <wallet/coincontrol.h> #include <wallet/coinselection.h> +#include <wallet/spend.h> #include <wallet/test/wallet_test_fixture.h> #include <wallet/wallet.h> @@ -39,7 +40,7 @@ CoinEligibilityFilter filter_standard_extra(6, 6, 0); CoinSelectionParams coin_selection_params(/* change_output_size= */ 0, /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0), /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), - /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); + /* tx_noinputs_size= */ 0, /* avoid_partial= */ false); static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set) { @@ -87,7 +88,7 @@ static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bo wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); wtx->m_is_cache_empty = false; } - COutput output(wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); + COutput output(wallet, *wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); vCoins.push_back(output); } static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) @@ -144,7 +145,7 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins) inline std::vector<OutputGroup>& KnapsackGroupOutputs(const CoinEligibilityFilter& filter) { static std::vector<OutputGroup> static_groups; - static_groups = testWallet.GroupOutputs(vCoins, coin_selection_params, filter, /* positive_only */false); + static_groups = GroupOutputs(testWallet, vCoins, coin_selection_params, filter, /* positive_only */false); return static_groups; } @@ -286,7 +287,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) CoinSelectionParams coin_selection_params_bnb(/* change_output_size= */ 0, /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000), /* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000), - /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); + /* tx_noinputs_size= */ 0, /* avoid_partial= */ false); CoinSet setCoinsRet; CAmount nValueRet; empty_wallet(); @@ -316,7 +317,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) coin_control.fAllowOtherInputs = true; coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i)); coin_selection_params_bnb.m_effective_feerate = CFeeRate(0); - BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb)); + BOOST_CHECK(SelectCoins(*wallet, vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb)); } } @@ -653,11 +654,11 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) CoinSelectionParams cs_params(/* change_output_size= */ 34, /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), - /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); + /* tx_noinputs_size= */ 0, /* avoid_partial= */ false); CoinSet out_set; CAmount out_value = 0; CCoinControl cc; - BOOST_CHECK(testWallet.SelectCoins(vCoins, target, out_set, out_value, cc, cs_params)); + BOOST_CHECK(SelectCoins(testWallet, vCoins, target, out_set, out_value, cc, cs_params)); BOOST_CHECK_GE(out_value, target); } } diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index 1cefa386b7..8a97f7779d 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -22,12 +22,12 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION); CTransactionRef prev_tx1; s_prev_tx1 >> prev_tx1; - m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(&m_wallet, prev_tx1)); + m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(prev_tx1)); CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION); CTransactionRef prev_tx2; s_prev_tx2 >> prev_tx2; - m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(&m_wallet, prev_tx2)); + m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(prev_tx2)); // Add scripts CScript rs1; diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp index 8821f680b3..e779b2450f 100644 --- a/src/wallet/test/spend_tests.cpp +++ b/src/wallet/test/spend_tests.cpp @@ -5,6 +5,7 @@ #include <policy/fees.h> #include <validation.h> #include <wallet/coincontrol.h> +#include <wallet/spend.h> #include <wallet/test/util.h> #include <wallet/test/wallet_test_fixture.h> @@ -32,7 +33,7 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup) coin_control.m_feerate.emplace(10000); coin_control.fOverrideFeeRate = true; FeeCalculation fee_calc; - BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, change_pos, error, coin_control, fee_calc)); + BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, change_pos, error, coin_control, fee_calc)); BOOST_CHECK_EQUAL(tx->vout.size(), 1); BOOST_CHECK_EQUAL(tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - fee); BOOST_CHECK_GT(fee, 0); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index bc5498c88e..5431a38bee 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -21,6 +21,8 @@ #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/context.h> +#include <wallet/receive.h> +#include <wallet/spend.h> #include <wallet/test/util.h> #include <wallet/test/wallet_test_fixture.h> @@ -103,7 +105,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); - BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); + BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0); } // Verify ScanForWalletTransactions picks up transactions in both the old @@ -122,7 +124,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); - BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 100 * COIN); + BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN); } // Prune the older block file. @@ -148,7 +150,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); - BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN); + BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 50 * COIN); } // Prune the remaining block file. @@ -173,7 +175,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); - BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); + BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0); } } @@ -233,7 +235,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) "downloading and rescanning the relevant blocks (see -reindex and -rescan " "options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); - RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt); + RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt); } } @@ -278,7 +280,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) request.params.push_back(backup_file); ::dumpwallet().HandleRequest(request); - RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt); + RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt); } // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME @@ -297,7 +299,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) AddWallet(context, wallet); wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); ::importwallet().HandleRequest(request); - RemoveWallet(context, wallet, /* load_on_startup= */ std::nullopt); + RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt); BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U); BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U); @@ -319,7 +321,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan(); - CWalletTx wtx(&wallet, m_coinbase_txns.back()); + CWalletTx wtx(m_coinbase_txns.back()); LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore); wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); @@ -329,13 +331,13 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0); + BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 0); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN); + BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 50*COIN); } static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) @@ -506,7 +508,7 @@ public: CCoinControl dummy; FeeCalculation fee_calc_out; { - BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy, fee_calc_out)); + BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, changePos, error, dummy, fee_calc_out)); } wallet->CommitTransaction(tx, {}, {}); CMutableTransaction blocktx; @@ -528,7 +530,7 @@ public: std::unique_ptr<CWallet> wallet; }; -BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) +BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) { std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString(); @@ -537,14 +539,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) std::map<CTxDestination, std::vector<COutput>> list; { LOCK(wallet->cs_wallet); - list = wallet->ListCoins(); + list = ListCoins(*wallet); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U); // Check initial balance from one mature coinbase transaction. - BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance()); + BOOST_CHECK_EQUAL(50 * COIN, GetAvailableBalance(*wallet)); // Add a transaction creating a change address, and confirm ListCoins still // returns the coin associated with the change address underneath the @@ -553,7 +555,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { LOCK(wallet->cs_wallet); - list = wallet->ListCoins(); + list = ListCoins(*wallet); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); @@ -563,7 +565,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { LOCK(wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(available); + AvailableCoins(*wallet, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto& group : list) { @@ -575,14 +577,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { LOCK(wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(available); + AvailableCoins(*wallet, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { LOCK(wallet->cs_wallet); - list = wallet->ListCoins(); + list = ListCoins(*wallet); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 131faefe0b..094221adf2 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -17,12 +17,8 @@ #include <list> #include <vector> -struct COutputEntry; - typedef std::map<std::string, std::string> mapValue_t; -//Get the marginal bytes of spending the specified output -int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false); static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue) { @@ -34,6 +30,7 @@ static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue) nOrderPos = atoi64(mapValue["n"]); } + static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue) { if (nOrderPos == -1) @@ -68,8 +65,6 @@ public: class CWalletTx { private: - const CWallet* const pwallet; - /** Constant used in hashBlock to indicate tx has been abandoned, only used at * serialization/deserialization to avoid ambiguity with conflicted. */ @@ -126,7 +121,6 @@ public: // memory only enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; - CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false) const; mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; /** * This flag is true if all m_amounts caches are empty. This is particularly @@ -139,9 +133,8 @@ public: mutable bool fInMempool; mutable CAmount nChangeCached; - CWalletTx(const CWallet* wallet, CTransactionRef arg) - : pwallet(wallet), - tx(std::move(arg)) + CWalletTx(CTransactionRef arg) + : tx(std::move(arg)) { Init(); } @@ -264,72 +257,13 @@ public: m_is_cache_empty = true; } - //! filter decides which addresses will count towards the debit - CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(const isminefilter& filter) const; - CAmount GetImmatureCredit(bool fUseCache = true) const; - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The - // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid - // having to resolve the issue of member access into incomplete type CWallet. - CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; - CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const; - CAmount GetChange() const; - - /** Get the marginal bytes if spending the specified output from this transaction */ - int GetSpendSize(unsigned int out, bool use_max_sig = false) const - { - return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig); - } - - void GetAmounts(std::list<COutputEntry>& listReceived, - std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const; - - bool IsFromMe(const isminefilter& filter) const - { - return (GetDebit(filter) > 0); - } - /** True if only scriptSigs are different */ bool IsEquivalentTo(const CWalletTx& tx) const; bool InMempool() const; - bool IsTrusted() const; int64_t GetTxTime() const; - /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ - bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay); - - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; - - /** - * Return depth of transaction in blockchain: - * <0 : conflicts with a transaction this deep in the blockchain - * 0 : in memory pool, waiting to be included in a block - * >=1 : this many blocks deep in the main chain - */ - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS; - bool IsInMainChain() const { return GetDepthInMainChain() > 0; } - - /** - * @return number of blocks to maturity for this transaction: - * 0 : is not a coinbase transaction, or is a mature coinbase transaction - * >0 : is a coinbase transaction which matures in this many blocks - */ - int GetBlocksToMaturity() const; bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } void setAbandoned() { @@ -346,7 +280,6 @@ public: void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } - bool IsImmatureCoinBase() const; // Disable copying of CWalletTx objects to prevent bugs where instances get // copied in and out of the mapWallet map, and fields are updated in the diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6f3dcf2afa..70349b2455 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -581,7 +581,7 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const const uint256& wtxid = it->second; std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { - int depth = mit->second.GetDepthInMainChain(); + int depth = GetTxDepthInMainChain(mit->second); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) return true; // Spent } @@ -900,7 +900,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio } // Inserts only if not already there, returns tx inserted or tx found - auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(this, tx)); + auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(tx)); CWalletTx& wtx = (*ret.first).second; bool fInsertedNew = ret.second; bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew); @@ -984,7 +984,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) { - const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(this, nullptr)); + const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(nullptr)); CWalletTx& wtx = ins.first->second; if (!fill_wtx(wtx, ins.second)) { return false; @@ -1074,7 +1074,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const { LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(hashTx); - return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); + return wtx && !wtx->isAbandoned() && GetTxDepthInMainChain(*wtx) == 0 && !wtx->InMempool(); } void CWallet::MarkInputsDirty(const CTransactionRef& tx) @@ -1100,7 +1100,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(hashTx); assert(it != mapWallet.end()); const CWalletTx& origtx = it->second; - if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) { + if (GetTxDepthInMainChain(origtx) != 0 || origtx.InMempool()) { return false; } @@ -1113,7 +1113,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = GetTxDepthInMainChain(wtx); // If the orig tx was not in block, none of its spends can be assert(currentconfirm <= 0); // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} @@ -1168,7 +1168,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = GetTxDepthInMainChain(wtx); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. @@ -1698,7 +1698,7 @@ void CWallet::ReacceptWalletTransactions() CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = GetTxDepthInMainChain(wtx); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -1709,24 +1709,24 @@ void CWallet::ReacceptWalletTransactions() for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); std::string unused_err_string; - wtx.SubmitMemoryPoolAndRelay(unused_err_string, false); + SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, false); } } -bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay) +bool CWallet::SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const { // Can't relay if wallet is not broadcasting - if (!pwallet->GetBroadcastTransactions()) return false; + if (!GetBroadcastTransactions()) return false; // Don't relay abandoned transactions - if (isAbandoned()) return false; + if (wtx.isAbandoned()) return false; // Don't try to submit coinbase transactions. These would fail anyway but would // cause log spam. - if (IsCoinBase()) return false; + if (wtx.IsCoinBase()) return false; // Don't try to submit conflicted or confirmed transactions. - if (GetDepthInMainChain() != 0) return false; + if (GetTxDepthInMainChain(wtx) != 0) return false; // Submit transaction to mempool for relay - pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString()); + WalletLogPrintf("Submitting wtx %s to mempool for relay\n", wtx.GetHash().ToString()); // We must set fInMempool here - while it will be re-set to true by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors @@ -1736,18 +1736,17 @@ bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay) // Irrespective of the failure reason, un-marking fInMempool // out-of-order is incorrect - it should be unmarked when // TransactionRemovedFromMempool fires. - bool ret = pwallet->chain().broadcastTransaction(tx, pwallet->m_default_max_tx_fee, relay, err_string); - fInMempool |= ret; + bool ret = chain().broadcastTransaction(wtx.tx, m_default_max_tx_fee, relay, err_string); + wtx.fInMempool |= ret; return ret; } -std::set<uint256> CWalletTx::GetConflicts() const +std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const { std::set<uint256> result; - if (pwallet != nullptr) { - uint256 myHash = GetHash(); - result = pwallet->GetConflicts(myHash); + uint256 myHash = wtx.GetHash(); + result = GetConflicts(myHash); result.erase(myHash); } return result; @@ -1785,11 +1784,11 @@ void CWallet::ResendWalletTransactions() for (std::pair<const uint256, CWalletTx>& item : mapWallet) { CWalletTx& wtx = item.second; // Attempt to rebroadcast all txes more than 5 minutes older than - // the last block. SubmitMemoryPoolAndRelay() will not rebroadcast + // the last block. SubmitTxMemoryPoolAndRelay() will not rebroadcast // any confirmed or conflicting txs. if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; std::string unused_err_string; - if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count; + if (SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, true)) ++submitted_tx_count; } } // cs_wallet @@ -1977,7 +1976,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve } std::string err_string; - if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) { + if (!SubmitTxMemoryPoolAndRelay(wtx, err_string, true)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } @@ -2192,7 +2191,7 @@ void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const { - LOCK(cs_wallet); + AssertLockHeld(cs_wallet); std::set<CTxDestination> result; for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book) { @@ -2911,28 +2910,27 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) m_pre_split = false; } -int CWalletTx::GetDepthInMainChain() const +int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const { - assert(pwallet != nullptr); - AssertLockHeld(pwallet->cs_wallet); - if (isUnconfirmed() || isAbandoned()) return 0; + AssertLockHeld(cs_wallet); + if (wtx.isUnconfirmed() || wtx.isAbandoned()) return 0; - return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1); + return (GetLastBlockHeight() - wtx.m_confirm.block_height + 1) * (wtx.isConflicted() ? -1 : 1); } -int CWalletTx::GetBlocksToMaturity() const +int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const { - if (!IsCoinBase()) + if (!wtx.IsCoinBase()) return 0; - int chain_depth = GetDepthInMainChain(); + int chain_depth = GetTxDepthInMainChain(wtx); assert(chain_depth >= 0); // coinbase tx should not be conflicted return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } -bool CWalletTx::IsImmatureCoinBase() const +bool CWallet::IsTxImmatureCoinBase(const CWalletTx& wtx) const { // note GetBlocksToMaturity is 0 for non-coinbase tx - return GetBlocksToMaturity() > 0; + return GetTxBlocksToMaturity(wtx) > 0; } bool CWallet::IsCrypted() const @@ -3254,12 +3252,13 @@ DescriptorScriptPubKeyMan* CWallet::GetDescriptorScriptPubKeyMan(const WalletDes ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label, bool internal) { + AssertLockHeld(cs_wallet); + if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { WalletLogPrintf("Cannot add WalletDescriptor to a non-descriptor wallet\n"); return nullptr; } - LOCK(cs_wallet); auto spk_man = GetDescriptorScriptPubKeyMan(desc); if (spk_man) { WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 607af3efb0..2dc9eff712 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -21,9 +21,7 @@ #include <validationinterface.h> #include <wallet/coinselection.h> #include <wallet/crypter.h> -#include <wallet/receive.h> #include <wallet/scriptpubkeyman.h> -#include <wallet/spend.h> #include <wallet/transaction.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> @@ -331,8 +329,6 @@ private: // ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers; - bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - /** * Catch wallet up to current chain, scanning new blocks, updating the best * block locator and m_last_block_processed, and registering for @@ -353,17 +349,6 @@ public: return *m_database; } - /** - * Select a set of coins such that nValueRet >= nTargetValue and at least - * all coins from coin_control are selected; never select unconfirmed coins if they are not ours - * param@[out] setCoinsRet Populated with inputs including pre-selected inputs from - * coin_control and Coin Selection if successful. - * param@[out] nValueRet Total value of selected coins including pre-selected ones - * from coin_control and Coin Selection if successful. - */ - bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, - const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - /** Get a name for this wallet for logging/debugging purposes. */ const std::string& GetName() const { return m_name; } @@ -419,39 +404,40 @@ public: interfaces::Chain& chain() const { assert(m_chain); return *m_chain; } const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - - //! check whether we support the named feature - bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); } - /** - * populate vCoins with vector of available COutputs. - */ - void AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + std::set<uint256> GetTxConflicts(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS; /** - * Return list of available coins and locked coins grouped by non-change output address. + * Return depth of transaction in blockchain: + * <0 : conflicts with a transaction this deep in the blockchain + * 0 : in memory pool, waiting to be included in a block + * >=1 : this many blocks deep in the main chain */ - std::map<CTxDestination, std::vector<COutput>> ListCoins() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + int GetTxDepthInMainChain(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS; + bool IsTxInMainChain(const CWalletTx& wtx) const { return GetTxDepthInMainChain(wtx) > 0; } /** - * Find non-change parent output. + * @return number of blocks to maturity for this transaction: + * 0 : is not a coinbase transaction, or is a mature coinbase transaction + * >0 : is a coinbase transaction which matures in this many blocks */ - const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + int GetTxBlocksToMaturity(const CWalletTx& wtx) const; + bool IsTxImmatureCoinBase(const CWalletTx& wtx) const; - /** - * Shuffle and select coins until nTargetValue is reached while avoiding - * small change; This method is stochastic for some inputs and upon - * completion the coin set and corresponding actual target value is - * assembled - * param@[in] coins Set of UTXOs to consider. These will be categorized into - * OutputGroups and filtered using eligibility_filter before - * selecting coins. - * param@[out] setCoinsRet Populated with the coins selected if successful. - * param@[out] nValueRet Used to return the total value of selected coins. - */ - bool AttemptSelection(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, - std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const; + //! check whether we support the named feature + bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); } bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -459,8 +445,6 @@ public: bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only) const; - /** Display address on an external signer. Returns false if external signer support is not compiled */ bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -546,24 +530,9 @@ public: void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ResendWalletTransactions(); - struct Balance { - CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more - CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) - CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain - CAmount m_watchonly_trusted{0}; - CAmount m_watchonly_untrusted_pending{0}; - CAmount m_watchonly_immature{0}; - }; - Balance GetBalance(int min_depth = 0, bool avoid_reuse = true) const; - CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const; OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const; - /** - * Insert additional inputs into the transaction by - * calling CreateTransaction(); - */ - bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); /** Fetch the inputs and sign with SIGHASH_ALL. */ bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** Sign the tx given the input coins and sighash. */ @@ -591,12 +560,6 @@ public: size_t* n_signed = nullptr) const; /** - * Create a new transaction paying the recipients with a set of coins - * selected by SelectCoins(); Also create the change output, when needed - * @note passing nChangePosInOut as -1 will result in setting a random position - */ - bool CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true); - /** * Submit the transaction to the node's mempool and then relay to peers. * Should be called after CreateTransaction unless you want to abort * broadcasting the transaction. @@ -607,6 +570,9 @@ public: */ void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm); + /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ + bool SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const; + bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const { std::vector<CTxOut> v_txouts(txouts.size()); @@ -664,10 +630,7 @@ public: int64_t GetOldestKeyPoolTime() const; - std::set<std::set<CTxDestination>> GetAddressGroupings() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - std::map<CTxDestination, CAmount> GetAddressBalances() const; - - std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; + std::set<CTxDestination> GetLabelAddresses(const std::string& label) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Marks all outputs in each one of the destinations dirty, so their cache is @@ -680,25 +643,16 @@ public: isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - isminetype IsMine(const CTxIn& txin) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Returns amount of debit if the input matches the * filter, otherwise returns 0 */ CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const; isminetype IsMine(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const; - bool IsChange(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool IsChange(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - CAmount GetChange(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; - /** Returns whether all of the inputs match the filter */ - bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const; - CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const; - CAmount GetChange(const CTransaction& tx) const; void chainStateFlushed(const CBlockLocator& loc) override; DBErrors LoadWallet(); @@ -922,7 +876,7 @@ public: DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const; //! Add a descriptor to the wallet, return a ScriptPubKeyMan & associated output type - ScriptPubKeyMan* AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label, bool internal); + ScriptPubKeyMan* AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label, bool internal) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); }; /** @@ -965,18 +919,6 @@ public: } }; -struct TxSize { - int64_t vsize{-1}; - int64_t weight{-1}; -}; - -/** Calculate the size of the transaction assuming all signatures are max size -* Use DummySignatureCreator, which inserts 71 byte signatures everywhere. -* NOTE: this requires that all inputs must be in mapWallet (eg the tx should -* be IsAllFromMe). */ -TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); -TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); - //! Add wallet name to persistent configuration so it will be loaded on startup. bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 2fabe65a93..03464cd2c8 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -954,7 +954,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal uint256 hash; ssKey >> hash; vTxHash.push_back(hash); - vWtx.emplace_back(nullptr /* wallet */, nullptr /* tx */); + vWtx.emplace_back(nullptr /* tx */); ssValue >> vWtx.back(); } } diff --git a/test/README.md b/test/README.md index 51e61562a4..412d5ae106 100644 --- a/test/README.md +++ b/test/README.md @@ -5,20 +5,24 @@ etc. This directory contains the following sets of tests: +- [fuzz](/test/fuzz) A runner to execute all fuzz targets from + [/src/test/fuzz](/src/test/fuzz). - [functional](/test/functional) which test the functionality of bitcoind and bitcoin-qt by interacting with them through the RPC and P2P interfaces. -- [util](/test/util) which tests the bitcoin utilities, currently only -bitcoin-tx. +- [util](/test/util) which tests the utilities (bitcoin-util, bitcoin-tx, ...). - [lint](/test/lint/) which perform various static analysis checks. -The util tests are run as part of `make check` target. The functional +The util tests are run as part of `make check` target. The fuzz tests, functional tests and lint scripts can be run as explained in the sections below. # Running tests locally Before tests can be run locally, Bitcoin Core must be built. See the [building instructions](/doc#building) for help. +## Fuzz tests + +See [/doc/fuzzing.md](/doc/fuzzing.md) ### Functional tests @@ -257,7 +261,7 @@ For ways to generate more granular profiles, see the README in ### Util tests -Util tests can be run locally by running `test/util/bitcoin-util-test.py`. +Util tests can be run locally by running `test/util/test_runner.py`. Use the `-v` option for verbose output. ### Lint tests @@ -269,7 +273,6 @@ Use the `-v` option for verbose output. | [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8) | [3.8.3](https://github.com/bitcoin/bitcoin/pull/19348) | `pip3 install flake8==3.8.3` | [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy) | [0.781](https://github.com/bitcoin/bitcoin/pull/19348) | `pip3 install mypy==0.781` | [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck) | [0.7.2](https://github.com/bitcoin/bitcoin/pull/21749) | [details...](https://github.com/koalaman/shellcheck#installing) -| [`lint-shell.sh`](lint/lint-shell.sh) | [yq](https://github.com/kislyuk/yq) | default | `pip3 install yq` | [`lint-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell) | [2.0.0](https://github.com/bitcoin/bitcoin/pull/20817) | `pip3 install codespell==2.0.0` Please be aware that on Linux distributions all dependencies are usually available as packages, but could be outdated. diff --git a/test/config.ini.in b/test/config.ini.in index e3872181cd..db80bba6f1 100644 --- a/test/config.ini.in +++ b/test/config.ini.in @@ -3,7 +3,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. # These environment variables are set by the build process and read by -# test/functional/test_runner.py and test/util/bitcoin-util-test.py +# test/*/test_runner.py and test/util/rpcauth-test.py [environment] PACKAGE_NAME=@PACKAGE_NAME@ diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py new file mode 100755 index 0000000000..8ccff340f0 --- /dev/null +++ b/test/functional/feature_addrman.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test addrman functionality""" + +import os +import struct + +from test_framework.messages import ser_uint256, hash256 +from test_framework.p2p import MAGIC_BYTES +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +def serialize_addrman(*, format=1, lowest_compatible=3): + new = [] + tried = [] + INCOMPATIBILITY_BASE = 32 + r = MAGIC_BYTES["regtest"] + r += struct.pack("B", format) + r += struct.pack("B", INCOMPATIBILITY_BASE + lowest_compatible) + r += ser_uint256(1) + r += struct.pack("i", len(new)) + r += struct.pack("i", len(tried)) + ADDRMAN_NEW_BUCKET_COUNT = 1 << 10 + r += struct.pack("i", ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30)) + for _ in range(ADDRMAN_NEW_BUCKET_COUNT): + r += struct.pack("i", 0) + checksum = hash256(r) + r += checksum + return r + + +def write_addrman(peers_dat, **kwargs): + with open(peers_dat, "wb") as f: + f.write(serialize_addrman(**kwargs)) + + +class AddrmanTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def run_test(self): + peers_dat = os.path.join(self.nodes[0].datadir, self.chain, "peers.dat") + + self.log.info("Check that mocked addrman is valid") + self.stop_node(0) + write_addrman(peers_dat) + with self.nodes[0].assert_debug_log(["Loaded 0 addresses from peers.dat"]): + self.start_node(0, extra_args=["-checkaddrman=1"]) + assert_equal(self.nodes[0].getnodeaddresses(), []) + + self.log.info("Check that addrman from future cannot be read") + self.stop_node(0) + write_addrman(peers_dat, lowest_compatible=111) + with self.nodes[0].assert_debug_log([ + f'ERROR: DeserializeDB: Deserialize or I/O error - Unsupported format of addrman database: 1. It is compatible with formats >=111, but the maximum supported by this version of {self.config["environment"]["PACKAGE_NAME"]} is 3.', + "Recreating peers.dat", + ]): + self.start_node(0) + assert_equal(self.nodes[0].getnodeaddresses(), []) + + self.log.info("Check that corrupt addrman cannot be read") + self.stop_node(0) + with open(peers_dat, "wb") as f: + f.write(serialize_addrman()[:-1]) + with self.nodes[0].assert_debug_log([ + "ERROR: DeserializeDB: Deserialize or I/O error - CAutoFile::read: end of file", + "Recreating peers.dat", + ]): + self.start_node(0) + assert_equal(self.nodes[0].getnodeaddresses(), []) + + self.log.info("Check that missing addrman is recreated") + self.stop_node(0) + os.remove(peers_dat) + with self.nodes[0].assert_debug_log([ + f"Missing or invalid file {peers_dat}", + "Recreating peers.dat", + ]): + self.start_node(0) + assert_equal(self.nodes[0].getnodeaddresses(), []) + + +if __name__ == "__main__": + AddrmanTest().main() diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py index c39f6e6d4b..7be393a4ea 100755 --- a/test/functional/feature_anchors.py +++ b/test/functional/feature_anchors.py @@ -25,9 +25,6 @@ class AnchorsTest(BitcoinTestFramework): self.num_nodes = 1 self.disable_autoconnect = False - def setup_network(self): - self.setup_nodes() - def run_test(self): node_anchors_path = os.path.join( self.nodes[0].datadir, "regtest", "anchors.dat" diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 0daa0ba3d8..3d9d8b7441 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -164,11 +164,14 @@ class ConfArgsTest(BitcoinTestFramework): # fixed seeds assert not os.path.exists(os.path.join(default_data_dir, "peers.dat")) start = int(time.time()) - with self.nodes[0].assert_debug_log(expected_msgs=[ - "Loaded 0 addresses from peers.dat", - "0 addresses found from DNS seeds", - "opencon thread start", # Ensure ThreadOpenConnections::start time is properly set - ]): + with self.nodes[0].assert_debug_log( + expected_msgs=[ + "Loaded 0 addresses from peers.dat", + "0 addresses found from DNS seeds", + "opencon thread start", # Ensure ThreadOpenConnections::start time is properly set + ], + timeout=10, + ): self.start_node(0, extra_args=['-dnsseed=1', '-fixedseeds=1', f'-mocktime={start}']) with self.nodes[0].assert_debug_log(expected_msgs=[ "Adding fixed seeds as 60 seconds have passed and addrman is empty", @@ -206,11 +209,14 @@ class ConfArgsTest(BitcoinTestFramework): # We expect the node will allow 60 seconds prior to using fixed seeds assert not os.path.exists(os.path.join(default_data_dir, "peers.dat")) start = int(time.time()) - with self.nodes[0].assert_debug_log(expected_msgs=[ - "Loaded 0 addresses from peers.dat", - "DNS seeding disabled", - "opencon thread start", # Ensure ThreadOpenConnections::start time is properly set - ]): + with self.nodes[0].assert_debug_log( + expected_msgs=[ + "Loaded 0 addresses from peers.dat", + "DNS seeding disabled", + "opencon thread start", # Ensure ThreadOpenConnections::start time is properly set + ], + timeout=10, + ): self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1', '-addnode=fakenodeaddr', f'-mocktime={start}']) with self.nodes[0].assert_debug_log(expected_msgs=[ "Adding fixed seeds as 60 seconds have passed and addrman is empty", diff --git a/test/functional/feature_presegwit_node_upgrade.py b/test/functional/feature_presegwit_node_upgrade.py index f20c519082..fd6b8620d4 100755 --- a/test/functional/feature_presegwit_node_upgrade.py +++ b/test/functional/feature_presegwit_node_upgrade.py @@ -9,6 +9,8 @@ from test_framework.util import ( assert_equal, softfork_active, ) +import os + class SegwitUpgradeTest(BitcoinTestFramework): def set_test_params(self): @@ -35,8 +37,11 @@ class SegwitUpgradeTest(BitcoinTestFramework): # Restarting the node (with segwit activation height set to 5) should result in a shutdown # because the blockchain consists of 3 insufficiently validated blocks per segwit consensus rules. node.assert_start_raises_init_error( - extra_args=["-segwitheight=5"], - expected_msg=": Witness data for blocks after height 5 requires validation. Please restart with -reindex..\nPlease restart with -reindex or -reindex-chainstate to recover.") + extra_args=["-segwitheight=5"], + expected_msg=": Witness data for blocks after height 5 requires " + f"validation. Please restart with -reindex..{os.linesep}" + "Please restart with -reindex or -reindex-chainstate to recover.", + ) # As directed, the user restarts the node with -reindex self.start_node(0, extra_args=["-reindex", "-segwitheight=5"]) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 694cca15fd..cb7556feb4 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -618,6 +618,17 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable']) assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0) + self.log.info('Check that the child tx can still be replaced (via a tx that also replaces the parent)') + replacement_parent_tx = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=confirmed_utxo, + sequence=0xffffffff, + fee_rate=Decimal('0.03'), + ) + # Check that child is removed and update wallet utxo state + assert_raises_rpc_error(-5, 'Transaction not in mempool', self.nodes[0].getmempoolentry, optout_child_tx['txid']) + self.wallet.get_utxo(txid=optout_child_tx['txid']) + def test_replacement_relay_fee(self): tx = self.wallet.send_self_transfer(from_node=self.nodes[0])['tx'] diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py index 563f2ea43e..13f33c321f 100755 --- a/test/functional/rpc_misc.py +++ b/test/functional/rpc_misc.py @@ -57,7 +57,7 @@ class RpcMiscTest(BitcoinTestFramework): self.log.info("test logging rpc and help") # Test logging RPC returns the expected number of logging categories. - assert_equal(len(node.logging()), 24) + assert_equal(len(node.logging()), 25) # Test toggling a logging category on/off/on with the logging RPC. assert_equal(node.logging()['qt'], True) diff --git a/test/functional/rpc_signer.py b/test/functional/rpc_signer.py index 3188763f49..9e963eba57 100755 --- a/test/functional/rpc_signer.py +++ b/test/functional/rpc_signer.py @@ -53,8 +53,12 @@ class RPCSignerTest(BitcoinTestFramework): ) # Handle script missing: - assert_raises_rpc_error(-1, 'execve failed: No such file or directory', - self.nodes[3].enumeratesigners + assert_raises_rpc_error( + -1, + "CreateProcess failed: The system cannot find the file specified." + if platform.system() == "Windows" + else "execve failed: No such file or directory", + self.nodes[3].enumeratesigners, ) # Handle error thrown by script diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d6d676da1f..3792d751de 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -282,6 +282,7 @@ BASE_SCRIPTS = [ 'p2p_blockfilters.py', 'p2p_message_capture.py', 'feature_includeconf.py', + 'feature_addrman.py', 'feature_asmap.py', 'mempool_unbroadcast.py', 'mempool_compatibility.py', diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index a826fb0575..4bf3927879 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -344,7 +344,7 @@ class ToolWalletTest(BitcoinTestFramework): non_exist_dump = os.path.join(self.nodes[0].datadir, "wallet.nodump") self.assert_raises_tool_error('Unknown wallet file format "notaformat" provided. Please provide one of "bdb" or "sqlite".', '-wallet=todump', '-format=notaformat', '-dumpfile={}'.format(wallet_dump), 'createfromdump') self.assert_raises_tool_error('Dump file {} does not exist.'.format(non_exist_dump), '-wallet=todump', '-dumpfile={}'.format(non_exist_dump), 'createfromdump') - wallet_path = os.path.join(self.nodes[0].datadir, 'regtest/wallets/todump2') + wallet_path = os.path.join(self.nodes[0].datadir, 'regtest', 'wallets', 'todump2') self.assert_raises_tool_error('Failed to create database path \'{}\'. Database already exists.'.format(wallet_path), '-wallet=todump2', '-dumpfile={}'.format(wallet_dump), 'createfromdump') self.assert_raises_tool_error("The -descriptors option can only be used with the 'create' command.", '-descriptors', '-wallet=todump2', '-dumpfile={}'.format(wallet_dump), 'createfromdump') diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index e7873a82bb..6372e1acd7 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -600,7 +600,7 @@ class WalletTest(BitcoinTestFramework): total_txs = len(self.nodes[0].listtransactions("*", 99999)) # Try with walletrejectlongchains - # Double chain limit but require combining inputs, so we pass SelectCoinsMinConf + # Double chain limit but require combining inputs, so we pass AttemptSelection self.stop_node(0) extra_args = ["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)] self.start_node(0, extra_args=extra_args) diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 233381f2d9..8e74f41bb6 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -24,10 +24,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "wallet/fees -> wallet/wallet -> wallet/fees" "wallet/wallet -> wallet/walletdb -> wallet/wallet" "node/coinstats -> validation -> node/coinstats" - # Temporary circular dependencies that allow wallet.h/wallet.cpp to be - # split up in a MOVEONLY commit. These are removed in #21206. - "wallet/receive -> wallet/wallet -> wallet/receive" - "wallet/spend -> wallet/wallet -> wallet/spend" ) EXIT_CODE=0 diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index 737d35a397..d6312270e7 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -39,10 +39,8 @@ export LC_ALL=C KNOWN_VIOLATIONS=( "src/bitcoin-tx.cpp.*stoul" - "src/bitcoin-tx.cpp.*trim_right" "src/dbwrapper.cpp.*stoul" "src/dbwrapper.cpp:.*vsnprintf" - "src/httprpc.cpp.*trim" "src/node/blockstorage.cpp:.*atoi" "src/qt/rpcconsole.cpp:.*atoi" "src/rest.cpp:.*strtol" diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index b52e105a33..63e7c57ddb 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -5,6 +5,8 @@ # names can be used. # See https://github.com/google/sanitizers/issues/1364 signed-integer-overflow:txmempool.cpp +# nLastSuccess read from peers.dat might cause an overflow in IsTerrible +signed-integer-overflow:addrman.cpp # https://github.com/bitcoin/bitcoin/pull/21798#issuecomment-829180719 signed-integer-overflow:policy/feerate.cpp diff --git a/test/util/bitcoin-util-test.py b/test/util/test_runner.py index aa8fd6eee5..aa8fd6eee5 100755 --- a/test/util/bitcoin-util-test.py +++ b/test/util/test_runner.py |