diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6690211292be..ad1e009fd474 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,7 @@ jobs: run: | # Run tests on commits after the last merge commit and before the PR head commit # Use clang++, because it is a bit faster and uses less memory than g++ - 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 -DWARN_INCOMPATIBLE_BDB=OFF -DWITH_MINIUPNPC=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 )) --combinedlogslen=99999999" ${{ env.TEST_BASE }} + 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 -DWARN_INCOMPATIBLE_BDB=OFF -DWITH_MINIUPNPC=ON -DWITH_USDT=ON -DCMAKE_CXX_FLAGS='-Wno-error=unused-member-function' -D RDTS_CONSENT=RUNTIME_CHECK && 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 )) --combinedlogslen=99999999" ${{ env.TEST_BASE }} macos-native-arm64: name: ${{ matrix.job-name }} @@ -240,7 +240,7 @@ jobs: - name: Generate build system run: | - cmake -B build --preset vs2022-static -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" ${{ matrix.generate-options }} + cmake -B build --preset vs2022-static -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" -D RDTS_CONSENT=RUNTIME_CHECK ${{ matrix.generate-options }} - name: Save vcpkg binary cache uses: actions/cache/save@v5 diff --git a/CMakeLists.txt b/CMakeLists.txt index 17cde5a8ce14..f2976b3c8db0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -315,6 +315,38 @@ if(BUILD_FOR_FUZZING) ) endif() +set(RDTS_CONSENT "UNSET" CACHE STRING "RDTS (BIP110) consent behaviour.") +set(RDTS_CONSENT_OPTIONS "RUNTIME_CHECK" "RUNTIME_WARN" "IMPLICIT") +set_property(CACHE RDTS_CONSENT PROPERTY STRINGS "UNSET" ${RDTS_CONSENT_OPTIONS}) +list(APPEND RDTS_CONSENT_OPTIONS "UNSUPPORTED_UNSAFE_NO_ENFORCEMENT") +if(RDTS_CONSENT MATCHES "^(1|ON|YES|TRUE|Y)$") + set(RDTS_CONSENT "IMPLICIT") +endif() +if(BUILD_DAEMON OR BUILD_GUI OR BUILD_KERNEL_LIB) + if(NOT RDTS_CONSENT IN_LIST RDTS_CONSENT_OPTIONS) + message(FATAL_ERROR + "RDTS_CONSENT is not set.\n" + "This version of Bitcoin Knots applies the BIP110 (RDTS) network upgrade, which fixes critical vulnerabilities in long-standing network design. To avoid applying this upgrade by accident, please choose whether to accept it now, or defer explicit confirmation until runtime.\n" + "Important: Because this upgrade already has broad community support, reverting to an older software version does not reject it. Running outdated software after any network upgrade only leaves your node vulnerable to displaying fake or fraudulent transactions. To effectively reject this upgrade, you need to run alternative software designed to split away from the upgraded network.\n" + "To accept this upgrade now, add to your CMake options:\n" + " -D RDTS_CONSENT=IMPLICIT\n" + "Or to defer explicit confirmation until runtime (via GUI prompt or consensusrules=rdts config setting):\n" + " -D RDTS_CONSENT=RUNTIME_CHECK (causes bitcoind to exit if absent)\n" + " -D RDTS_CONSENT=RUNTIME_WARN (causes bitcoind to warn hourly if absent)\n" + "For more information, see: https://bitcoinknots.org/learn/2026-rdts" + ) + endif() +else() + # Nothing else enforces consensus rules, but RDTSConsentFlags::UNSET doesn't actually exist so would break the build + set(RDTS_CONSENT "IMPLICIT") +endif() +if(RDTS_CONSENT STREQUAL "UNSUPPORTED_UNSAFE_NO_ENFORCEMENT") + list(APPEND configure_warnings + "RDTS_CONSENT=UNSUPPORTED_UNSAFE_NO_ENFORCEMENT is NOT a supported option. Use at your own risk! +Note that this will NOT make your node reject RDTS\; it will merely be insecure after RDTS activates." + ) +endif() + # The external_lib_interface interface library aims to encapsulate build flags, which # are specific to non-internal libraries. # It is intended to be a usage requirement for non-internal library @@ -787,6 +819,7 @@ message(" ZeroMQ .............................. ${WITH_ZMQ}") message(" USDT tracing ........................ ${WITH_USDT}") message(" QR code (GUI) ....................... ${WITH_QRENCODE}") message(" DBus (GUI) .......................... ${WITH_DBUS}") +message(" RDTS consent ........................ ${RDTS_CONSENT}") message("Tests:") message(" test_bitcoin ........................ ${BUILD_TESTS}") message(" test_bitcoin-qt ..................... ${BUILD_GUI_TESTS}") diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh index 36f8b9dfc2d1..03a19fed6ad8 100755 --- a/ci/test/03_test_script.sh +++ b/ci/test/03_test_script.sh @@ -110,7 +110,7 @@ if [ "$DOWNLOAD_PREVIOUS_RELEASES" = "true" ]; then test/get_previous_releases.py -b -t "$PREVIOUS_RELEASES_DIR" fi -BITCOIN_CONFIG_ALL="-DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON" +BITCOIN_CONFIG_ALL="-DBUILD_BENCH=ON -DBUILD_FUZZ_BINARY=ON -D RDTS_CONSENT=RUNTIME_CHECK" if [ -z "$NO_DEPENDS" ]; then BITCOIN_CONFIG_ALL="${BITCOIN_CONFIG_ALL} -DCMAKE_TOOLCHAIN_FILE=$DEPENDS_DIR/$HOST/toolchain.cmake" fi diff --git a/cmake/bitcoin-build-config.h.in b/cmake/bitcoin-build-config.h.in index d554d16bd332..e01c86b6b34b 100644 --- a/cmake/bitcoin-build-config.h.in +++ b/cmake/bitcoin-build-config.h.in @@ -174,4 +174,6 @@ /* Define if building embedded LevelDB */ #cmakedefine EMBEDDED_LEVELDB 1 +#define RDTS_CONSENT RDTSConsentFlag::@RDTS_CONSENT@ + #endif //BITCOIN_CONFIG_H diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index a3088a5b72cf..757adbef982f 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,5 +1,46 @@ +# syntax=docker/dockerfile:1 FROM alpine:3.22 AS builder +ARG RDTS_CONSENT="UNSET" + +RUN </tmp/rdts_check + case "$(echo "$RDTS_CONSENT" | tr '[:lower:]' '[:upper:]')" in + 1|ON|YES|TRUE|Y) RDTS_CONSENT="IMPLICIT" ;; + esac + case "$RDTS_CONSENT" in + RUNTIME_CHECK|RUNTIME_WARN|IMPLICIT) + ;; + *) + red=$(printf '\033[31m') + term_reset=$(printf '\033[0m') + cat <&2 + +${red}ERROR: RDTS_CONSENT is not set. + +This version of Bitcoin Knots applies the BIP110 (RDTS) network upgrade, which fixes critical vulnerabilities in long-standing network design. To avoid applying this upgrade by accident, please choose whether to accept it now, or defer explicit confirmation until runtime. + +Important: Because this upgrade already has broad community support, reverting to an older software version does not reject it. Running outdated software after any network upgrade only leaves your node vulnerable to displaying fake or fraudulent transactions. To effectively reject this upgrade, you need to run alternative software designed to split away from the upgraded network. + +To accept this upgrade now, add to your Docker build arguments: + + --build-arg RDTS_CONSENT=IMPLICIT + +Or to defer explicit confirmation until runtime (via consensusrules=rdts config setting): + + --build-arg RDTS_CONSENT=RUNTIME_CHECK (exits if absent) + --build-arg RDTS_CONSENT=RUNTIME_WARN (warns hourly if absent) + +For more information, see: https://bitcoinknots.org/learn/2026-rdts ${term_reset} +EOF + exit 1 + ;; + esac +EOSCRIPT +EORUN + +RUN . /tmp/rdts_check + RUN apk add --no-cache \ build-base \ cmake \ @@ -17,6 +58,7 @@ COPY . . WORKDIR /opt/bitcoin/build RUN cmake .. \ + -D RDTS_CONSENT="${RDTS_CONSENT}" \ -DCMAKE_INSTALL_PREFIX="/usr/local/" \ -DBUILD_DAEMON="ON" \ -DBUILD_CLI="ON" \ @@ -43,6 +85,9 @@ RUN strip --strip-unneeded /usr/local/bin/* FROM alpine:3.22 AS final +ARG RDTS_CONSENT +LABEL org.bitcoinknots.rdts_consent="${RDTS_CONSENT}" + ARG USER_ID=1000 ARG GROUP_ID=1000 diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index 0ebae76f5410..99d321a3cdc9 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -209,6 +209,7 @@ mkdir -p "$OUTDIR" # CONFIGFLAGS CONFIGFLAGS="-DREDUCE_EXPORTS=ON -DBUILD_BENCH=OFF -DBUILD_GUI_TESTS=OFF -DBUILD_FUZZ_BINARY=OFF" CONFIGFLAGS="$CONFIGFLAGS -DCMAKE_SKIP_BUILD_RPATH=TRUE" # check-symbols is fussy about rpath and we don't need it +CONFIGFLAGS="$CONFIGFLAGS -D RDTS_CONSENT=RUNTIME_WARN" # CFLAGS HOST_CFLAGS="-O2 -g" diff --git a/src/chainparams.cpp b/src/chainparams.cpp index c123fa5be552..703d213c7783 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +138,17 @@ const CChainParams &Params() { std::unique_ptr CreateChainParams(const ArgsManager& args, const ChainType chain) { + g_rdts_consent = static_cast(args.GetIntArg("rdts_consent_flag", static_cast(g_rdts_consent))); + g_enable_rdts = g_rdts_consent != RDTSConsentFlag::UNSUPPORTED_UNSAFE_NO_ENFORCEMENT; + if (g_rdts_consent == RDTSConsentFlag::UNSUPPORTED_UNSAFE_NO_ENFORCEMENT && !g_enable_rdts) { + for (const auto& rulesok : args.GetArgs(CONSENSUSRULES_CONFIG_NAME)) { + if (rulesok == CONSENSUSRULES_REQUIRED) { + g_enable_rdts = true; + break; + } + } + } + switch (chain) { case ChainType::MAIN: return CChainParams::Main(); diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index adbd6a517469..5d19c230bfc3 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -13,6 +13,9 @@ class ArgsManager; +static const std::string CONSENSUSRULES_CONFIG_NAME{"consensusrules"}; +static const std::string CONSENSUSRULES_REQUIRED{"rdts"}; + /** * CBaseChainParams defines the base parameters (shared between bitcoin-cli and bitcoind) * of a given instance of the Bitcoin system. diff --git a/src/init.cpp b/src/init.cpp index 23ab427b1c2e..4d02cc881ece 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -693,6 +694,8 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-uaspoof=", strprintf("Replace entire user agent string with custom identifier (should be formatted '%s' as specified in BIP 14)", BIP14_EXAMPLE_UA), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); SetupChainParamsBaseOptions(argsman); + argsman.AddArg("-consensusrules=", strprintf("Enforce the specified consensus rules (default: none). Must be %s to use this software.", CONSENSUSRULES_REQUIRED), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-rdts_consent_flag=", strprintf("Test RDTS consent flag (default: %u)", static_cast(g_rdts_consent)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-acceptnonstddatacarrier", strprintf("Relay and mine non-OP_RETURN datacarrier injection (default: %u)", @@ -1559,6 +1562,84 @@ static ChainstateLoadResult InitAndLoadChainstate( return {status, error}; }; +bool UserProtocolRulesCheck() +{ + const auto rules_requested{gArgs.GetArgs(CONSENSUSRULES_CONFIG_NAME)}; + for (const auto& rulesok : rules_requested) { + if (rulesok == CONSENSUSRULES_REQUIRED) continue; + return InitError(strprintf(_("Unknown rule specified in -%s: %s"), CONSENSUSRULES_CONFIG_NAME, rulesok)); + } + return true; +} + +bool UserProtocolRulesConsent() +{ + if (g_rdts_consent == RDTSConsentFlag::IMPLICIT) { + LogPrintf("User already consented to '%s' consensus rules (at installation)\n", CONSENSUSRULES_REQUIRED); + return true; + } + for (const auto& rulesok : gArgs.GetArgs(CONSENSUSRULES_CONFIG_NAME)) { + if (rulesok == CONSENSUSRULES_REQUIRED) { + LogPrintf("User already consented to '%s' consensus rules (in config)\n", CONSENSUSRULES_REQUIRED); + return true; + } + } + + bilingual_str msg = strprintf(_( + "BIP110/RDTS Network Upgrade\n" + "\n" + "This version of %s applies the BIP110 (RDTS) network upgrade, " + "which fixes critical vulnerabilities in long-standing network design. " + "However, you are in control of your own software, and this application asks for explicit confirmation.\n" + "\n" + "Important: " + "Because this upgrade already has broad community support, " + "reverting to an older software version does not reject it. " + "Running outdated software after any network upgrade only leaves your node vulnerable to displaying fake or fraudulent transactions. " + "To effectively reject this upgrade, you need to run alternative software designed to split away from the upgraded network.\n" + "\n" + "For more information, see: %s" + ), + CLIENT_NAME, + "https://bitcoinknots.org/learn/2026-rdts"); + const bilingual_str msg_manual_suffix = strprintf(_( + "To confirm this upgrade, add to your %s file: %s" + ), + gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME).utf8string(), + CONSENSUSRULES_CONFIG_NAME + "=" + CONSENSUSRULES_REQUIRED); + const bilingual_str msg_manual = msg + Untranslated("\n\n") + msg_manual_suffix; + + if (!gArgs.GetSettingsPath()) { + msg = msg_manual; + } + + const bool consent = uiInterface.ThreadSafeQuestion( + _("Attention:") + Untranslated(" ") + msg, + msg_manual.original + , "Attention", CClientUIInterface::MSG_WARNING | CClientUIInterface::BTN_ABORT); + + if (consent) { + if (gArgs.GetSettingsPath()) { + // Write to settings.json so we don't ask anymore + LogPrintf("User interactively consented to '%s' consensus rules (%s)\n", CONSENSUSRULES_REQUIRED, "remembering for next time"); + gArgs.LockSettings([&](common::Settings& settings) { + auto& setting = settings.rw_settings[CONSENSUSRULES_CONFIG_NAME]; + if (setting.isArray()) { + // Normally, it doesn't make sense to support multiple rulesets, but if the user has done so already, don't lose the current set + setting.push_back(CONSENSUSRULES_REQUIRED); + } else { + setting = CONSENSUSRULES_REQUIRED; + } + }); + gArgs.WriteSettingsFile(); + } else { + LogPrintf("User interactively consented to '%s' consensus rules (%s)\n", CONSENSUSRULES_REQUIRED, "settings disabled, so can't save"); + } + } + + return consent; +} + bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) { const ArgsManager& args = *Assert(node.args); @@ -1633,6 +1714,36 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) g_wallet_init_interface.Construct(node); uiInterface.InitWallet(); + if (!UserProtocolRulesCheck()) { + return false; + } + + if (!(chainparams.IsTestChain() || UserProtocolRulesConsent())) { + if (g_rdts_consent == RDTSConsentFlag::RUNTIME_CHECK) { + return InitError(_("User has not consented to supported protocol rules. Exiting")); + } else if (g_rdts_consent == RDTSConsentFlag::UNSUPPORTED_UNSAFE_NO_ENFORCEMENT) { + LogError("User has not consented to supported protocol rules. This node will NOT enforce them. Warning every hour."); + g_local_services = ServiceFlags(g_local_services & ~NODE_REDUCED_DATA); + scheduler.scheduleEvery([]{ + LogError("RDTS is not enabled. This node is therefore vulnerable to displaying fake or fraudulent transactions.\n"); + LogError("For more information, see: %s\n", "https://bitcoinknots.org/learn/2026-rdts"); + LogError("To enable RDTS enforcement and disable this warning, add to %s: %s\n", + gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME).utf8string(), + CONSENSUSRULES_CONFIG_NAME + "=" + CONSENSUSRULES_REQUIRED); + }, std::chrono::hours{1}); + } else { + LogError("User has not consented to supported protocol rules. This node will STILL enforce them. Warning every hour."); + g_rdts_warning = true; + scheduler.scheduleEvery([]{ + LogError("This software applies the BIP110/RDTS network upgrade, which fixes critical vulnerabilities, but explicit user confirmation has not been configured.\n"); + LogError("For more information, see: %s\n", "https://bitcoinknots.org/learn/2026-rdts"); + LogError("To confirm this upgrade and dismiss this warning, add to your %s file: %s\n", + gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME).utf8string(), + CONSENSUSRULES_CONFIG_NAME + "=" + CONSENSUSRULES_REQUIRED); + }, std::chrono::hours{1}); + } + } + if (interfaces::Ipc* ipc = node.init->ipc()) { for (std::string address : gArgs.GetArgs("-ipcbind")) { try { diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index b418139ef9dc..057c3071c51c 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -3,6 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include // IWYU pragma: keep + #include #include @@ -28,6 +30,10 @@ using namespace util::hex_literals; +RDTSConsentFlag g_rdts_consent{RDTS_CONSENT}; +bool g_enable_rdts{g_rdts_consent != RDTSConsentFlag::UNSUPPORTED_UNSAFE_NO_ENFORCEMENT}; +bool g_rdts_warning{false}; + // Workaround MSVC bug triggering C7595 when calling consteval constructors in // initializer lists. // A fix may be on the way: @@ -126,6 +132,14 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_REDUCED_DATA].active_duration = 52416; // ~1 year consensus.vDeployments[Consensus::DEPLOYMENT_REDUCED_DATA].threshold = 1109; // 55% of 2016 + if (g_rdts_consent == RDTSConsentFlag::UNSUPPORTED_UNSAFE_NO_ENFORCEMENT && !g_enable_rdts) { + consensus.vDeployments[Consensus::DEPLOYMENT_REDUCED_DATA].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_REDUCED_DATA].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_REDUCED_DATA].max_activation_height = std::numeric_limits::max(); + consensus.vDeployments[Consensus::DEPLOYMENT_REDUCED_DATA].active_duration = std::numeric_limits::max(); + consensus.vDeployments[Consensus::DEPLOYMENT_REDUCED_DATA].threshold = 0; + } + consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000dee8e2a309ad8a9820433c68"}; consensus.defaultAssumeValid = uint256{"00000000000000000000611fd22f2df7c8fbd0688745c3a6c3bb5109cc2a12cb"}; // 912683 diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h index 09d2ddfee51f..c1ab67928428 100644 --- a/src/kernel/chainparams.h +++ b/src/kernel/chainparams.h @@ -24,6 +24,17 @@ #include #include +enum class RDTSConsentFlag { + RUNTIME_CHECK, + IMPLICIT, + RUNTIME_WARN, + UNSUPPORTED_UNSAFE_NO_ENFORCEMENT, +}; + +extern RDTSConsentFlag g_rdts_consent; +extern bool g_enable_rdts; +extern bool g_rdts_warning; + typedef std::map MapCheckpoints; struct CCheckpointData { diff --git a/src/kernel/warning.h b/src/kernel/warning.h index 888aa4074d00..2b6ac77a1a5b 100644 --- a/src/kernel/warning.h +++ b/src/kernel/warning.h @@ -7,6 +7,7 @@ namespace kernel { enum class Warning { + RULES_NOT_CONSENTED, UNKNOWN_NEW_RULES_ACTIVATED, LARGE_WORK_INVALID_CHAIN, UNKNOWN_NEW_RULES_SIGNAL_VBITS, diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 7d46831d1e9d..63a93adbd862 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -185,7 +186,11 @@ class NodeImpl : public Node { args().WriteSettingsFile(/*errors=*/nullptr, /*backup=*/true); args().LockSettings([&](common::Settings& settings) { - settings.rw_settings.clear(); + std::map new_rw_settings; + if (auto it = settings.rw_settings.find(CONSENSUSRULES_CONFIG_NAME); it != settings.rw_settings.end()) { + new_rw_settings.emplace(it->first, std::move(it->second)); + } + settings.rw_settings.swap(new_rw_settings); }); args().WriteSettingsFile(); } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index cfadfc924d41..9ae750c4e354 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -634,6 +635,13 @@ int GuiMain(int argc, char* argv[]) PaymentServer::ipcParseCommandLine(argc, argv); #endif + if (g_rdts_consent == RDTSConsentFlag::RUNTIME_WARN) { + // The GUI user is presented with a choice to consent or exit. + // It doesn't make sense to continue running if they choose exit. + // We set this here since it should be after SelectParams loads the test option yet still before AppInitMain acts on it + g_rdts_consent = RDTSConsentFlag::RUNTIME_CHECK; + } + QScopedPointer networkStyle(NetworkStyle::instantiate(Params().GetChainType())); assert(!networkStyle.isNull()); // Allow for separate UI settings for testnets diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index f4f099e2b502..6d97ce18819d 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1331,6 +1331,9 @@ void BitcoinGUI::message(const QString& title, QString message, unsigned int sty int nMBoxIcon = QMessageBox::Information; int nNotifyIcon = Notificator::Information; + const bool is_rich_text = message.startsWith(""); + if (is_rich_text) message.remove(0, 4); + QString msgType; if (!title.isEmpty()) { msgType = title; @@ -1373,7 +1376,7 @@ void BitcoinGUI::message(const QString& title, QString message, unsigned int sty showNormalIfMinimized(); QMessageBox mBox(static_cast(nMBoxIcon), strTitle, message, buttons, this); - mBox.setTextFormat(Qt::PlainText); + mBox.setTextFormat(is_rich_text ? Qt::RichText : Qt::PlainText); mBox.setDetailedText(detailed_message); int r = mBox.exec(); if (ret != nullptr) @@ -1668,6 +1671,10 @@ static bool ThreadSafeMessageBox(BitcoinGUI* gui, const bilingual_str& message, style &= ~CClientUIInterface::SECURE; bool ret = false; + const QString msg = modal + ? ("" + GUIUtil::MakeHtmlLink(GUIUtil::HtmlEscape(QString::fromStdString(message.translated), true))) + : QString::fromStdString(message.translated); + QString detailed_message; // This is original message, in English, for googling and referencing. if (message.original != message.translated) { detailed_message = BitcoinGUI::tr("Original message:") + "\n" + QString::fromStdString(message.original); @@ -1677,7 +1684,7 @@ static bool ThreadSafeMessageBox(BitcoinGUI* gui, const bilingual_str& message, bool invoked = QMetaObject::invokeMethod(gui, "message", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), - Q_ARG(QString, QString::fromStdString(message.translated)), + Q_ARG(QString, msg), Q_ARG(unsigned int, style), Q_ARG(bool*, &ret), Q_ARG(QString, detailed_message)); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index cdc2a50bab3f..c35ef649901f 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1277,6 +1277,12 @@ QString MakeHtmlLink(const QString& source, const QString& link) QLatin1String("") + link + QLatin1String("")); } +QString MakeHtmlLink(const QString& source) +{ + static const QRegularExpression uri(QStringLiteral(R"#(([\s>]|^)((https)://([\w./-]+))(?=\.?[\s<]|\.?$))#"), QRegularExpression::InvertedGreedinessOption); + return QString(source).replace(uri, QStringLiteral(R"#(\1\3⁠:⁠/⁠/⁠\4)#")); +} + void PrintSlotException( const std::exception* exception, const QObject* sender, diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index d1b20d0b4d39..86ae02e65505 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -424,6 +424,7 @@ namespace GUIUtil * Replaces a plain text link with an HTML tagged one. */ QString MakeHtmlLink(const QString& source, const QString& link); + QString MakeHtmlLink(const QString& source); void PrintSlotException( const std::exception* exception, diff --git a/src/validation.cpp b/src/validation.cpp index 67d5b1bdde6f..9cae13deb836 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -9,8 +9,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -6728,6 +6730,31 @@ ChainstateManager::ChainstateManager(const util::SignalInterrupt& interrupt, Opt m_blockman{interrupt, std::move(blockman_options)}, m_validation_cache{m_options.script_execution_cache_bytes, m_options.signature_cache_bytes} { + if (GetParams().IsTestChain() + ? (!g_enable_rdts) + : GetConsensus().vDeployments[Consensus::DEPLOYMENT_REDUCED_DATA].nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE) { + m_options.notifications.warningSet(kernel::Warning::RULES_NOT_CONSENTED, + strprintf(_("Warning: RDTS is not enabled. This node is therefore vulnerable to displaying fake or fraudulent transactions. To enable RDTS enforcement and disable this warning, add %s to your %s file."), + CONSENSUSRULES_CONFIG_NAME + "=" + CONSENSUSRULES_REQUIRED, +#ifdef BUILDING_FOR_LIBBITCOINKERNEL + "bitcoin.conf" +#else + gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME).utf8string() +#endif + ) + ); + } else if (g_rdts_warning) { + m_options.notifications.warningSet(kernel::Warning::RULES_NOT_CONSENTED, + strprintf(_("Warning: This software applies the BIP110/RDTS network upgrade, but explicit confirmation has not been configured. To confirm this upgrade and dismiss this warning, add %s to your %s file."), + CONSENSUSRULES_CONFIG_NAME + "=" + CONSENSUSRULES_REQUIRED, +#ifdef BUILDING_FOR_LIBBITCOINKERNEL + "bitcoin.conf" +#else + gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME).utf8string() +#endif + ) + ); + } } ChainstateManager::~ChainstateManager() diff --git a/test/functional/mining_mainnet.py b/test/functional/mining_mainnet.py index 456381af55ab..7c62f2aa1bcf 100755 --- a/test/functional/mining_mainnet.py +++ b/test/functional/mining_mainnet.py @@ -44,6 +44,7 @@ def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.chain = "" # main + self.extra_args = [['-consensusrules=rdts']] * self.num_nodes def add_options(self, parser): parser.add_argument( diff --git a/test/functional/rpc_validateaddress.py b/test/functional/rpc_validateaddress.py index bf094a7df893..006a93225c09 100755 --- a/test/functional/rpc_validateaddress.py +++ b/test/functional/rpc_validateaddress.py @@ -179,7 +179,7 @@ def set_test_params(self): self.setup_clean_chain = True self.chain = "" # main self.num_nodes = 1 - self.extra_args = [["-prune=899"]] * self.num_nodes + self.extra_args = [["-prune=899", '-consensusrules=rdts']] * self.num_nodes def check_valid(self, addr, spk): info = self.nodes[0].validateaddress(addr)