diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 337e8af00f..8021bdffe4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: git rebase --exec "echo Running test-one-commit on \$( git log -1 ) && CC=clang CXX=clang++ cmake -B build -DWERROR=ON -DWITH_ZMQ=ON -DBUILD_GUI=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWITH_BDB=ON -DWITH_USDT=ON -DCMAKE_CXX_FLAGS='-Wno-error=unused-member-function' && cmake --build build -j $(nproc) && ctest --output-on-failure --stop-on-failure --test-dir build -j $(nproc) && ./build/test/functional/test_runner.py -j $(( $(nproc) * 2 ))" ${{ env.TEST_BASE }} macos-native-arm64: - name: 'macOS 14 native, arm64, no depends, sqlite only, gui' + name: ${{ matrix.job-name }} # Use latest image, but hardcode version to avoid silent upgrades (and breaks). # See: https://github.com/actions/runner-images#available-images. runs-on: macos-14 @@ -89,9 +89,20 @@ jobs: timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + job-type: [standard, fuzz] + include: + - job-type: standard + file-env: './ci/test/00_setup_env_mac_native.sh' + job-name: 'macOS 14 native, arm64, no depends, sqlite only, gui' + - job-type: fuzz + file-env: './ci/test/00_setup_env_mac_native_fuzz.sh' + job-name: 'macOS 14 native, arm64, fuzz' + env: DANGER_RUN_CI_ON_HOST: 1 - FILE_ENV: './ci/test/00_setup_env_mac_native.sh' BASE_ROOT_DIR: ${{ github.workspace }} steps: @@ -120,11 +131,13 @@ jobs: uses: actions/cache/restore@v4 with: path: ${{ env.CCACHE_DIR }} - key: ${{ github.job }}-ccache-${{ github.run_id }} - restore-keys: ${{ github.job }}-ccache- + key: ${{ github.job }}-${{ matrix.job-type }}-ccache-${{ github.run_id }} + restore-keys: ${{ github.job }}-${{ matrix.job-type }}-ccache- - name: CI script run: ./ci/test_run_all.sh + env: + FILE_ENV: ${{ matrix.file-env }} - name: Save Ccache cache uses: actions/cache/save@v4 @@ -132,10 +145,10 @@ jobs: with: path: ${{ env.CCACHE_DIR }} # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#update-a-cache - key: ${{ github.job }}-ccache-${{ github.run_id }} + key: ${{ github.job }}-${{ matrix.job-type }}-ccache-${{ github.run_id }} win64-native: - name: 'Win64 native, VS 2022' + name: ${{ matrix.job-name }} # Use latest image, but hardcode version to avoid silent upgrades (and breaks). # See: https://github.com/actions/runner-images#available-images. runs-on: windows-2022 @@ -146,6 +159,18 @@ jobs: PYTHONUTF8: 1 TEST_RUNNER_TIMEOUT_FACTOR: 40 + strategy: + fail-fast: false + matrix: + job-type: [standard, fuzz] + include: + - job-type: standard + generate-options: '-DBUILD_GUI=ON -DWITH_BDB=ON -DWITH_ZMQ=ON -DBUILD_BENCH=ON -DWERROR=ON' + job-name: 'Win64 native, VS 2022' + - job-type: fuzz + generate-options: '-DVCPKG_MANIFEST_NO_DEFAULT_FEATURES=ON -DVCPKG_MANIFEST_FEATURES="sqlite" -DBUILD_GUI=OFF -DBUILD_FOR_FUZZING=ON -DWERROR=ON' + job-name: 'Win64 native fuzz, VS 2022' + steps: - name: Checkout uses: actions/checkout@v4 @@ -186,11 +211,11 @@ jobs: - name: Generate build system run: | - cmake -B build --preset vs2022-static -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" -DBUILD_GUI=ON -DWITH_BDB=ON -DWITH_ZMQ=ON -DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -DWERROR=ON + cmake -B build --preset vs2022-static -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" ${{ matrix.generate-options }} - name: Save vcpkg binary cache uses: actions/cache/save@v4 - if: github.event_name != 'pull_request' && steps.vcpkg-binary-cache.outputs.cache-hit != 'true' + if: github.event_name != 'pull_request' && steps.vcpkg-binary-cache.outputs.cache-hit != 'true' && matrix.job-type == 'standard' with: path: ~/AppData/Local/vcpkg/archives key: ${{ github.job }}-vcpkg-binary-${{ hashFiles('cmake_version', 'msbuild_version', 'toolset_version', 'vcpkg.json') }} @@ -201,11 +226,13 @@ jobs: cmake --build . -j $env:NUMBER_OF_PROCESSORS --config Release - name: Run test suite + if: matrix.job-type == 'standard' working-directory: build run: | ctest --output-on-failure --stop-on-failure -j $env:NUMBER_OF_PROCESSORS -C Release - name: Run functional tests + if: matrix.job-type == 'standard' working-directory: build env: BITCOIND: '${{ github.workspace }}\build\src\Release\bitcoind.exe' @@ -216,6 +243,23 @@ jobs: shell: cmd run: py -3 test\functional\test_runner.py --jobs %NUMBER_OF_PROCESSORS% --ci --quiet --tmpdirprefix=%RUNNER_TEMP% --combinedlogslen=99999999 --timeout-factor=%TEST_RUNNER_TIMEOUT_FACTOR% %TEST_RUNNER_EXTRA% + - name: Clone corpora + if: matrix.job-type == 'fuzz' + run: | + git clone --depth=1 https://github.com/bitcoin-core/qa-assets "$env:RUNNER_TEMP\qa-assets" + Set-Location "$env:RUNNER_TEMP\qa-assets" + Write-Host "Using qa-assets repo from commit ..." + git log -1 + + - name: Run fuzz tests + if: matrix.job-type == 'fuzz' + working-directory: build + env: + BITCOINFUZZ: '${{ github.workspace }}\build\src\test\fuzz\Release\fuzz.exe' + shell: cmd + run: | + py -3 test\fuzz\test_runner.py --par %NUMBER_OF_PROCESSORS% --loglevel DEBUG %RUNNER_TEMP%\qa-assets\fuzz_corpora + asan-lsan-ubsan-integer-no-depends-usdt: name: 'ASan + LSan + UBSan + integer, no depends, USDT' runs-on: ubuntu-24.04 # has to match container in ci/test/00_setup_env_native_asan.sh for tracing tools diff --git a/CMakeLists.txt b/CMakeLists.txt index 27f8deed34..7a0ba70dad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,8 +253,8 @@ if(WIN32) ]=] target_compile_definitions(core_interface INTERFACE - _WIN32_WINNT=0x0601 - _WIN32_IE=0x0501 + _WIN32_WINNT=0x0A00 + _WIN32_IE=0x0A00 WIN32_LEAN_AND_MEAN NOMINMAX ) @@ -292,9 +292,11 @@ if(WIN32) # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412. try_append_cxx_flags("-Wa,-muse-unaligned-vector-move" TARGET core_interface SKIP_LINK) try_append_linker_flag("-static" TARGET core_interface) - # We require Windows 7 (NT 6.1) or later. + # We support Windows 10+, however it's not possible to set these values accordingly, + # due to a bug in mingw-w64. See https://sourceforge.net/p/mingw-w64/bugs/968/. + # As a best effort, target Windows 8. try_append_linker_flag("-Wl,--major-subsystem-version,6" TARGET core_interface) - try_append_linker_flag("-Wl,--minor-subsystem-version,1" TARGET core_interface) + try_append_linker_flag("-Wl,--minor-subsystem-version,2" TARGET core_interface) endif() endif() @@ -436,8 +438,16 @@ configure_file(contrib/filter-lcov.py filter-lcov.py USE_SOURCE_PERMISSIONS COPY # Don't allow extended (non-ASCII) symbols in identifiers. This is easier for code review. try_append_cxx_flags("-fno-extended-identifiers" TARGET core_interface SKIP_LINK) -try_append_cxx_flags("-ffile-prefix-map=A=B" TARGET core_interface SKIP_LINK - IF_CHECK_PASSED "-ffile-prefix-map=${PROJECT_SOURCE_DIR}/src=." +# Avoiding the `-ffile-prefix-map` compiler option because it implies +# `-fcoverage-prefix-map` on Clang or `-fprofile-prefix-map` on GCC, +# which can cause issues with coverage builds, particularly when using +# Clang in the OSS-Fuzz environment due to its use of other options +# and a third party script, or with GCC. +try_append_cxx_flags("-fdebug-prefix-map=A=B" TARGET core_interface SKIP_LINK + IF_CHECK_PASSED "-fdebug-prefix-map=${PROJECT_SOURCE_DIR}/src=." +) +try_append_cxx_flags("-fmacro-prefix-map=A=B" TARGET core_interface SKIP_LINK + IF_CHECK_PASSED "-fmacro-prefix-map=${PROJECT_SOURCE_DIR}/src=." ) # Currently all versions of gcc are subject to a class of bugs, see the @@ -478,8 +488,7 @@ if(ENABLE_HARDENING) try_append_cxx_flags("-fcf-protection=full" TARGET hardening_interface) if(MINGW) - # stack-clash-protection doesn't compile with GCC 10 and earlier. - # In any case, it is a no-op for Windows. + # stack-clash-protection is a no-op for Windows. # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90458 for more details. else() try_append_cxx_flags("-fstack-clash-protection" TARGET hardening_interface) diff --git a/ci/test/00_setup_env_mac_native_fuzz.sh b/ci/test/00_setup_env_mac_native_fuzz.sh new file mode 100755 index 0000000000..1a453a4353 --- /dev/null +++ b/ci/test/00_setup_env_mac_native_fuzz.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# +# Copyright (c) The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C.UTF-8 + +export CMAKE_GENERATOR="Ninja" +export BITCOIN_CONFIG="-DBUILD_FOR_FUZZING=ON" +export CI_OS_NAME="macos" +export NO_DEPENDS=1 +export OSX_SDK="" +export RUN_UNIT_TESTS=false +export RUN_FUNCTIONAL_TESTS=false +export RUN_FUZZ_TESTS=true diff --git a/contrib/devtools/gen-manpages.py b/contrib/devtools/gen-manpages.py index b58625a81d..621ed45904 100755 --- a/contrib/devtools/gen-manpages.py +++ b/contrib/devtools/gen-manpages.py @@ -6,6 +6,7 @@ import subprocess import sys import tempfile +import argparse BINARIES = [ 'src/namecoind', @@ -16,6 +17,18 @@ 'src/qt/namecoin-qt', ] +parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, +) +parser.add_argument( + "-s", + "--skip-missing-binaries", + action="store_true", + default=False, + help="skip generation for binaries that are not found in the build path", +) +args = parser.parse_args() + # Paths to external utilities. git = os.getenv('GIT', 'git') help2man = os.getenv('HELP2MAN', 'help2man') @@ -38,8 +51,12 @@ try: r = subprocess.run([abspath, "--version"], stdout=subprocess.PIPE, check=True, text=True) except IOError: - print(f'{abspath} not found or not an executable', file=sys.stderr) - sys.exit(1) + if(args.skip_missing_binaries): + print(f'{abspath} not found or not an executable. Skipping...', file=sys.stderr) + continue + else: + print(f'{abspath} not found or not an executable', file=sys.stderr) + sys.exit(1) # take first line (which must contain version) verstr = r.stdout.splitlines()[0] # last word of line is the actual version e.g. v22.99.0-5c6b3d5b3508 @@ -51,6 +68,10 @@ versions.append((abspath, verstr, copyright)) +if not versions: + print(f'No binaries found in {builddir}. Please ensure the binaries are present in {builddir}, or set another build path using the BUILDDIR env variable.') + sys.exit(1) + if any(verstr.endswith('-dirty') for (_, verstr, _) in versions): print("WARNING: Binaries were built from a dirty tree.") print('man pages generated from dirty binaries should NOT be committed.') diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index 3f6010280a..564f1db5ac 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -260,7 +260,7 @@ def check_PE_libraries(binary) -> bool: def check_PE_subsystem_version(binary) -> bool: major: int = binary.optional_header.major_subsystem_version minor: int = binary.optional_header.minor_subsystem_version - if major == 6 and minor == 1: + if major == 6 and minor == 2: return True return False diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index c75a5e1546..b4b524dac0 100755 --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -135,7 +135,7 @@ def test_PE(self): } ''') - self.assertEqual(call_symbol_check(cxx, source, executable, ['-lpdh', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,1']), + self.assertEqual(call_symbol_check(cxx, source, executable, ['-lpdh', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,2']), (1, 'pdh.dll is not in ALLOWED_LIBRARIES!\n' + executable + ': failed DYNAMIC_LIBRARIES')) @@ -166,7 +166,7 @@ def test_PE(self): } ''') - self.assertEqual(call_symbol_check(cxx, source, executable, ['-lole32', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,1']), + self.assertEqual(call_symbol_check(cxx, source, executable, ['-lole32', '-Wl,--major-subsystem-version', '-Wl,6', '-Wl,--minor-subsystem-version', '-Wl,2']), (0, '')) diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index bd2ecddcac..eccb408559 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -13,7 +13,6 @@ ((gnu packages linux) #:select (linux-libre-headers-6.1)) (gnu packages llvm) (gnu packages mingw) - (gnu packages moreutils) (gnu packages pkg-config) ((gnu packages python) #:select (python-minimal)) ((gnu packages python-build) #:select (python-tomli)) @@ -21,6 +20,7 @@ ((gnu packages tls) #:select (openssl)) ((gnu packages version-control) #:select (git-minimal)) (guix build-system cmake) + (guix build-system gnu) (guix build-system python) (guix build-system trivial) (guix download) @@ -28,7 +28,7 @@ (guix git-download) ((guix licenses) #:prefix license:) (guix packages) - ((guix utils) #:select (substitute-keyword-arguments))) + ((guix utils) #:select (cc-for-target substitute-keyword-arguments))) (define-syntax-rule (search-our-patches file-name ...) "Return the list of absolute file names corresponding to each @@ -487,6 +487,36 @@ inspecting signatures in Mach-O binaries.") (("^install-others =.*$") (string-append "install-others = " out "/etc/rpc\n"))))))))))))) +;; The sponge tool from moreutils. +(define-public sponge + (package + (name "sponge") + (version "0.69") + (source (origin + (method url-fetch) + (uri (string-append + "https://git.joeyh.name/index.cgi/moreutils.git/snapshot/ + moreutils-" version ".tar.gz")) + (file-name (string-append "moreutils-" version ".tar.gz")) + (sha256 + (base32 + "1l859qnzccslvxlh5ghn863bkq2vgmqgnik6jr21b9kc6ljmsy8g")))) + (build-system gnu-build-system) + (arguments + (list #:phases + #~(modify-phases %standard-phases + (delete 'configure) + (replace 'install + (lambda* (#:key outputs #:allow-other-keys) + (let ((bin (string-append (assoc-ref outputs "out") "/bin"))) + (install-file "sponge" bin))))) + #:make-flags + #~(list "sponge" (string-append "CC=" #$(cc-for-target))))) + (home-page "https://joeyh.name/code/moreutils/") + (synopsis "Miscellaneous general-purpose command-line tools") + (description "Just sponge") + (license license:gpl2+))) + (packages->manifest (append (list ;; The Basics @@ -502,7 +532,7 @@ inspecting signatures in Mach-O binaries.") patch gawk sed - moreutils + sponge ;; Compression and archiving tar gzip diff --git a/depends/hosts/mingw32.mk b/depends/hosts/mingw32.mk index 755d7aebe4..4628f7255d 100644 --- a/depends/hosts/mingw32.mk +++ b/depends/hosts/mingw32.mk @@ -23,5 +23,5 @@ mingw32_debug_CXXFLAGS=$(mingw32_debug_CFLAGS) mingw32_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC mingw32_cmake_system_name=Windows -# Windows 7 (NT 6.1). -mingw32_cmake_system_version=6.1 +# Windows 10 +mingw32_cmake_system_version=10.0 diff --git a/depends/packages/libevent.mk b/depends/packages/libevent.mk index 91bc75c1d3..9507fe9ddf 100644 --- a/depends/packages/libevent.mk +++ b/depends/packages/libevent.mk @@ -7,14 +7,14 @@ $(package)_patches=cmake_fixups.patch $(package)_build_subdir=build # When building for Windows, we set _WIN32_WINNT to target the same Windows -# version as we do in configure. Due to quirks in libevents build system, this +# version as we do in releases. Due to quirks in libevents build system, this # is also required to enable support for ipv6. See #19375. define $(package)_set_vars $(package)_config_opts=-DEVENT__DISABLE_BENCHMARK=ON -DEVENT__DISABLE_OPENSSL=ON $(package)_config_opts+=-DEVENT__DISABLE_SAMPLES=ON -DEVENT__DISABLE_REGRESS=ON $(package)_config_opts+=-DEVENT__DISABLE_TESTS=ON -DEVENT__LIBRARY_TYPE=STATIC $(package)_cppflags += -D_GNU_SOURCE - $(package)_cppflags_mingw32=-D_WIN32_WINNT=0x0601 + $(package)_cppflags_mingw32=-D_WIN32_WINNT=0x0A00 ifeq ($(NO_HARDEN),) $(package)_cppflags+=-D_FORTIFY_SOURCE=3 diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk index 743cc38cc7..89e10d15ef 100644 --- a/depends/packages/zeromq.mk +++ b/depends/packages/zeromq.mk @@ -18,7 +18,7 @@ define $(package)_set_vars $(package)_config_opts += -DBUILD_SHARED=OFF -DBUILD_TESTS=OFF -DZMQ_BUILD_TESTS=OFF $(package)_config_opts += -DENABLE_DRAFTS=OFF -DZMQ_BUILD_TESTS=OFF $(package)_cxxflags += -ffile-prefix-map=$($(package)_extract_dir)=/usr - $(package)_config_opts_mingw32 += -DZMQ_WIN32_WINNT=0x0601 -DZMQ_HAVE_IPC=OFF + $(package)_config_opts_mingw32 += -DZMQ_WIN32_WINNT=0x0A00 -DZMQ_HAVE_IPC=OFF endef define $(package)_preprocess_cmds diff --git a/doc/descriptors.md b/doc/descriptors.md index 8c55d4ae37..115ca5e61a 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -23,6 +23,9 @@ Supporting RPCs are: - `listdescriptors` outputs descriptors imported into a descriptor wallet (since v22). - `scanblocks` takes as input descriptors to scan for in blocks and returns the relevant blockhashes (since v25). +- `getdescriptoractivity` takes as input descriptors and blockhashes (as output + by `scanblocks`) and returns rich event data related to spends or receives associated + with the given descriptors. This document describes the language. For the specifics on usage, see the RPC documentation for the functions mentioned above. diff --git a/doc/release-notes-30708.md b/doc/release-notes-30708.md new file mode 100644 index 0000000000..5cf17c7b65 --- /dev/null +++ b/doc/release-notes-30708.md @@ -0,0 +1,6 @@ +New RPCs +-------- + +- `getdescriptoractivity` can be used to find all spend/receive activity relevant to + a given set of descriptors within a set of specified blocks. This call can be used with + `scanblocks` to lessen the need for additional indexing programs. diff --git a/doc/release-notes-empty-template.md b/doc/release-notes-empty-template.md index 1ff55b5ccc..bd4600b395 100644 --- a/doc/release-notes-empty-template.md +++ b/doc/release-notes-empty-template.md @@ -42,8 +42,8 @@ codesign -s - bitcoin-cli bitcoin-qt bitcoin-tx bitcoin-util bitcoin-wallet bitc Compatibility ============== -Bitcoin Core is supported and extensively tested on operating systems -using the Linux Kernel 3.17+, macOS 13.0+, and Windows 7 and newer. Bitcoin +Bitcoin Core is supported and tested on operating systems using the +Linux Kernel 3.17+, macOS 13.0+, and Windows 10 and newer. Bitcoin Core should also work on most other Unix-like systems but is not as frequently tested on them. It is not recommended to use Bitcoin Core on unsupported systems. diff --git a/src/bench/mempool_ephemeral_spends.cpp b/src/bench/mempool_ephemeral_spends.cpp index f34664a736..a8f2efecb9 100644 --- a/src/bench/mempool_ephemeral_spends.cpp +++ b/src/bench/mempool_ephemeral_spends.cpp @@ -44,7 +44,7 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench) } // Tx with many outputs - CMutableTransaction tx1 = CMutableTransaction(); + CMutableTransaction tx1; tx1.vin.resize(1); tx1.vout.resize(number_outputs); for (size_t i = 0; i < tx1.vout.size(); i++) { @@ -56,7 +56,7 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench) const auto& parent_txid = tx1.GetHash(); // Spends all outputs of tx1, other details don't matter - CMutableTransaction tx2 = CMutableTransaction(); + CMutableTransaction tx2; tx2.vin.resize(tx1.vout.size()); for (size_t i = 0; i < tx2.vin.size(); i++) { tx2.vin[0].prevout.hash = parent_txid; @@ -74,9 +74,12 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench) uint32_t iteration{0}; + TxValidationState dummy_state; + Txid dummy_txid; + bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { - CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool); + CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool, dummy_state, dummy_txid); iteration++; }); } diff --git a/src/bench/prevector.cpp b/src/bench/prevector.cpp index 9b83c42693..adc3d18de0 100644 --- a/src/bench/prevector.cpp +++ b/src/bench/prevector.cpp @@ -87,6 +87,7 @@ static void PrevectorFillVectorDirect(benchmark::Bench& bench) { bench.run([&] { std::vector> vec; + vec.reserve(260); for (size_t i = 0; i < 260; ++i) { vec.emplace_back(); } @@ -99,6 +100,7 @@ static void PrevectorFillVectorIndirect(benchmark::Bench& bench) { bench.run([&] { std::vector> vec; + vec.reserve(260); for (size_t i = 0; i < 260; ++i) { // force allocation vec.emplace_back(29, T{}); diff --git a/src/bench/sign_transaction.cpp b/src/bench/sign_transaction.cpp index 3f0635711d..40219458f3 100644 --- a/src/bench/sign_transaction.cpp +++ b/src/bench/sign_transaction.cpp @@ -62,7 +62,7 @@ static void SignTransactionSingleInput(benchmark::Bench& bench, InputType input_ bench.minEpochIterations(100).run([&] { CMutableTransaction tx{unsigned_tx}; std::map coins; - CScript prev_spk = prev_spks[(iter++) % prev_spks.size()]; + const CScript& prev_spk = prev_spks[(iter++) % prev_spks.size()]; coins[prevout] = Coin(CTxOut(10000, prev_spk), /*nHeightIn=*/100, /*fCoinBaseIn=*/false); std::map input_errors; bool complete = SignTransaction(tx, &keystore, coins, SIGHASH_ALL, input_errors); diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 25f9c716f9..62ef92bfee 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -321,7 +321,7 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn CAmount value = ExtractAndValidateValue(vStrInputParts[0]); // extract and validate ADDRESS - std::string strAddr = vStrInputParts[1]; + const std::string& strAddr = vStrInputParts[1]; CTxDestination destination = DecodeDestination(strAddr); if (!IsValidDestination(destination)) { throw std::runtime_error("invalid TX output address"); @@ -354,7 +354,7 @@ static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& str bool bSegWit = false; bool bScriptHash = false; if (vStrInputParts.size() == 3) { - std::string flags = vStrInputParts[2]; + const std::string& flags = vStrInputParts[2]; bSegWit = (flags.find('W') != std::string::npos); bScriptHash = (flags.find('S') != std::string::npos); } @@ -415,7 +415,7 @@ static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& s bool bSegWit = false; bool bScriptHash = false; if (vStrInputParts.size() == numkeys + 4) { - std::string flags = vStrInputParts.back(); + const std::string& flags = vStrInputParts.back(); bSegWit = (flags.find('W') != std::string::npos); bScriptHash = (flags.find('S') != std::string::npos); } @@ -490,14 +490,14 @@ static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& str CAmount value = ExtractAndValidateValue(vStrInputParts[0]); // extract and validate script - std::string strScript = vStrInputParts[1]; + const std::string& strScript = vStrInputParts[1]; CScript scriptPubKey = ParseScript(strScript); // Extract FLAGS bool bSegWit = false; bool bScriptHash = false; if (vStrInputParts.size() == 3) { - std::string flags = vStrInputParts.back(); + const std::string& flags = vStrInputParts.back(); bSegWit = (flags.find('W') != std::string::npos); bScriptHash = (flags.find('S') != std::string::npos); } diff --git a/src/core_io.h b/src/core_io.h index e13c5d2a2c..294f95e504 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -10,6 +10,7 @@ #include #include +#include class CBlock; class CBlockHeader; diff --git a/src/policy/ephemeral_policy.cpp b/src/policy/ephemeral_policy.cpp index 6854822e35..b7d254dd63 100644 --- a/src/policy/ephemeral_policy.cpp +++ b/src/policy/ephemeral_policy.cpp @@ -2,29 +2,39 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include +#include +#include #include +#include +#include +#include +#include -bool HasDust(const CTransactionRef& tx, CFeeRate dust_relay_rate) -{ - return std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& output) { return IsDust(output, dust_relay_rate); }); -} +#include +#include +#include +#include +#include +#include +#include -bool CheckValidEphemeralTx(const CTransactionRef& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state) +bool PreCheckEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state) { // We never want to give incentives to mine this transaction alone - if ((base_fee != 0 || mod_fee != 0) && HasDust(tx, dust_relay_rate)) { + if ((base_fee != 0 || mod_fee != 0) && !GetDust(tx, dust_relay_rate).empty()) { return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "dust", "tx with dust output must be 0-fee"); } return true; } -std::optional CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool) +bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Txid& out_child_txid) { - if (!Assume(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;}))) { + if (!Assume(std::ranges::all_of(package, [](const auto& tx){return tx != nullptr;}))) { // Bail out of spend checks if caller gave us an invalid package - return std::nullopt; + return true; } std::map map_txid_ref; @@ -33,7 +43,6 @@ std::optional CheckEphemeralSpends(const Package& package, CFeeRate dust_r } for (const auto& tx : package) { - Txid txid = tx->GetHash(); std::unordered_set processed_parent_set; std::unordered_set unspent_parent_dust; @@ -63,6 +72,10 @@ std::optional CheckEphemeralSpends(const Package& package, CFeeRate dust_r processed_parent_set.insert(parent_txid); } + if (unspent_parent_dust.empty()) { + continue; + } + // Now that we have gathered parents' dust, make sure it's spent // by the child for (const auto& tx_input : tx->vin) { @@ -70,9 +83,12 @@ std::optional CheckEphemeralSpends(const Package& package, CFeeRate dust_r } if (!unspent_parent_dust.empty()) { - return txid; + out_child_txid = tx->GetHash(); + out_child_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "missing-ephemeral-spends", + strprintf("tx %s did not spend parent's ephemeral dust", out_child_txid.ToString())); + return false; } } - return std::nullopt; + return true; } diff --git a/src/policy/ephemeral_policy.h b/src/policy/ephemeral_policy.h index 26140f9a02..4667f25b5a 100644 --- a/src/policy/ephemeral_policy.h +++ b/src/policy/ephemeral_policy.h @@ -5,17 +5,22 @@ #ifndef BITCOIN_POLICY_EPHEMERAL_POLICY_H #define BITCOIN_POLICY_EPHEMERAL_POLICY_H +#include #include -#include #include -#include + +#include + +class CFeeRate; +class CTxMemPool; +class TxValidationState; /** These utility functions ensure that ephemeral dust is safely * created and spent without unduly risking them entering the utxo * set. * This is ensured by requiring: - * - CheckValidEphemeralTx checks are respected + * - PreCheckEphemeralTx checks are respected * - The parent has no child (and 0-fee as implied above to disincentivize mining) * - OR the parent transaction has exactly one child, and the dust is spent by that child * @@ -25,7 +30,7 @@ * TxC, spends TxA's dust * * All the dust is spent if TxA+TxB+TxC is accepted, but the mining template may just pick - * up TxA+TxB rather than the three "legal configurations: + * up TxA+TxB rather than the three "legal configurations": * 1) None * 2) TxA+TxB+TxC * 3) TxA+TxC @@ -34,22 +39,20 @@ * are the only way to bring fees. */ -/** Returns true if transaction contains dust */ -bool HasDust(const CTransactionRef& tx, CFeeRate dust_relay_rate); - /* All the following checks are only called if standardness rules are being applied. */ /** Must be called for each transaction once transaction fees are known. * Does context-less checks about a single transaction. - * Returns false if the fee is non-zero and dust exists, populating state. True otherwise. + * @returns false if the fee is non-zero and dust exists, populating state. True otherwise. */ -bool CheckValidEphemeralTx(const CTransactionRef& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state); +bool PreCheckEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state); /** Must be called for each transaction(package) if any dust is in the package. * Checks that each transaction's parents have their dust spent by the child, * where parents are either in the mempool or in the package itself. - * The function returns std::nullopt if all dust is properly spent, or the txid of the violating child spend. + * Sets out_child_state and out_child_txid on failure. + * @returns true if all dust is properly spent. */ -std::optional CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool); +bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Txid& out_child_txid); #endif // BITCOIN_POLICY_EPHEMERAL_POLICY_H diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 077167dcd6..0f0cb3427f 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -67,6 +67,15 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn)); } +std::vector GetDust(const CTransaction& tx, CFeeRate dust_relay_rate) +{ + std::vector dust_outputs; + for (uint32_t i{0}; i < tx.vout.size(); ++i) { + if (IsDust(tx.vout[i], dust_relay_rate)) dust_outputs.push_back(i); + } + return dust_outputs; +} + bool IsStandard(const CScript& scriptPubKey, const std::optional& max_datacarrier_bytes, TxoutType& whichType) { std::vector > vSolutions; @@ -130,7 +139,6 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat } unsigned int nDataOut = 0; - unsigned int num_dust_outputs{0}; TxoutType whichType; for (const CTxOut& txout : tx.vout) { if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) { @@ -143,13 +151,11 @@ bool IsStandardTx(const CTransaction& tx, const std::optional& max_dat else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) { reason = "bare-multisig"; return false; - } else if (IsDust(txout, dust_relay_fee)) { - num_dust_outputs++; } } // Only MAX_DUST_OUTPUTS_PER_TX dust is permitted(on otherwise valid ephemeral dust) - if (num_dust_outputs > MAX_DUST_OUTPUTS_PER_TX) { + if (GetDust(tx, dust_relay_fee).size() > MAX_DUST_OUTPUTS_PER_TX) { reason = "dust"; return false; } diff --git a/src/policy/policy.h b/src/policy/policy.h index 55bb85727b..6fd3c879c5 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -132,6 +132,8 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee); bool IsStandard(const CScript& scriptPubKey, const std::optional& max_datacarrier_bytes, TxoutType& whichType); +/** Get the vout index numbers of all dust outputs */ +std::vector GetDust(const CTransaction& tx, CFeeRate dust_relay_rate); // Changing the default transaction version requires a two step process: first // adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index a9483affde..b6835a3a80 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -166,7 +166,7 @@ bool BitcoinUnits::parse(Unit unit, const QString& value, CAmount* val_out) { return false; // More than one dot } - QString whole = parts[0]; + const QString& whole = parts[0]; QString decimals; if(parts.size() > 1) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index dc2f7605d5..9d94419e97 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -59,9 +59,11 @@ #include #include +#include #include #include #include +#include using kernel::CCoinsStats; using kernel::CoinStatsHashType; @@ -2689,6 +2691,235 @@ static RPCHelpMan scanblocks() }; } +static RPCHelpMan getdescriptoractivity() +{ + return RPCHelpMan{"getdescriptoractivity", + "\nGet spend and receive activity associated with a set of descriptors for a set of blocks. " + "This command pairs well with the `relevant_blocks` output of `scanblocks()`.\n" + "This call may take several minutes. If you encounter timeouts, try specifying no RPC timeout (bitcoin-cli -rpcclienttimeout=0)", + { + RPCArg{"blockhashes", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The list of blockhashes to examine for activity. Order doesn't matter. Must be along main chain or an error is thrown.\n", { + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A valid blockhash"}, + }}, + scan_objects_arg_desc, + {"include_mempool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include unconfirmed activity"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::ARR, "activity", "events", { + {RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR, "type", "always 'spend'"}, + {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the spent output"}, + {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The blockhash this spend appears in (omitted if unconfirmed)"}, + {RPCResult::Type::NUM, "height", /*optional=*/true, "Height of the spend (omitted if unconfirmed)"}, + {RPCResult::Type::STR_HEX, "spend_txid", "The txid of the spending transaction"}, + {RPCResult::Type::NUM, "spend_vout", "The vout of the spend"}, + {RPCResult::Type::STR_HEX, "prevout_txid", "The txid of the prevout"}, + {RPCResult::Type::NUM, "prevout_vout", "The vout of the prevout"}, + {RPCResult::Type::OBJ, "prevout_spk", "", ScriptPubKeyDoc()}, + }}, + {RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR, "type", "always 'receive'"}, + {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the new output"}, + {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block that this receive is in (omitted if unconfirmed)"}, + {RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the receive (omitted if unconfirmed)"}, + {RPCResult::Type::STR_HEX, "txid", "The txid of the receiving transaction"}, + {RPCResult::Type::NUM, "vout", "The vout of the receiving output"}, + {RPCResult::Type::OBJ, "output_spk", "", ScriptPubKeyDoc()}, + }}, + // TODO is the skip_type_check avoidable with a heterogeneous ARR? + }, /*skip_type_check=*/true}, + }, + }, + RPCExamples{ + HelpExampleCli("getdescriptoractivity", "'[\"000000000000000000001347062c12fded7c528943c8ce133987e2e2f5a840ee\"]' '[\"addr(bc1qzl6nsgqzu89a66l50cvwapnkw5shh23zarqkw9)\"]'") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + UniValue ret(UniValue::VOBJ); + UniValue activity(UniValue::VARR); + NodeContext& node = EnsureAnyNodeContext(request.context); + ChainstateManager& chainman = EnsureChainman(node); + + struct CompareByHeightAscending { + bool operator()(const CBlockIndex* a, const CBlockIndex* b) const { + return a->nHeight < b->nHeight; + } + }; + + std::set blockindexes_sorted; + + { + // Validate all given blockhashes, and ensure blocks are along a single chain. + LOCK(::cs_main); + for (const UniValue& blockhash : request.params[0].get_array().getValues()) { + uint256 bhash = ParseHashV(blockhash, "blockhash"); + CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(bhash); + if (!pindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + if (!chainman.ActiveChain().Contains(pindex)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain"); + } + blockindexes_sorted.insert(pindex); + } + } + + std::set scripts_to_watch; + + // Determine scripts to watch. + for (const UniValue& scanobject : request.params[1].get_array().getValues()) { + FlatSigningProvider provider; + std::vector scripts = EvalDescriptorStringOrObject(scanobject, provider); + + for (const CScript& script : scripts) { + scripts_to_watch.insert(script); + } + } + + const auto AddSpend = [&]( + const CScript& spk, + const CAmount val, + const CTransactionRef& tx, + int vin, + const CTxIn& txin, + const CBlockIndex* index + ) { + UniValue event(UniValue::VOBJ); + UniValue spkUv(UniValue::VOBJ); + ScriptToUniv(spk, /*out=*/spkUv, /*include_hex=*/true, /*include_address=*/true); + + event.pushKV("type", "spend"); + event.pushKV("amount", ValueFromAmount(val)); + if (index) { + event.pushKV("blockhash", index->GetBlockHash().ToString()); + event.pushKV("height", index->nHeight); + } + event.pushKV("spend_txid", tx->GetHash().ToString()); + event.pushKV("spend_vin", vin); + event.pushKV("prevout_txid", txin.prevout.hash.ToString()); + event.pushKV("prevout_vout", txin.prevout.n); + event.pushKV("prevout_spk", spkUv); + + return event; + }; + + const auto AddReceive = [&](const CTxOut& txout, const CBlockIndex* index, int vout, const CTransactionRef& tx) { + UniValue event(UniValue::VOBJ); + UniValue spkUv(UniValue::VOBJ); + ScriptToUniv(txout.scriptPubKey, /*out=*/spkUv, /*include_hex=*/true, /*include_address=*/true); + + event.pushKV("type", "receive"); + event.pushKV("amount", ValueFromAmount(txout.nValue)); + if (index) { + event.pushKV("blockhash", index->GetBlockHash().ToString()); + event.pushKV("height", index->nHeight); + } + event.pushKV("txid", tx->GetHash().ToString()); + event.pushKV("vout", vout); + event.pushKV("output_spk", spkUv); + + return event; + }; + + BlockManager* blockman; + Chainstate& active_chainstate = chainman.ActiveChainstate(); + { + LOCK(::cs_main); + blockman = CHECK_NONFATAL(&active_chainstate.m_blockman); + } + + for (const CBlockIndex* blockindex : blockindexes_sorted) { + const CBlock block{GetBlockChecked(chainman.m_blockman, *blockindex)}; + const CBlockUndo block_undo{GetUndoChecked(*blockman, *blockindex)}; + + for (size_t i = 0; i < block.vtx.size(); ++i) { + const auto& tx = block.vtx.at(i); + + if (!tx->IsCoinBase()) { + // skip coinbase; spends can't happen there. + const auto& txundo = block_undo.vtxundo.at(i - 1); + + for (size_t vin_idx = 0; vin_idx < tx->vin.size(); ++vin_idx) { + const auto& coin = txundo.vprevout.at(vin_idx); + const auto& txin = tx->vin.at(vin_idx); + if (scripts_to_watch.contains(coin.out.scriptPubKey)) { + activity.push_back(AddSpend( + coin.out.scriptPubKey, coin.out.nValue, tx, vin_idx, txin, blockindex)); + } + } + } + + for (size_t vout_idx = 0; vout_idx < tx->vout.size(); ++vout_idx) { + const auto& vout = tx->vout.at(vout_idx); + if (scripts_to_watch.contains(vout.scriptPubKey)) { + activity.push_back(AddReceive(vout, blockindex, vout_idx, tx)); + } + } + } + } + + bool search_mempool = true; + if (!request.params[2].isNull()) { + search_mempool = request.params[2].get_bool(); + } + + if (search_mempool) { + const CTxMemPool& mempool = EnsureMemPool(node); + LOCK(::cs_main); + LOCK(mempool.cs); + const CCoinsViewCache& coins_view = &active_chainstate.CoinsTip(); + + for (const CTxMemPoolEntry& e : mempool.entryAll()) { + const auto& tx = e.GetSharedTx(); + + for (size_t vin_idx = 0; vin_idx < tx->vin.size(); ++vin_idx) { + CScript scriptPubKey; + CAmount value; + const auto& txin = tx->vin.at(vin_idx); + std::optional coin = coins_view.GetCoin(txin.prevout); + + // Check if the previous output is in the chain + if (!coin) { + // If not found in the chain, check the mempool. Likely, this is a + // child transaction of another transaction in the mempool. + CTransactionRef prev_tx = CHECK_NONFATAL(mempool.get(txin.prevout.hash)); + + if (txin.prevout.n >= prev_tx->vout.size()) { + throw std::runtime_error("Invalid output index"); + } + const CTxOut& out = prev_tx->vout[txin.prevout.n]; + scriptPubKey = out.scriptPubKey; + value = out.nValue; + } else { + // Coin found in the chain + const CTxOut& out = coin->out; + scriptPubKey = out.scriptPubKey; + value = out.nValue; + } + + if (scripts_to_watch.contains(scriptPubKey)) { + UniValue event(UniValue::VOBJ); + activity.push_back(AddSpend( + scriptPubKey, value, tx, vin_idx, txin, nullptr)); + } + } + + for (size_t vout_idx = 0; vout_idx < tx->vout.size(); ++vout_idx) { + const auto& vout = tx->vout.at(vout_idx); + if (scripts_to_watch.contains(vout.scriptPubKey)) { + activity.push_back(AddReceive(vout, nullptr, vout_idx, tx)); + } + } + } + } + + ret.pushKV("activity", activity); + return ret; +}, + }; +} + static RPCHelpMan getblockfilter() { return RPCHelpMan{"getblockfilter", @@ -3256,6 +3487,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t) {"blockchain", &preciousblock}, {"blockchain", &scantxoutset}, {"blockchain", &scanblocks}, + {"blockchain", &getdescriptoractivity}, {"blockchain", &getblockfilter}, {"blockchain", &dumptxoutset}, {"blockchain", &loadtxoutset}, diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 025ccf4ab6..d405444864 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -92,6 +92,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "scanblocks", 3, "stop_height" }, { "scanblocks", 5, "options" }, { "scanblocks", 5, "filter_false_positives" }, + { "getdescriptoractivity", 0, "blockhashes" }, + { "getdescriptoractivity", 1, "scanobjects" }, + { "getdescriptoractivity", 2, "include_mempool" }, { "scantxoutset", 1, "scanobjects" }, { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index ad1b2791e2..386e686628 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -499,7 +499,7 @@ static RPCHelpMan prioritisetransaction() // Non-0 fee dust transactions are not allowed for entry, and modification not allowed afterwards const auto& tx = mempool.get(hash); - if (tx && HasDust(tx, mempool.m_opts.dust_relay_feerate)) { + if (mempool.m_opts.require_standard && tx && !GetDust(*tx, mempool.m_opts.dust_relay_feerate).empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Priority is not supported for transactions with dust outputs."); } diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 644077b2ad..e50c5bd8ed 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -123,6 +123,7 @@ static RPCHelpMan createmultisig() // Get the public keys const UniValue& keys = request.params[1].get_array(); std::vector pubkeys; + pubkeys.reserve(keys.size()); for (unsigned int i = 0; i < keys.size(); ++i) { pubkeys.push_back(HexToPubKey(keys[i].get_str())); } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 3bcb1249e7..bb9b6790db 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -82,18 +82,6 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, } } -static std::vector ScriptPubKeyDoc() { - return - { - {RPCResult::Type::STR, "asm", "Disassembly of the output script"}, - {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, - {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, - {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"}, - NameOpResult, - }; -} - static std::vector DecodeTxDoc(const std::string& txid_field_doc) { return { diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 213beac0c1..63d0abd12f 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include