diff options
33 files changed, 344 insertions, 140 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index eaaa2c471f..29116c9940 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -321,32 +321,10 @@ task: task: name: 'macOS 11.0 [gui, no tests] [jammy]' - << : *CONTAINER_DEPENDS_TEMPLATE + << : *GLOBAL_TASK_TEMPLATE container: docker_arguments: CI_IMAGE_NAME_TAG: ubuntu:jammy FILE_ENV: "./ci/test/00_setup_env_mac.sh" - macos_sdk_cache: - folder: "depends/SDKs/$MACOS_SDK" - fingerprint_key: "$MACOS_SDK" - << : *MAIN_TEMPLATE - env: - MACOS_SDK: "Xcode-12.2-12B45b-extracted-SDK-with-libcxx-headers" - << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV - -task: - name: 'macOS 13 native arm64 [gui, sqlite only] [no depends]' - macos_instance: - # Use latest image, but hardcode version to avoid silent upgrades (and breaks) - image: ghcr.io/cirruslabs/macos-ventura-xcode:14.3.1 # https://cirrus-ci.org/guide/macOS - << : *BASE_TEMPLATE - check_clang_script: - - clang --version - brew_install_script: - - brew install boost libevent qt@5 miniupnpc libnatpmp ccache zeromq qrencode libtool automake gnu-getopt - << : *MAIN_TEMPLATE env: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV - CI_USE_APT_INSTALL: "no" - PACKAGE_MANAGER_INSTALL: "echo" # Nothing to do - FILE_ENV: "./ci/test/00_setup_env_mac_native_arm64.sh" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..f6d22f033f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +# Copyright (c) 2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +name: CI +on: + # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request. + pull_request: + # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push. + push: + branches: + - '**' + tags-ignore: + - '**' + +env: + DANGER_RUN_CI_ON_HOST: 1 + TEST_RUNNER_TIMEOUT_FACTOR: 40 + +jobs: + macos-native-x86_64: + name: macOS 13 native, x86_64 [no depends, sqlite only, gui] + # Use latest image, but hardcode version to avoid silent upgrades (and breaks). + # See: https://github.com/actions/runner-images#available-images. + runs-on: macos-13 + + # No need to run on the read-only mirror, unless it is a PR. + if: github.repository != 'bitcoin-core/gui' || github.event_name == 'pull_request' + + timeout-minutes: 120 + + env: + MAKEJOBS: '-j4' + CI_USE_APT_INSTALL: 'no' + PACKAGE_MANAGER_INSTALL: 'echo' # Nothing to do + FILE_ENV: './ci/test/00_setup_env_mac_native.sh' + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Clang version + run: clang --version + + - name: Install Homebrew packages + run: brew install boost libevent qt@5 miniupnpc libnatpmp ccache zeromq qrencode libtool automake gnu-getopt + + - name: Set Ccache directory + run: echo "CCACHE_DIR=${RUNNER_TEMP}/ccache_dir" >> "$GITHUB_ENV" + + - name: Ccache cache + uses: actions/cache@v3 + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ github.job }}-ccache-cache-${{ github.run_id }} + restore-keys: ${{ github.job }}-ccache-cache + + - name: CI script + run: ./ci/test_run_all.sh diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index 75b47af0ed..3014714a44 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -8,10 +8,18 @@ export LC_ALL=C.UTF-8 set -ex -# The root dir. +# The source root dir, usually from git, usually read-only. # The ci system copies this folder. -BASE_ROOT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../../ >/dev/null 2>&1 && pwd ) -export BASE_ROOT_DIR +BASE_READ_ONLY_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../../ >/dev/null 2>&1 && pwd ) +export BASE_READ_ONLY_DIR +# The destination root dir inside the container. +if [ -z "${DANGER_RUN_CI_ON_HOST}" ] ; then + # This folder only exists on the ci guest and will be a copy of BASE_READ_ONLY_DIR + export BASE_ROOT_DIR="/ci_container_base" +else + # This folder is equal to BASE_READ_ONLY_DIR and is read-write + export BASE_ROOT_DIR="${BASE_READ_ONLY_DIR}" +fi # The depends dir. # This folder exists only on the ci guest, and on the ci host as a volume. export DEPENDS_DIR=${DEPENDS_DIR:-$BASE_ROOT_DIR/depends} diff --git a/ci/test/00_setup_env_mac_native_arm64.sh b/ci/test/00_setup_env_mac_native.sh index eee72db435..c9f65bf397 100755 --- a/ci/test/00_setup_env_mac_native_arm64.sh +++ b/ci/test/00_setup_env_mac_native.sh @@ -1,18 +1,18 @@ #!/usr/bin/env bash # -# Copyright (c) 2019-2022 The Bitcoin Core developers +# Copyright (c) 2019-present The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. export LC_ALL=C.UTF-8 -export HOST=arm64-apple-darwin +export HOST=x86_64-apple-darwin export PIP_PACKAGES="zmq" export GOAL="install" export BITCOIN_CONFIG="--with-gui --with-miniupnpc --with-natpmp --enable-reduce-exports" export CI_OS_NAME="macos" export NO_DEPENDS=1 export OSX_SDK="" -export CCACHE_MAXSIZE=300M +export CCACHE_MAXSIZE=400M export RUN_FUZZ_TESTS=true export FUZZ_TESTS_CONFIG="--exclude banman" # https://github.com/bitcoin/bitcoin/issues/27924 diff --git a/ci/test/00_setup_env_native_fuzz_with_msan.sh b/ci/test/00_setup_env_native_fuzz_with_msan.sh index 89006bf95e..007bb4bedf 100755 --- a/ci/test/00_setup_env_native_fuzz_with_msan.sh +++ b/ci/test/00_setup_env_native_fuzz_with_msan.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export CI_IMAGE_NAME_TAG="ubuntu:22.04" -LIBCXX_DIR="${BASE_SCRATCH_DIR}/msan/cxx_build/" +LIBCXX_DIR="/msan/cxx_build/" export MSAN_FLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O1 -fno-optimize-sibling-calls" LIBCXX_FLAGS="-nostdinc++ -nostdlib++ -isystem ${LIBCXX_DIR}include/c++/v1 -L${LIBCXX_DIR}lib -Wl,-rpath,${LIBCXX_DIR}lib -lc++ -lc++abi -lpthread -Wno-unused-command-line-argument" export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}" diff --git a/ci/test/00_setup_env_native_msan.sh b/ci/test/00_setup_env_native_msan.sh index a96ed30dd3..1d39ef9c48 100755 --- a/ci/test/00_setup_env_native_msan.sh +++ b/ci/test/00_setup_env_native_msan.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export CI_IMAGE_NAME_TAG="ubuntu:22.04" -LIBCXX_DIR="${BASE_SCRATCH_DIR}/msan/cxx_build/" +LIBCXX_DIR="/msan/cxx_build/" export MSAN_FLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O1 -fno-optimize-sibling-calls" LIBCXX_FLAGS="-nostdinc++ -nostdlib++ -isystem ${LIBCXX_DIR}include/c++/v1 -L${LIBCXX_DIR}lib -Wl,-rpath,${LIBCXX_DIR}lib -lc++ -lc++abi -lpthread -Wno-unused-command-line-argument" export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}" diff --git a/ci/test/01_base_install.sh b/ci/test/01_base_install.sh index 83213b2856..c9a496e6ab 100755 --- a/ci/test/01_base_install.sh +++ b/ci/test/01_base_install.sh @@ -42,33 +42,35 @@ if [ -n "$PIP_PACKAGES" ]; then fi if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then - git clone --depth=1 https://github.com/llvm/llvm-project -b llvmorg-16.0.6 "${BASE_SCRATCH_DIR}"/msan/llvm-project - - cmake -G Ninja -B "${BASE_SCRATCH_DIR}"/msan/clang_build/ -DLLVM_ENABLE_PROJECTS="clang" \ - -DCMAKE_BUILD_TYPE=Release \ - -DLLVM_TARGETS_TO_BUILD=Native \ - -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \ - -S "${BASE_SCRATCH_DIR}"/msan/llvm-project/llvm - - ninja -C "${BASE_SCRATCH_DIR}"/msan/clang_build/ "$MAKEJOBS" - ninja -C "${BASE_SCRATCH_DIR}"/msan/clang_build/ install-runtimes - - update-alternatives --install /usr/bin/clang++ clang++ "${BASE_SCRATCH_DIR}"/msan/clang_build/bin/clang++ 100 - update-alternatives --install /usr/bin/clang clang "${BASE_SCRATCH_DIR}"/msan/clang_build/bin/clang 100 - update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer "${BASE_SCRATCH_DIR}"/msan/clang_build/bin/llvm-symbolizer 100 - - cmake -G Ninja -B "${BASE_SCRATCH_DIR}"/msan/cxx_build/ -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi' \ - -DCMAKE_BUILD_TYPE=Release \ - -DLLVM_USE_SANITIZER=MemoryWithOrigins \ - -DCMAKE_C_COMPILER=clang \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DLLVM_TARGETS_TO_BUILD=Native \ - -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF \ - -DLIBCXX_ENABLE_DEBUG_MODE=ON \ - -DLIBCXX_ENABLE_ASSERTIONS=ON \ - -S "${BASE_SCRATCH_DIR}"/msan/llvm-project/runtimes - - ninja -C "${BASE_SCRATCH_DIR}"/msan/cxx_build/ "$MAKEJOBS" + git clone --depth=1 https://github.com/llvm/llvm-project -b llvmorg-16.0.6 /msan/llvm-project + + cmake -G Ninja -B /msan/clang_build/ \ + -DLLVM_ENABLE_PROJECTS="clang" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_TARGETS_TO_BUILD=Native \ + -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \ + -S /msan/llvm-project/llvm + + ninja -C /msan/clang_build/ "$MAKEJOBS" + ninja -C /msan/clang_build/ install-runtimes + + update-alternatives --install /usr/bin/clang++ clang++ /msan/clang_build/bin/clang++ 100 + update-alternatives --install /usr/bin/clang clang /msan/clang_build/bin/clang 100 + update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /msan/clang_build/bin/llvm-symbolizer 100 + + cmake -G Ninja -B /msan/cxx_build/ \ + -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi' \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_USE_SANITIZER=MemoryWithOrigins \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DLLVM_TARGETS_TO_BUILD=Native \ + -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF \ + -DLIBCXX_ENABLE_DEBUG_MODE=ON \ + -DLIBCXX_ENABLE_ASSERTIONS=ON \ + -S /msan/llvm-project/runtimes + + ninja -C /msan/cxx_build/ "$MAKEJOBS" fi if [[ "${RUN_TIDY}" == "true" ]]; then diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index 7ecf00097c..99e16bcb98 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -6,10 +6,6 @@ export LC_ALL=C.UTF-8 -# Create folders that are mounted into the docker -mkdir -p "${CCACHE_DIR}" -mkdir -p "${PREVIOUS_RELEASES_DIR}" - export ASAN_OPTIONS="detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1" export LSAN_OPTIONS="suppressions=${BASE_ROOT_DIR}/test/sanitizer_suppressions/lsan" export TSAN_OPTIONS="suppressions=${BASE_ROOT_DIR}/test/sanitizer_suppressions/tsan:halt_on_error=1:log_path=${BASE_SCRATCH_DIR}/sanitizer-output/tsan" @@ -23,11 +19,11 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then docker run --rm "${CI_IMAGE_NAME_TAG}" bash -c "env | grep --extended-regexp '^(HOME|PATH|USER)='" | tee --append /tmp/env echo "Creating $CI_IMAGE_NAME_TAG container to run in" DOCKER_BUILDKIT=1 docker build \ - --file "${BASE_ROOT_DIR}/ci/test_imagefile" \ + --file "${BASE_READ_ONLY_DIR}/ci/test_imagefile" \ --build-arg "CI_IMAGE_NAME_TAG=${CI_IMAGE_NAME_TAG}" \ --build-arg "FILE_ENV=${FILE_ENV}" \ --tag="${CONTAINER_NAME}" \ - "${BASE_ROOT_DIR}" + "${BASE_READ_ONLY_DIR}" docker volume create "${CONTAINER_NAME}_ccache" || true docker volume create "${CONTAINER_NAME}_depends" || true docker volume create "${CONTAINER_NAME}_previous_releases" || true @@ -41,7 +37,7 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then # shellcheck disable=SC2086 CI_CONTAINER_ID=$(docker run $CI_CONTAINER_CAP --rm --interactive --detach --tty \ - --mount type=bind,src=$BASE_ROOT_DIR,dst=/ro_base,readonly \ + --mount type=bind,src=$BASE_READ_ONLY_DIR,dst=/ro_base,readonly \ --mount "type=volume,src=${CONTAINER_NAME}_ccache,dst=$CCACHE_DIR" \ --mount "type=volume,src=${CONTAINER_NAME}_depends,dst=$DEPENDS_DIR" \ --mount "type=volume,src=${CONTAINER_NAME}_previous_releases,dst=$PREVIOUS_RELEASES_DIR" \ @@ -52,6 +48,9 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then export CI_EXEC_CMD_PREFIX="docker exec ${CI_CONTAINER_ID}" else echo "Running on host system without docker wrapper" + echo "Create missing folders" + mkdir -p "${CCACHE_DIR}" + mkdir -p "${PREVIOUS_RELEASES_DIR}" fi CI_EXEC () { diff --git a/contrib/devtools/bitcoin-tidy/CMakeLists.txt b/contrib/devtools/bitcoin-tidy/CMakeLists.txt index 9ed18696d4..35e60d1d87 100644 --- a/contrib/devtools/bitcoin-tidy/CMakeLists.txt +++ b/contrib/devtools/bitcoin-tidy/CMakeLists.txt @@ -25,6 +25,12 @@ else() target_compile_options(bitcoin-tidy PRIVATE -fno-exceptions) endif() +if(CMAKE_HOST_APPLE) + # ld64 expects no undefined symbols by default + target_link_options(bitcoin-tidy PRIVATE -Wl,-flat_namespace) + target_link_options(bitcoin-tidy PRIVATE -Wl,-undefined -Wl,suppress) +endif() + # Add warnings if (MSVC) target_compile_options(bitcoin-tidy PRIVATE /W4) @@ -33,7 +39,12 @@ else() target_compile_options(bitcoin-tidy PRIVATE -Wextra) endif() -set(CLANG_TIDY_COMMAND "${CLANG_TIDY_EXE}" "--load=${CMAKE_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}bitcoin-tidy${CMAKE_SHARED_LIBRARY_SUFFIX}" "-checks=-*,bitcoin-*") +if(CMAKE_VERSION VERSION_LESS 3.27) + set(CLANG_TIDY_COMMAND "${CLANG_TIDY_EXE}" "--load=${CMAKE_BINARY_DIR}/${CMAKE_SHARED_MODULE_PREFIX}bitcoin-tidy${CMAKE_SHARED_MODULE_SUFFIX}" "-checks=-*,bitcoin-*") +else() + # CLANG_TIDY_COMMAND supports generator expressions as of 3.27 + set(CLANG_TIDY_COMMAND "${CLANG_TIDY_EXE}" "--load=$<TARGET_FILE:bitcoin-tidy>" "-checks=-*,bitcoin-*") +endif() # Create a dummy library that runs clang-tidy tests as a side-effect of building add_library(bitcoin-tidy-tests OBJECT EXCLUDE_FROM_ALL example_logprintf.cpp) diff --git a/contrib/devtools/bitcoin-tidy/README b/contrib/devtools/bitcoin-tidy/README index 1669fe98f5..c15e07c4ed 100644 --- a/contrib/devtools/bitcoin-tidy/README +++ b/contrib/devtools/bitcoin-tidy/README @@ -3,6 +3,9 @@ Example Usage: ```bash -cmake -S . -B build -DLLVM_DIR=/path/to/lib/cmake/llvm -DCMAKE_BUILD_TYPE=Release -make -C build -j$(nproc) +cmake -S . -B build -DLLVM_DIR=$(llvm-config --cmakedir) -DCMAKE_BUILD_TYPE=Release + +cmake --build build -j$(nproc) + +cmake --build build --target bitcoin-tidy-tests -j$(nproc) ``` diff --git a/src/bip324.cpp b/src/bip324.cpp index 7ed99e5585..314e756829 100644 --- a/src/bip324.cpp +++ b/src/bip324.cpp @@ -8,14 +8,19 @@ #include <crypto/chacha20.h> #include <crypto/chacha20poly1305.h> #include <crypto/hkdf_sha256_32.h> +#include <key.h> +#include <pubkey.h> #include <random.h> #include <span.h> #include <support/cleanse.h> +#include <uint256.h> #include <algorithm> #include <assert.h> #include <cstdint> #include <cstddef> +#include <iterator> +#include <string> BIP324Cipher::BIP324Cipher() noexcept { diff --git a/src/bip324.h b/src/bip324.h index 8d025c2ee3..0238c479c0 100644 --- a/src/bip324.h +++ b/src/bip324.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_BIP324_H #define BITCOIN_BIP324_H +#include <array> #include <cstddef> #include <optional> @@ -54,6 +55,7 @@ public: /** Initialize when the other side's public key is received. Can only be called once. * + * initiator is set to true if we are the initiator establishing the v2 P2P connection. * self_decrypt is only for testing, and swaps encryption/decryption keys, so that encryption * and decryption can be tested without knowing the other side's private key. */ diff --git a/src/compat/compat.h b/src/compat/compat.h index 8195bceaec..435a403552 100644 --- a/src/compat/compat.h +++ b/src/compat/compat.h @@ -22,19 +22,18 @@ #include <ws2tcpip.h> #include <cstdint> #else -#include <fcntl.h> -#include <sys/mman.h> -#include <sys/select.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <net/if.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <arpa/inet.h> -#include <ifaddrs.h> -#include <limits.h> -#include <netdb.h> -#include <unistd.h> +#include <arpa/inet.h> // IWYU pragma: export +#include <fcntl.h> // IWYU pragma: export +#include <ifaddrs.h> // IWYU pragma: export +#include <net/if.h> // IWYU pragma: export +#include <netdb.h> // IWYU pragma: export +#include <netinet/in.h> // IWYU pragma: export +#include <netinet/tcp.h> // IWYU pragma: export +#include <sys/mman.h> // IWYU pragma: export +#include <sys/select.h> // IWYU pragma: export +#include <sys/socket.h> // IWYU pragma: export +#include <sys/types.h> // IWYU pragma: export +#include <unistd.h> // IWYU pragma: export #endif // We map Linux / BSD error functions and codes, to the equivalent diff --git a/src/crypto/chacha20poly1305.cpp b/src/crypto/chacha20poly1305.cpp index c936dd2265..26161641bb 100644 --- a/src/crypto/chacha20poly1305.cpp +++ b/src/crypto/chacha20poly1305.cpp @@ -11,9 +11,7 @@ #include <support/cleanse.h> #include <assert.h> -#include <cstdint> #include <cstddef> -#include <iterator> AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept : m_chacha20(UCharCast(key.data())) { @@ -95,7 +93,7 @@ bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std: m_chacha20.Seek64(nonce, 0); std::byte expected_tag[EXPANSION]; ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag); - if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.data() + cipher.size() - EXPANSION), EXPANSION)) return false; + if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.last(EXPANSION).data()), EXPANSION)) return false; // Decrypt (starting at block 1). m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain1.data()), plain1.size()); diff --git a/src/crypto/chacha20poly1305.h b/src/crypto/chacha20poly1305.h index dc8fb1cc13..a847c258ef 100644 --- a/src/crypto/chacha20poly1305.h +++ b/src/crypto/chacha20poly1305.h @@ -6,7 +6,6 @@ #define BITCOIN_CRYPTO_CHACHA20POLY1305_H #include <cstddef> -#include <cstdlib> #include <stdint.h> #include <crypto/chacha20.h> diff --git a/src/init.cpp b/src/init.cpp index 33389e695c..c2c4dbe459 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -116,6 +116,7 @@ #endif using kernel::DumpMempool; +using kernel::LoadMempool; using kernel::ValidationCacheSizes; using node::ApplyArgsManOptions; @@ -1676,7 +1677,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return; } // Load mempool from disk - chainman.ActiveChainstate().LoadMempool(ShouldPersistMempool(args) ? MempoolPath(args) : fs::path{}); + if (auto* pool{chainman.ActiveChainstate().GetMempool()}) { + LoadMempool(*pool, ShouldPersistMempool(args) ? MempoolPath(args) : fs::path{}, chainman.ActiveChainstate(), {}); + pool->SetLoadTried(!chainman.m_interrupt); + } }); // Wait for genesis block to be processed diff --git a/src/kernel/mempool_persist.cpp b/src/kernel/mempool_persist.cpp index d060e45af3..6be07da222 100644 --- a/src/kernel/mempool_persist.cpp +++ b/src/kernel/mempool_persist.cpp @@ -19,7 +19,6 @@ #include <util/time.h> #include <validation.h> -#include <chrono> #include <cstdint> #include <cstdio> #include <exception> @@ -37,11 +36,11 @@ namespace kernel { static const uint64_t MEMPOOL_DUMP_VERSION = 1; -bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, FopenFn mockable_fopen_function) +bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts) { if (load_path.empty()) return false; - FILE* filestr{mockable_fopen_function(load_path, "rb")}; + FILE* filestr{opts.mockable_fopen_function(load_path, "rb")}; CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); if (file.IsNull()) { LogPrintf("Failed to open mempool file from disk. Continuing anyway.\n"); @@ -53,7 +52,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active int64_t failed = 0; int64_t already_there = 0; int64_t unbroadcast = 0; - auto now = NodeClock::now(); + const auto now{NodeClock::now()}; try { uint64_t version; @@ -72,8 +71,12 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active file >> nTime; file >> nFeeDelta; + if (opts.use_current_time) { + nTime = TicksSinceEpoch<std::chrono::seconds>(now); + } + CAmount amountdelta = nFeeDelta; - if (amountdelta) { + if (amountdelta && opts.apply_fee_delta_priority) { pool.PrioritiseTransaction(tx->GetHash(), amountdelta); } if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_expiry)) { @@ -101,17 +104,21 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active std::map<uint256, CAmount> mapDeltas; file >> mapDeltas; - for (const auto& i : mapDeltas) { - pool.PrioritiseTransaction(i.first, i.second); + if (opts.apply_fee_delta_priority) { + for (const auto& i : mapDeltas) { + pool.PrioritiseTransaction(i.first, i.second); + } } std::set<uint256> unbroadcast_txids; file >> unbroadcast_txids; - unbroadcast = unbroadcast_txids.size(); - for (const auto& txid : unbroadcast_txids) { - // Ensure transactions were accepted to mempool then add to - // unbroadcast set. - if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid); + if (opts.apply_unbroadcast_set) { + unbroadcast = unbroadcast_txids.size(); + for (const auto& txid : unbroadcast_txids) { + // Ensure transactions were accepted to mempool then add to + // unbroadcast set. + if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid); + } } } catch (const std::exception& e) { LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what()); diff --git a/src/kernel/mempool_persist.h b/src/kernel/mempool_persist.h index cb09119e4a..e124a8eadf 100644 --- a/src/kernel/mempool_persist.h +++ b/src/kernel/mempool_persist.h @@ -12,15 +12,21 @@ class CTxMemPool; namespace kernel { -/** Dump the mempool to disk. */ +/** Dump the mempool to a file. */ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen, bool skip_file_commit = false); -/** Load the mempool from disk. */ +struct ImportMempoolOptions { + fsbridge::FopenFn mockable_fopen_function{fsbridge::fopen}; + bool use_current_time{false}; + bool apply_fee_delta_priority{true}; + bool apply_unbroadcast_set{true}; +}; +/** Import the file and attempt to add its contents to the mempool. */ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, - fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen); + ImportMempoolOptions&& opts); } // namespace kernel diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index d289a9240e..2908c37c1f 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -229,6 +229,10 @@ static const CRPCConvertParam vRPCConvertParams[] = { "importaddress", 2, "rescan" }, { "importaddress", 3, "p2sh" }, { "importpubkey", 2, "rescan" }, + { "importmempool", 1, "options" }, + { "importmempool", 1, "apply_fee_delta_priority" }, + { "importmempool", 1, "use_current_time" }, + { "importmempool", 1, "apply_unbroadcast_set" }, { "importmulti", 0, "requests" }, { "importmulti", 1, "options" }, { "importmulti", 1, "rescan" }, diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 11d2874961..90ee2a48af 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -696,7 +696,7 @@ static RPCHelpMan getmempoolinfo() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"}, + {RPCResult::Type::BOOL, "loaded", "True if the initial load attempt of the persisted mempool finished"}, {RPCResult::Type::NUM, "size", "Current tx count"}, {RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"}, {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"}, @@ -719,6 +719,66 @@ static RPCHelpMan getmempoolinfo() }; } +static RPCHelpMan importmempool() +{ + return RPCHelpMan{ + "importmempool", + "Import a mempool.dat file and attempt to add its contents to the mempool.\n" + "Warning: Importing untrusted files is dangerous, especially if metadata from the file is taken over.", + { + {"filepath", RPCArg::Type::STR, RPCArg::Optional::NO, "The mempool file"}, + {"options", + RPCArg::Type::OBJ_NAMED_PARAMS, + RPCArg::Optional::OMITTED, + "", + { + {"use_current_time", RPCArg::Type::BOOL, RPCArg::Default{true}, + "Whether to use the current system time or use the entry time metadata from the mempool file.\n" + "Warning: Importing untrusted metadata may lead to unexpected issues and undesirable behavior."}, + {"apply_fee_delta_priority", RPCArg::Type::BOOL, RPCArg::Default{false}, + "Whether to apply the fee delta metadata from the mempool file.\n" + "It will be added to any existing fee deltas.\n" + "The fee delta can be set by the prioritisetransaction RPC.\n" + "Warning: Importing untrusted metadata may lead to unexpected issues and undesirable behavior.\n" + "Only set this bool if you understand what it does."}, + {"apply_unbroadcast_set", RPCArg::Type::BOOL, RPCArg::Default{false}, + "Whether to apply the unbroadcast set metadata from the mempool file.\n" + "Warning: Importing untrusted metadata may lead to unexpected issues and undesirable behavior."}, + }, + RPCArgOptions{.oneline_description = "\"options\""}}, + }, + RPCResult{RPCResult::Type::OBJ, "", "", std::vector<RPCResult>{}}, + RPCExamples{HelpExampleCli("importmempool", "/path/to/mempool.dat") + HelpExampleRpc("importmempool", "/path/to/mempool.dat")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + const NodeContext& node{EnsureAnyNodeContext(request.context)}; + + CTxMemPool& mempool{EnsureMemPool(node)}; + Chainstate& chainstate = EnsureChainman(node).ActiveChainstate(); + + if (chainstate.IsInitialBlockDownload()) { + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Can only import the mempool after the block download and sync is done."); + } + + const fs::path load_path{fs::u8path(request.params[0].get_str())}; + const UniValue& use_current_time{request.params[1]["use_current_time"]}; + const UniValue& apply_fee_delta{request.params[1]["apply_fee_delta_priority"]}; + const UniValue& apply_unbroadcast{request.params[1]["apply_unbroadcast_set"]}; + kernel::ImportMempoolOptions opts{ + .use_current_time = use_current_time.isNull() ? true : use_current_time.get_bool(), + .apply_fee_delta_priority = apply_fee_delta.isNull() ? false : apply_fee_delta.get_bool(), + .apply_unbroadcast_set = apply_unbroadcast.isNull() ? false : apply_unbroadcast.get_bool(), + }; + + if (!kernel::LoadMempool(mempool, load_path, chainstate, std::move(opts))) { + throw JSONRPCError(RPC_MISC_ERROR, "Unable to import mempool file, see debug.log for details."); + } + + UniValue ret{UniValue::VOBJ}; + return ret; + }, + }; +} + static RPCHelpMan savemempool() { return RPCHelpMan{"savemempool", @@ -921,6 +981,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t) {"blockchain", &gettxspendingprevout}, {"blockchain", &getmempoolinfo}, {"blockchain", &getrawmempool}, + {"blockchain", &importmempool}, {"blockchain", &savemempool}, {"hidden", &submitpackage}, }; diff --git a/src/test/bip324_tests.cpp b/src/test/bip324_tests.cpp index ccb9e59e58..04472611ec 100644 --- a/src/test/bip324_tests.cpp +++ b/src/test/bip324_tests.cpp @@ -6,13 +6,15 @@ #include <chainparams.h> #include <key.h> #include <pubkey.h> +#include <span.h> #include <test/util/random.h> #include <test/util/setup_common.h> #include <util/strencodings.h> #include <array> -#include <vector> #include <cstddef> +#include <cstdint> +#include <vector> #include <boost/test/unit_test.hpp> @@ -131,10 +133,10 @@ void TestBIP324PacketVector( // Decrypt length auto to_decrypt = ciphertext; if (error >= 2 && error <= 9) { - to_decrypt[InsecureRandRange(to_decrypt.size())] ^= std::byte(1U << InsecureRandRange(8)); + to_decrypt[InsecureRandRange(to_decrypt.size())] ^= std::byte(1U << (error - 2)); } - // Decrypt length and resize ciphertext to accomodate. + // Decrypt length and resize ciphertext to accommodate. uint32_t dec_len = dec_cipher.DecryptLength(MakeByteSpan(to_decrypt).first(cipher.LENGTH_LEN)); to_decrypt.resize(dec_len + cipher.EXPANSION); diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 6663c914a9..6fbe74a680 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -300,11 +300,11 @@ static void TestFSChaCha20Poly1305(const std::string& plain_hex, const std::stri for (int it = 0; it < 10; ++it) { // During it==0 we use the single-plain Encrypt/Decrypt; others use a split at prefix. size_t prefix = it ? InsecureRandRange(plain.size() + 1) : plain.size(); + std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}}; // Do msg_idx dummy encryptions to seek to the correct packet. FSChaCha20Poly1305 enc_aead{key, 224}; for (uint64_t i = 0; i < msg_idx; ++i) { - std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}}; enc_aead.Encrypt(Span{dummy_tag}.first(0), Span{dummy_tag}.first(0), dummy_tag); } @@ -319,7 +319,6 @@ static void TestFSChaCha20Poly1305(const std::string& plain_hex, const std::stri // Do msg_idx dummy decryptions to seek to the correct packet. FSChaCha20Poly1305 dec_aead{key, 224}; for (uint64_t i = 0; i < msg_idx; ++i) { - std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}}; dec_aead.Decrypt(dummy_tag, Span{dummy_tag}.first(0), Span{dummy_tag}.first(0)); } diff --git a/src/test/fuzz/bip324.cpp b/src/test/fuzz/bip324.cpp index 359de6c66a..98ac10e364 100644 --- a/src/test/fuzz/bip324.cpp +++ b/src/test/fuzz/bip324.cpp @@ -11,7 +11,6 @@ #include <test/util/xoroshiro128plusplus.h> #include <cstdint> -#include <tuple> #include <vector> namespace { @@ -75,13 +74,13 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize) // - Bit 0: whether the ignore bit is set in message // - Bit 1: whether the responder (0) or initiator (1) sends // - Bit 2: whether this ciphertext will be corrupted (making it the last sent one) - // - Bit 3-4: controls the maximum aad length (max 511 bytes) + // - Bit 3-4: controls the maximum aad length (max 4095 bytes) // - Bit 5-7: controls the maximum content length (max 16383 bytes, for performance reasons) unsigned mode = provider.ConsumeIntegral<uint8_t>(); bool ignore = mode & 1; bool from_init = mode & 2; bool damage = mode & 4; - unsigned aad_length_bits = 3 * ((mode >> 3) & 3); + unsigned aad_length_bits = 4 * ((mode >> 3) & 3); unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1); unsigned length_bits = 2 * ((mode >> 5) & 7); unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1); diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 723dc6420f..b088aa0bd7 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -155,14 +155,16 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view) } assert((exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin && exists_using_get_coin) || (!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin && !exists_using_get_coin)); + // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent. const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point); - if (exists_using_have_coin_in_backend) { + if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) { assert(exists_using_have_coin); } Coin coin_using_backend_get_coin; if (backend_coins_view.GetCoin(random_out_point, coin_using_backend_get_coin)) { assert(exists_using_have_coin_in_backend); - assert(coin_using_get_coin == coin_using_backend_get_coin); + // Note we can't assert that `coin_using_get_coin == coin_using_backend_get_coin` because the coin in + // the cache may have been modified but not yet flushed. } else { assert(!exists_using_have_coin_in_backend); } diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index 2782888dc3..9e76c7be3e 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -78,6 +78,7 @@ const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{ "generatetoaddress", // avoid prohibitively slow execution (when `num_blocks` is large) "generatetodescriptor", // avoid prohibitively slow execution (when `nblocks` is large) "gettxoutproof", // avoid prohibitively slow execution + "importmempool", // avoid reading from disk "importwallet", // avoid reading from disk "loadwallet", // avoid reading from disk "savemempool", // disabled as a precautionary measure: may take a file path argument in the future diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp index c203dd4e39..5d020b4d59 100644 --- a/src/test/fuzz/validation_load_mempool.cpp +++ b/src/test/fuzz/validation_load_mempool.cpp @@ -20,6 +20,7 @@ #include <vector> using kernel::DumpMempool; +using kernel::LoadMempool; using node::MempoolPath; @@ -47,6 +48,10 @@ FUZZ_TARGET(validation_load_mempool, .init = initialize_validation_load_mempool) auto fuzzed_fopen = [&](const fs::path&, const char*) { return fuzzed_file_provider.open(); }; - (void)chainstate.LoadMempool(MempoolPath(g_setup->m_args), fuzzed_fopen); + (void)LoadMempool(pool, MempoolPath(g_setup->m_args), chainstate, + { + .mockable_fopen_function = fuzzed_fopen, + }); + pool.SetLoadTried(true); (void)DumpMempool(pool, MempoolPath(g_setup->m_args), fuzzed_fopen, true); } diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index a8d6fb4b3f..9cf976a700 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -14,14 +14,26 @@ #include <net.h> #include <netaddress.h> #include <netbase.h> +#include <random.h> +#include <tinyformat.h> +#include <util/check.h> +#include <util/fs.h> #include <util/readwritefile.h> #include <util/strencodings.h> +#include <util/string.h> #include <util/thread.h> #include <util/time.h> +#include <algorithm> +#include <cassert> +#include <cstdlib> #include <deque> #include <functional> +#include <map> +#include <optional> #include <set> +#include <thread> +#include <utility> #include <vector> #include <event2/buffer.h> @@ -79,15 +91,15 @@ void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) if (s.size() < 4) // Short line continue; // <status>(-|+| )<data><CRLF> - self->message.code = LocaleIndependentAtoi<int>(s.substr(0,3)); + self->message.code = ToIntegral<int>(s.substr(0, 3)).value_or(0); self->message.lines.push_back(s.substr(4)); char ch = s[3]; // '-','+' or ' ' if (ch == ' ') { // Final line, dispatch reply and clean up if (self->message.code >= 600) { + // (currently unused) // Dispatch async notifications to async handler // Synchronous and asynchronous messages are never interleaved - self->async_handler(*self, self->message); } else { if (!self->reply_handlers.empty()) { // Invoke reply handler with message diff --git a/src/torcontrol.h b/src/torcontrol.h index afc5413db0..1a9065b01a 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -11,19 +11,14 @@ #include <netaddress.h> #include <util/fs.h> -#include <boost/signals2/signal.hpp> +#include <event2/util.h> -#include <event2/bufferevent.h> -#include <event2/event.h> - -#include <cstdlib> +#include <cstdint> #include <deque> #include <functional> #include <string> #include <vector> -class CService; - extern const std::string DEFAULT_TOR_CONTROL; static const bool DEFAULT_LISTEN_ONION = true; @@ -83,8 +78,6 @@ public: */ bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler); - /** Response handlers for async replies */ - boost::signals2::signal<void(TorControlConnection &,const TorControlReply &)> async_handler; private: /** Callback when ready for use */ std::function<void(TorControlConnection&)> connected; diff --git a/src/txmempool.h b/src/txmempool.h index a1867eb895..fa1dbbf4b5 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -663,13 +663,13 @@ public: void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants, size_t* ancestorsize = nullptr, CAmount* ancestorfees = nullptr) const; /** - * @returns true if we've made an attempt to load the mempool regardless of + * @returns true if an initial attempt to load the persisted mempool was made, regardless of * whether the attempt was successful or not */ bool GetLoadTried() const; /** - * Set whether or not we've made an attempt to load the mempool (regardless + * Set whether or not an initial attempt to load the persisted mempool was made (regardless * of whether the attempt was successful or not) */ void SetLoadTried(bool load_tried); diff --git a/src/validation.cpp b/src/validation.cpp index c45c847471..cec6d13181 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -69,7 +69,6 @@ using kernel::CCoinsStats; using kernel::CoinStatsHashType; using kernel::ComputeUTXOStats; -using kernel::LoadMempool; using kernel::Notifications; using fsbridge::FopenFn; @@ -4126,13 +4125,6 @@ void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight } } -void Chainstate::LoadMempool(const fs::path& load_path, FopenFn mockable_fopen_function) -{ - if (!m_mempool) return; - ::LoadMempool(*m_mempool, load_path, *this, mockable_fopen_function); - m_mempool->SetLoadTried(!m_chainman.m_interrupt); -} - bool Chainstate::LoadChainTip() { AssertLockHeld(cs_main); diff --git a/src/validation.h b/src/validation.h index d7ad86a5e8..4c9f807f5d 100644 --- a/src/validation.h +++ b/src/validation.h @@ -712,9 +712,6 @@ public: /** Find the last common block of this chain and a locator. */ const CBlockIndex* FindForkInGlobalIndex(const CBlockLocator& locator) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - /** Load the persisted mempool from disk */ - void LoadMempool(const fs::path& load_path, fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen); - /** Update the chain tip based on database information, i.e. CoinsTip()'s best block. */ bool LoadChainTip() EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index a1335ff069..32a927084a 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -46,7 +46,7 @@ from test_framework.util import ( assert_greater_than_or_equal, assert_raises_rpc_error, ) -from test_framework.wallet import MiniWallet +from test_framework.wallet import MiniWallet, COIN class MempoolPersistTest(BitcoinTestFramework): @@ -159,6 +159,16 @@ class MempoolPersistTest(BitcoinTestFramework): assert self.nodes[0].getmempoolinfo()["loaded"] assert_equal(len(self.nodes[0].getrawmempool()), 0) + self.log.debug("Import mempool at runtime to node0.") + assert_equal({}, self.nodes[0].importmempool(mempooldat0)) + assert_equal(len(self.nodes[0].getrawmempool()), 7) + fees = self.nodes[0].getmempoolentry(txid=last_txid)["fees"] + assert_equal(fees["base"], fees["modified"]) + assert_equal({}, self.nodes[0].importmempool(mempooldat0, {"apply_fee_delta_priority": True, "apply_unbroadcast_set": True})) + assert_equal(2, self.nodes[0].getmempoolinfo()["unbroadcastcount"]) + fees = self.nodes[0].getmempoolentry(txid=last_txid)["fees"] + assert_equal(fees["base"] + Decimal("0.00001000"), fees["modified"]) + self.log.debug("Stop-start node0. Verify that it has the transactions in its mempool.") self.stop_nodes() self.start_node(0) @@ -186,6 +196,7 @@ class MempoolPersistTest(BitcoinTestFramework): assert_raises_rpc_error(-1, "Unable to dump mempool to disk", self.nodes[1].savemempool) os.rmdir(mempooldotnew1) + self.test_importmempool_union() self.test_persist_unbroadcast() def test_persist_unbroadcast(self): @@ -210,6 +221,46 @@ class MempoolPersistTest(BitcoinTestFramework): node0.mockscheduler(16 * 60) # 15 min + 1 for buffer self.wait_until(lambda: len(conn.get_invs()) == 1) + def test_importmempool_union(self): + self.log.debug("Submit different transactions to node0 and node1's mempools") + self.start_node(0) + self.start_node(2) + tx_node0 = self.mini_wallet.send_self_transfer(from_node=self.nodes[0]) + tx_node1 = self.mini_wallet.send_self_transfer(from_node=self.nodes[1]) + tx_node01 = self.mini_wallet.create_self_transfer() + tx_node01_secret = self.mini_wallet.create_self_transfer() + self.nodes[0].prioritisetransaction(tx_node01["txid"], 0, COIN) + self.nodes[0].prioritisetransaction(tx_node01_secret["txid"], 0, 2 * COIN) + self.nodes[1].prioritisetransaction(tx_node01_secret["txid"], 0, 3 * COIN) + self.nodes[0].sendrawtransaction(tx_node01["hex"]) + self.nodes[1].sendrawtransaction(tx_node01["hex"]) + assert tx_node0["txid"] in self.nodes[0].getrawmempool() + assert not tx_node0["txid"] in self.nodes[1].getrawmempool() + assert not tx_node1["txid"] in self.nodes[0].getrawmempool() + assert tx_node1["txid"] in self.nodes[1].getrawmempool() + assert tx_node01["txid"] in self.nodes[0].getrawmempool() + assert tx_node01["txid"] in self.nodes[1].getrawmempool() + assert not tx_node01_secret["txid"] in self.nodes[0].getrawmempool() + assert not tx_node01_secret["txid"] in self.nodes[1].getrawmempool() + + self.log.debug("Check that importmempool can add txns without replacing the entire mempool") + mempooldat0 = str(self.nodes[0].chain_path / "mempool.dat") + result0 = self.nodes[0].savemempool() + assert_equal(mempooldat0, result0["filename"]) + assert_equal({}, self.nodes[1].importmempool(mempooldat0, {"apply_fee_delta_priority": True})) + # All transactions should be in node1's mempool now. + assert tx_node0["txid"] in self.nodes[1].getrawmempool() + assert tx_node1["txid"] in self.nodes[1].getrawmempool() + assert not tx_node1["txid"] in self.nodes[0].getrawmempool() + # For transactions that already existed, priority should be changed + entry_node01 = self.nodes[1].getmempoolentry(tx_node01["txid"]) + assert_equal(entry_node01["fees"]["base"] + 1, entry_node01["fees"]["modified"]) + # Deltas for not-yet-submitted transactions should be applied as well (prioritisation is stackable). + self.nodes[1].sendrawtransaction(tx_node01_secret["hex"]) + entry_node01_secret = self.nodes[1].getmempoolentry(tx_node01_secret["txid"]) + assert_equal(entry_node01_secret["fees"]["base"] + 5, entry_node01_secret["fees"]["modified"]) + self.stop_nodes() + if __name__ == "__main__": MempoolPersistTest().main() diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py index e367daae2c..fa4f009f34 100755 --- a/test/functional/wallet_fundrawtransaction.py +++ b/test/functional/wallet_fundrawtransaction.py @@ -23,6 +23,7 @@ from test_framework.util import ( assert_raises_rpc_error, count_bytes, find_vout_for_address, + get_fee, ) from test_framework.wallet_util import generate_keypair @@ -570,6 +571,8 @@ class RawTransactionsTest(BitcoinTestFramework): df_wallet = self.nodes[1].get_wallet_rpc(self.default_wallet_name) self.nodes[1].createwallet(wallet_name="locked_wallet", descriptors=self.options.descriptors) wallet = self.nodes[1].get_wallet_rpc("locked_wallet") + # This test is not meant to exercise fee estimation. Making sure all txs are sent at a consistent fee rate. + wallet.settxfee(self.min_relay_tx_fee) # Add some balance to the wallet (this will be reverted at the end of the test) df_wallet.sendall(recipients=[wallet.getnewaddress()]) @@ -599,8 +602,11 @@ class RawTransactionsTest(BitcoinTestFramework): # Choose input inputs = wallet.listunspent() - # Deduce fee to produce a changeless transaction - value = inputs[0]["amount"] - Decimal("0.00002200") + + # Deduce exact fee to produce a changeless transaction + tx_size = 110 # Total tx size: 110 vbytes, p2wpkh -> p2wpkh. Input 68 vbytes + rest of tx is 42 vbytes. + value = inputs[0]["amount"] - get_fee(tx_size, self.min_relay_tx_fee) + outputs = {self.nodes[0].getnewaddress():value} rawtx = wallet.createrawtransaction(inputs, outputs) # fund a transaction that does not require a new key for the change output |