diff --git a/configure.ac b/configure.ac index a180727b2b10..5eb595f625e0 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) dnl Don't forget to push a corresponding tag when updating any of _CLIENT_VERSION_* numbers define(_CLIENT_VERSION_MAJOR, 23) define(_CLIENT_VERSION_MINOR, 0) -define(_CLIENT_VERSION_BUILD, 0) +define(_CLIENT_VERSION_BUILD, 1) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2025) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 5e0e20aa38df..6c8eb44984f2 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -1,9 +1,9 @@ package=qt -$(package)_version=5.15.14 -$(package)_download_path=https://download.qt.io/official_releases/qt/5.15/$($(package)_version)/submodules +$(package)_version=5.15.18 +$(package)_download_path=https://download.qt.io/archive/qt/5.15/$($(package)_version)/submodules $(package)_suffix=everywhere-opensource-src-$($(package)_version).tar.xz $(package)_file_name=qtbase-$($(package)_suffix) -$(package)_sha256_hash=500d3b390048e9538c28b5f523dfea6936f9c2e10d24ab46580ff57d430b98be +$(package)_sha256_hash=7b632550ea1048fc10c741e46e2e3b093e5ca94dfa6209e9e0848800e247023b $(package)_linux_dependencies=freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm $(package)_qt_libs=corelib network widgets gui plugins testlib $(package)_linguist_tools = lrelease lupdate lconvert @@ -19,19 +19,19 @@ $(package)_patches += no_warnings_for_symbols.patch $(package)_patches += rcc_hardcode_timestamp.patch $(package)_patches += duplicate_lcqpafonts.patch $(package)_patches += guix_cross_lib_path.patch -$(package)_patches += fix-macos-linker.patch $(package)_patches += memory_resource.patch -$(package)_patches += clang_18_libpng.patch $(package)_patches += utc_from_string_no_optimize.patch $(package)_patches += windows_lto.patch $(package)_patches += darwin_no_libm.patch -$(package)_patches += zlib-timebits64.patch +$(package)_patches += CVE-2025-4211-qtbase-5.15.patch +$(package)_patches += CVE-2025-5455-qtbase-5.15.patch +$(package)_patches += CVE-2025-30348-qtbase-5.15.patch $(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) -$(package)_qttranslations_sha256_hash=5b94d1a11b566908622fcca2f8b799744d2f8a68da20be4caa5953ed63b10489 +$(package)_qttranslations_sha256_hash=e5625757913caf66a9d702ba102ae92cb165d8dde17759b6de9fdea84a1f857f $(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=12061a85baf5f4de8fbc795e1d3872b706f340211b9e70962caeffc6f5e89563 +$(package)_qttools_sha256_hash=931e0969d9f9d8f233e5e9bf9db0cea9ce9914d49982f1795fe6191010113568 $(package)_extra_sources = $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) @@ -242,7 +242,6 @@ endef define $(package)_preprocess_cmds cp $($(package)_patch_dir)/qt.pro qt.pro && \ cp $($(package)_patch_dir)/qttools_src.pro qttools/src/src.pro && \ - patch -p1 -i $($(package)_patch_dir)/fix-macos-linker.patch && \ patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_qt_pkgconfig.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \ @@ -250,14 +249,15 @@ define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/qtbase-moc-ignore-gcc-macro.patch && \ patch -p1 -i $($(package)_patch_dir)/memory_resource.patch && \ patch -p1 -i $($(package)_patch_dir)/no_warnings_for_symbols.patch && \ - patch -p1 -i $($(package)_patch_dir)/clang_18_libpng.patch && \ patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch && \ patch -p1 -i $($(package)_patch_dir)/duplicate_lcqpafonts.patch && \ patch -p1 -i $($(package)_patch_dir)/utc_from_string_no_optimize.patch && \ patch -p1 -i $($(package)_patch_dir)/guix_cross_lib_path.patch && \ patch -p1 -i $($(package)_patch_dir)/windows_lto.patch && \ patch -p1 -i $($(package)_patch_dir)/darwin_no_libm.patch && \ - patch -p1 -i $($(package)_patch_dir)/zlib-timebits64.patch && \ + patch -p1 -i $($(package)_patch_dir)/CVE-2025-4211-qtbase-5.15.patch && \ + patch -p1 -i $($(package)_patch_dir)/CVE-2025-5455-qtbase-5.15.patch && \ + patch -p1 -i $($(package)_patch_dir)/CVE-2025-30348-qtbase-5.15.patch && \ mkdir -p qtbase/mkspecs/macx-clang-linux &&\ cp -f qtbase/mkspecs/macx-clang/qplatformdefs.h qtbase/mkspecs/macx-clang-linux/ &&\ cp -f $($(package)_patch_dir)/mac-qmake.conf qtbase/mkspecs/macx-clang-linux/qmake.conf && \ diff --git a/depends/patches/qt/CVE-2025-30348-qtbase-5.15.patch b/depends/patches/qt/CVE-2025-30348-qtbase-5.15.patch new file mode 100644 index 000000000000..b13574f3ba27 --- /dev/null +++ b/depends/patches/qt/CVE-2025-30348-qtbase-5.15.patch @@ -0,0 +1,156 @@ +From 16918c1df3e709df2a97281e3825d94c84edb668 Mon Sep 17 00:00:00 2001 +From: Christian Ehrlicher +Date: Tue, 06 Aug 2024 22:39:44 +0200 +Subject: [PATCH] XML/QDom: speedup encodeText() + +The code copied the whole string, then replaced parts inline, at +the cost of relocating everything beyond, at each replacement. +Instead, copy character by character (in chunks where possible) +and append replacements as we skip what they replace. + +Manual conflict resolution for 6.5: +- This is a manual cherry-pick. The original change was only + picked to 6.8, but the quadratic behavior is present in Qt 5, too. +- Changed Task-number to Fixes: because this is the real fix; + the QString change, 315210de916d060c044c01e53ff249d676122b1b, + was unrelated to the original QTBUG-127549. + +Manual conflcit resolution for 5.15: +- Kept/re-added QTextCodec::canEncode() check +- Ported from Qt 6 to 5, to wit: + - qsizetype -> int + - QStringView::first/sliced(n) -> left/mid(n) + (these functions are clearly called in-range, so the widened + contract of the Qt 5 functions doesn't matter) +- Ported from C++17- and C++14-isms to C++11: + - replaced polymorphic lambda with a normal one (this requires + rewriting the !canEncode() branch to use QByteArray/QLatin1String + instead of QString) +- As a drive-by, corrected the indentation of the case labels to + horizontally align existing code (and follow Qt style) + +Fixes: QTBUG-127549 +Change-Id: I368482859ed0c4127f1eec2919183711b5488ada +Reviewed-by: Edward Welbourne +(cherry picked from commit 2ce08e3671b8d18b0284447e5908ce15e6e8f80f) +Reviewed-by: Qt Cherry-pick Bot +(cherry picked from commit 225e235cf966a44af23dbe9aaaa2fd20ab6430ee) +Reviewed-by: Fabian Kosmale +(cherry picked from commit 905a5bd421efff6a1d90b6140500d134d32ca745) +--- + +diff --git a/qtbase/src/xml/dom/qdom.cpp b/qtbase/src/xml/dom/qdom.cpp +index 872221c..bf70477 100644 +--- a/qtbase/src/xml/dom/qdom.cpp ++++ b/qtbase/src/xml/dom/qdom.cpp +@@ -3676,59 +3676,67 @@ + const QTextCodec *const codec = s.codec(); + Q_ASSERT(codec); + #endif +- QString retval(str); +- int len = retval.length(); +- int i = 0; ++ QString retval; ++ int start = 0; ++ auto appendToOutput = [&](int cur, QLatin1String replacement) ++ { ++ if (start < cur) { ++ retval.reserve(str.size() + replacement.size()); ++ retval.append(QStringView(str).left(cur).mid(start)); ++ } ++ // Skip over str[cur], replaced by replacement ++ start = cur + 1; ++ retval.append(replacement); ++ }; + +- while (i < len) { +- const QChar ati(retval.at(i)); +- +- if (ati == QLatin1Char('<')) { +- retval.replace(i, 1, QLatin1String("<")); +- len += 3; +- i += 4; +- } else if (encodeQuotes && (ati == QLatin1Char('"'))) { +- retval.replace(i, 1, QLatin1String(""")); +- len += 5; +- i += 6; +- } else if (ati == QLatin1Char('&')) { +- retval.replace(i, 1, QLatin1String("&")); +- len += 4; +- i += 5; +- } else if (ati == QLatin1Char('>') && i >= 2 && retval[i - 1] == QLatin1Char(']') && retval[i - 2] == QLatin1Char(']')) { +- retval.replace(i, 1, QLatin1String(">")); +- len += 3; +- i += 4; +- } else if (performAVN && +- (ati == QChar(0xA) || +- ati == QChar(0xD) || +- ati == QChar(0x9))) { +- const QString replacement(QLatin1String("&#x") + QString::number(ati.unicode(), 16) + QLatin1Char(';')); +- retval.replace(i, 1, replacement); +- i += replacement.length(); +- len += replacement.length() - 1; +- } else if (encodeEOLs && ati == QChar(0xD)) { +- retval.replace(i, 1, QLatin1String(" ")); // Replace a single 0xD with a ref for 0xD +- len += 4; +- i += 5; +- } else { ++ const int len = str.size(); ++ for (int cur = 0; cur < len; ++cur) { ++ switch (const char16_t ati = str[cur].unicode()) { ++ case u'<': ++ appendToOutput(cur, QLatin1String("<")); ++ break; ++ case u'"': ++ if (encodeQuotes) ++ appendToOutput(cur, QLatin1String(""")); ++ break; ++ case u'&': ++ appendToOutput(cur, QLatin1String("&")); ++ break; ++ case u'>': ++ if (cur >= 2 && str[cur - 1] == u']' && str[cur - 2] == u']') ++ appendToOutput(cur, QLatin1String(">")); ++ break; ++ case u'\r': ++ if (performAVN || encodeEOLs) ++ appendToOutput(cur, QLatin1String(" ")); // \r == 0x0d ++ break; ++ case u'\n': ++ if (performAVN) ++ appendToOutput(cur, QLatin1String(" ")); // \n == 0x0a ++ break; ++ case u'\t': ++ if (performAVN) ++ appendToOutput(cur, QLatin1String(" ")); // \t == 0x09 ++ break; ++ default: + #if QT_CONFIG(textcodec) + if(codec->canEncode(ati)) +- ++i; ++ ; // continue + else + #endif + { + // We have to use a character reference to get it through. +- const ushort codepoint(ati.unicode()); +- const QString replacement(QLatin1String("&#x") + QString::number(codepoint, 16) + QLatin1Char(';')); +- retval.replace(i, 1, replacement); +- i += replacement.length(); +- len += replacement.length() - 1; ++ const QByteArray replacement = "&#x" + QByteArray::number(uint{ati}, 16) + ';'; ++ appendToOutput(cur, QLatin1String{replacement}); + } ++ break; + } + } +- +- return retval; ++ if (start > 0) { ++ retval.append(QStringView(str).left(len).mid(start)); ++ return retval; ++ } ++ return str; + } + + void QDomAttrPrivate::save(QTextStream& s, int, int) const diff --git a/depends/patches/qt/CVE-2025-4211-qtbase-5.15.patch b/depends/patches/qt/CVE-2025-4211-qtbase-5.15.patch new file mode 100644 index 000000000000..0682ed28b61c --- /dev/null +++ b/depends/patches/qt/CVE-2025-4211-qtbase-5.15.patch @@ -0,0 +1,61 @@ +From 3d20cd0105c2ae06605c5078e7675e200f1a001a Mon Sep 17 00:00:00 2001 +From: Mårten Nordheim +Date: Mon, 17 Mar 2025 14:22:11 +0100 +Subject: [PATCH] QFileSystemEngine/Win: Use GetTempPath2 when available + +Because the documentation for GetTempPath nows says apps should call +GetTempPath2.[0] + +Starting with Windows 11[1], and recently Windows 10[2], +GetTempPath2 was added. The difference being that elevated +processes are returned a different directory. Usually +'C:\Windows\SystemTemp'. + +Currently temporary files of an elevated process may be placed in a +world write-able location. GetTempPath2, by default, but can be +overridden, places it in a directory that's only accessible by SYSTEM +and administrators. + +[0] https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw#remarks +[1] https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppath2w +(Minimum supported client - Windows 11 Build 22000) +[2] https://blogs.windows.com/windows-insider/2025/03/13/releasing-windows-10-build-19045-5674-to-the-release-preview-channel/ +(This update enables system processes to store temporary files ...) + +[ChangeLog][QtCore][Important Behavior Changes] On +Windows, generating temporary directories for processes with elevated +privileges may now return a different path with a stricter +set of permissions. Please consult Microsoft's documentation from when +they made the same change for the .NET framework: +https://support.microsoft.com/en-us/topic/gettemppath-changes-in-windows-february-cumulative-update-preview-4cc631fb-9d97-4118-ab6d-f643cd0a7259 + +Change-Id: I5caf11151fb2f711bbc5599231f140598b3c9d03 +Reviewed-by: Marc Mutz +(cherry picked from commit 69633bcb58e681bac5bff3744e5a2352788dc36c) +Reviewed-by: Qt Cherry-pick Bot +(cherry picked from commit 6a684a53b371ec483b27bf243af24819be63f85f) +(cherry picked from commit bbeccc0c22e520f46f0b33e281fa5ac85ac9c727) +(cherry picked from commit 59d7eb9bbb4f13cccbd9323fd995a8c108b56e60) +--- + +diff --git a/qtbase/src/corelib/io/qfilesystemengine_win.cpp b/qtbase/src/corelib/io/qfilesystemengine_win.cpp +index 75c661f..37a400f 100644 +--- a/qtbase/src/corelib/io/qfilesystemengine_win.cpp ++++ b/qtbase/src/corelib/io/qfilesystemengine_win.cpp +@@ -1390,7 +1390,15 @@ + QString ret; + #ifndef Q_OS_WINRT + wchar_t tempPath[MAX_PATH]; +- const DWORD len = GetTempPath(MAX_PATH, tempPath); ++ using GetTempPathPrototype = DWORD (WINAPI *)(DWORD, LPWSTR); ++ // We try to resolve GetTempPath2 and use that, otherwise fall back to GetTempPath: ++ static GetTempPathPrototype getTempPathW = []() { ++ const HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); ++ if (auto *func = QFunctionPointer(GetProcAddress(kernel32, "GetTempPath2W"))) ++ return GetTempPathPrototype(func); ++ return GetTempPath; ++ }(); ++ const DWORD len = getTempPathW(MAX_PATH, tempPath); + if (len) { // GetTempPath() can return short names, expand. + wchar_t longTempPath[MAX_PATH]; + const DWORD longLen = GetLongPathName(tempPath, longTempPath, MAX_PATH); diff --git a/depends/patches/qt/CVE-2025-5455-qtbase-5.15.patch b/depends/patches/qt/CVE-2025-5455-qtbase-5.15.patch new file mode 100644 index 000000000000..352be5e20266 --- /dev/null +++ b/depends/patches/qt/CVE-2025-5455-qtbase-5.15.patch @@ -0,0 +1,20 @@ +diff --git a/qtbase/src/corelib/io/qdataurl.cpp b/qtbase/src/corelib/io/qdataurl.cpp +index f14d399301f..83e59e3ac00 100644 +--- a/qtbase/src/corelib/io/qdataurl.cpp ++++ b/qtbase/src/corelib/io/qdataurl.cpp +@@ -76,10 +76,11 @@ Q_CORE_EXPORT bool qDecodeDataUrl(const QUrl &uri, QString &mimeType, QByteArray + } + + if (data.toLower().startsWith("charset")) { +- int i = 7; // strlen("charset") +- while (data.at(i) == ' ') +- ++i; +- if (data.at(i) == '=') ++ int prefixSize = 7; // strlen("charset") ++ QLatin1String copy(data.constData() + prefixSize, data.size() - prefixSize); ++ while (copy.startsWith(QLatin1String(" "))) ++ copy = copy.mid(1); ++ if (copy.startsWith(QLatin1String("="))) + data.prepend("text/plain;"); + } + diff --git a/depends/patches/qt/clang_18_libpng.patch b/depends/patches/qt/clang_18_libpng.patch deleted file mode 100644 index e807905b321a..000000000000 --- a/depends/patches/qt/clang_18_libpng.patch +++ /dev/null @@ -1,40 +0,0 @@ -fix Qt macOS build with Clang 18 - - See: - https://github.com/pnggroup/libpng/commit/893b8113f04d408cc6177c6de19c9889a48faa24. - - In a similar manner as zlib (madler/zlib#895), - libpng contains a header configuration that's no longer valid and - hasn't been exercised for the macOS target. - - - The target OS conditional macros are misused. Specifically - `TARGET_OS_MAC` covers all Apple targets, including iOS, and it - should not be checked with `#if defined` as they would always be - defined (to either 1 or 0) on Apple platforms. - - `#include ` no longer works for the macOS target and results - in a compilation failure. macOS ships all required functions in - `math.h`, and clients should use `math.h` instead. - ---- a/qtbase/src/3rdparty/libpng/pngpriv.h -+++ b/qtbase/src/3rdparty/libpng/pngpriv.h -@@ -514,18 +514,8 @@ - */ - # include - --# if (defined(__MWERKS__) && defined(macintosh)) || defined(applec) || \ -- defined(THINK_C) || defined(__SC__) || defined(TARGET_OS_MAC) -- /* We need to check that hasn't already been included earlier -- * as it seems it doesn't agree with , yet we should really use -- * if possible. -- */ --# if !defined(__MATH_H__) && !defined(__MATH_H) && !defined(__cmath__) --# include --# endif --# else --# include --# endif -+# include -+ - # if defined(_AMIGA) && defined(__SASC) && defined(_M68881) - /* Amiga SAS/C: We must include builtin FPU functions when compiling using - * MATH=68881 diff --git a/depends/patches/qt/fix-macos-linker.patch b/depends/patches/qt/fix-macos-linker.patch deleted file mode 100644 index e43968565655..000000000000 --- a/depends/patches/qt/fix-macos-linker.patch +++ /dev/null @@ -1,55 +0,0 @@ -qmake: Don't error out if QMAKE_DEFAULT_LIBDIRS is empty on macOS - -The new linker in Xcode 15 doesn't provide any default linker or -framework paths when requested via -v, but still seems to use the -default paths documented in the ld man page. - -We trust that linker will do the right thing, even if we don't -know of its default linker paths. - -We also need to opt out of the default fallback logic to -set the libdirs to /lib and /usr/lib. - -This may result in UnixMakefileGenerator::findLibraries finding -different libraries than expected, if additional paths are -passed with -L, which will then take precedence for qmake, -even if the linker itself will use the library from the -SDK's default paths. This should hopefully not be an issue -in practice, as we don't turn -lFoo into absolute paths in -qmake, so the only risk is that we're picking up the wrong -prl files and adding additional dependencies that the lib -in the SDK doesn't have. - -Upstream commits: - - Qt 5.15.16: Not yet publicly available. - - Qt dev: cdf64b0e47115cc473e1afd1472b4b09e130b2a5 - -For other Qt branches see -https://codereview.qt-project.org/q/I2347b26e2df0828471373b0e15b8c9089274c65d - ---- old/qtbase/mkspecs/features/toolchain.prf -+++ new/qtbase/mkspecs/features/toolchain.prf -@@ -288,9 +288,12 @@ isEmpty($${target_prefix}.INCDIRS) { - } - } - } -- isEmpty(QMAKE_DEFAULT_LIBDIRS)|isEmpty(QMAKE_DEFAULT_INCDIRS): \ -+ isEmpty(QMAKE_DEFAULT_INCDIRS): \ - !integrity: \ -- error("failed to parse default search paths from compiler output") -+ error("failed to parse default include paths from compiler output") -+ isEmpty(QMAKE_DEFAULT_LIBDIRS): \ -+ !integrity:!darwin: \ -+ error("failed to parse default library paths from compiler output") - QMAKE_DEFAULT_LIBDIRS = $$unique(QMAKE_DEFAULT_LIBDIRS) - } else: ghs { - cmd = $$QMAKE_CXX $$QMAKE_CXXFLAGS -$${LITERAL_HASH} -o /tmp/fake_output /tmp/fake_input.cpp -@@ -412,7 +415,7 @@ isEmpty($${target_prefix}.INCDIRS) { - QMAKE_DEFAULT_INCDIRS = $$split(INCLUDE, $$QMAKE_DIRLIST_SEP) - } - -- unix:if(!cross_compile|host_build) { -+ unix:!darwin:if(!cross_compile|host_build) { - isEmpty(QMAKE_DEFAULT_INCDIRS): QMAKE_DEFAULT_INCDIRS = /usr/include /usr/local/include - isEmpty(QMAKE_DEFAULT_LIBDIRS): QMAKE_DEFAULT_LIBDIRS = /lib /usr/lib - } diff --git a/depends/patches/qt/memory_resource.patch b/depends/patches/qt/memory_resource.patch index 312f0669f631..14e25121c00a 100644 --- a/depends/patches/qt/memory_resource.patch +++ b/depends/patches/qt/memory_resource.patch @@ -14,36 +14,3 @@ and https://bugreports.qt.io/browse/QTBUG-114316 # include # include #else - ---- a/qtbase/src/corelib/global/qcompilerdetection.h -+++ b/qtbase/src/corelib/global/qcompilerdetection.h -@@ -1055,16 +1055,22 @@ - # endif // !_HAS_CONSTEXPR - # endif // !__GLIBCXX__ && !_LIBCPP_VERSION - # endif // Q_OS_QNX --# if (defined(Q_CC_CLANG) || defined(Q_CC_INTEL)) && defined(Q_OS_MAC) && defined(__GNUC_LIBSTD__) \ -- && ((__GNUC_LIBSTD__-0) * 100 + __GNUC_LIBSTD_MINOR__-0 <= 402) -+# if (defined(Q_CC_CLANG) || defined(Q_CC_INTEL)) && defined(Q_OS_MAC) -+# if defined(__GNUC_LIBSTD__) && ((__GNUC_LIBSTD__-0) * 100 + __GNUC_LIBSTD_MINOR__-0 <= 402) - // Apple has not updated libstdc++ since 2007, which means it does not have - // or std::move. Let's disable these features --# undef Q_COMPILER_INITIALIZER_LISTS --# undef Q_COMPILER_RVALUE_REFS --# undef Q_COMPILER_REF_QUALIFIERS -+# undef Q_COMPILER_INITIALIZER_LISTS -+# undef Q_COMPILER_RVALUE_REFS -+# undef Q_COMPILER_REF_QUALIFIERS - // Also disable , since it's clearly not there --# undef Q_COMPILER_ATOMICS --# endif -+# undef Q_COMPILER_ATOMICS -+# endif -+# if defined(__cpp_lib_memory_resource) \ -+ && ((defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 140000) \ -+ || (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 170000)) -+# undef __cpp_lib_memory_resource // Only supported on macOS 14 and iOS 17 -+# endif -+# endif // (defined(Q_CC_CLANG) || defined(Q_CC_INTEL)) && defined(Q_OS_MAC) - # if defined(Q_CC_CLANG) && defined(Q_CC_INTEL) && Q_CC_INTEL >= 1500 - // ICC 15.x and 16.0 have their own implementation of std::atomic, which is activated when in Clang mode - // (probably because libc++'s on OS X failed to compile), but they're missing some diff --git a/depends/patches/qt/zlib-timebits64.patch b/depends/patches/qt/zlib-timebits64.patch deleted file mode 100644 index 139c1dfa77f3..000000000000 --- a/depends/patches/qt/zlib-timebits64.patch +++ /dev/null @@ -1,31 +0,0 @@ -From a566e156b3fa07b566ddbf6801b517a9dba04fa3 Mon Sep 17 00:00:00 2001 -From: Mark Adler -Date: Sat, 29 Jul 2023 22:13:09 -0700 -Subject: [PATCH] Avoid compiler complaints if _TIME_BITS defined when building - zlib. - -zlib does not use time_t, so _TIME_BITS is irrelevant. However it -may be defined anyway as part of a sledgehammer indiscriminately -applied to all builds. - -From https://github.com/madler/zlib/commit/a566e156b3fa07b566ddbf6801b517a9dba04fa3.patch ---- - qtbase/src/3rdparty/zlib/src/gzguts.h | 5 ++--- - 1 file changed, 2 insertions(+), 3 deletions(-) - -diff --git a/qtbase/src/3rdparty/zlib/src/gzguts.h b/qtbase/src/3rdparty/zlib/src/gzguts.h -index e23f831f5..f9375047e 100644 ---- a/qtbase/src/3rdparty/zlib/src/gzguts.h -+++ b/qtbase/src/3rdparty/zlib/src/gzguts.h -@@ -26,9 +26,8 @@ - # ifndef _LARGEFILE_SOURCE - # define _LARGEFILE_SOURCE 1 - # endif --# ifdef _FILE_OFFSET_BITS --# undef _FILE_OFFSET_BITS --# endif -+# undef _FILE_OFFSET_BITS -+# undef _TIME_BITS - #endif - - #ifdef HAVE_HIDDEN diff --git a/doc/dependencies.md b/doc/dependencies.md index cef8516ac5ab..c5bed851f37b 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -33,7 +33,7 @@ You can find installation instructions in the `build-*.md` file for your platfor | [Fontconfig](../depends/packages/fontconfig.mk) | [link](https://www.freedesktop.org/wiki/Software/fontconfig/) | [2.12.6](https://github.com/bitcoin/bitcoin/pull/23495) | 2.6 | Yes | | [FreeType](../depends/packages/freetype.mk) | [link](https://freetype.org) | [2.11.0](https://github.com/bitcoin/bitcoin/commit/01544dd78ccc0b0474571da854e27adef97137fb) | 2.3.0 | Yes | | [qrencode](../depends/packages/qrencode.mk) | [link](https://fukuchi.org/works/qrencode/) | [4.1.1](https://github.com/bitcoin/bitcoin/pull/27312) | | No | -| [Qt](../depends/packages/qt.mk) | [link](https://download.qt.io/official_releases/qt/) | [5.15.3](https://github.com/bitcoin/bitcoin/pull/24668) | [5.11.3](https://github.com/bitcoin/bitcoin/pull/24132) | No | +| [Qt](../depends/packages/qt.mk) | [link](https://download.qt.io/official_releases/qt/) | [5.15.18](https://github.com/dashpay/dash/pull/6949) | [5.11.3](https://github.com/bitcoin/bitcoin/pull/24132) | No | ### Networking | Dependency | Releases | Version used | Minimum required | Runtime | diff --git a/doc/evodb-verify-repair.md b/doc/evodb-verify-repair.md new file mode 100644 index 000000000000..5ebb0edf63ec --- /dev/null +++ b/doc/evodb-verify-repair.md @@ -0,0 +1,160 @@ +# EvoDb Verify and Repair RPC Commands + +### `evodb verify` + +Verifies the integrity of evodb diff records between snapshots stored every 576 blocks. + +**Syntax:** +``` +evodb verify [startBlock] [stopBlock] +``` + +**Parameters:** +- `startBlock` (optional): Starting block height. Defaults to DIP0003 activation height. +- `stopBlock` (optional): Ending block height. Defaults to current chain tip. + +**Returns:** +```json +{ + "startHeight": n, // Actual starting height (may be clamped to DIP0003) + "stopHeight": n, // Ending block height + "diffsRecalculated": 0, // Always 0 for verify mode + "snapshotsVerified": n, // Number of snapshot pairs that passed verification + "verificationErrors": [ // Array of errors (empty if verification passed) + "error message", + ... + ] +} +``` + +**Description:** + +This is a **read-only** operation that checks whether applying the stored diffs between consecutive snapshots produces the expected results. It does not modify the database. + +The command processes snapshot pairs (snapshots are stored every 576 blocks) and applies all diffs between them, verifying that the result matches the target snapshot. If all verification passes, `verificationErrors` will be empty. + +**Use cases:** +- Diagnose suspected evodb corruption +- Verify database integrity after hardware issues +- Confirm successful repair operations + +**Example:** +```bash +# Verify entire chain +dash-cli evodb verify + +# Verify specific range +dash-cli evodb verify 1000 10000 +``` + +--- + +### `evodb repair` + +Repairs corrupted evodb diff records by recalculating them from blockchain data. + +**Syntax:** +``` +evodb repair [startBlock] [stopBlock] +``` + +**Parameters:** +- `startBlock` (optional): Starting block height. Defaults to DIP0003 activation height. +- `stopBlock` (optional): Ending block height. Defaults to current chain tip. + +**Returns:** +```json +{ + "startHeight": n, // Actual starting height (may be clamped to DIP0003) + "stopHeight": n, // Ending block height + "diffsRecalculated": n, // Number of diffs successfully recalculated + "snapshotsVerified": n, // Number of snapshot pairs that passed verification + "verificationErrors": [ // Errors during verification phase + "error message", + ... + ], + "repairErrors": [ // Critical errors during repair phase + "error message", // Non-empty means full reindex required + ... + ] +} +``` + +**Description:** + +This command first runs verification on all snapshot pairs in the specified range. For any pairs that fail verification, it recalculates the diffs from actual blockchain data and writes the corrected diffs to the database. + +The repair process: +1. **Verification phase**: Checks all snapshot pairs for corruption +2. **Repair phase**: For failed pairs, recalculates diffs from blockchain blocks +3. **Database update**: Writes repaired diffs in efficient 16MB batches +4. **Cache clearing**: Clears both diff and list caches to prevent serving stale data + +**Important notes:** +- Requires all blockchain data to be available (blocks must not be pruned in the repair range) +- If repair encounters critical errors (block read failures, missing snapshots), a full reindex is required +- Critical errors are prefixed with "CRITICAL:" in error messages +- Successfully repaired diffs are verified before being committed to the database + +**Use cases:** +- Repair corrupted evodb after unclean shutdown +- Fix database inconsistencies after hardware failures +- Recover from disk corruption without full reindex (when possible) + +**Example:** +```bash +# Repair entire chain +dash-cli evodb repair + +# Repair specific range +dash-cli evodb repair 1000 10000 +``` + +--- + +## When to Use These Commands + +### Use `evodb verify` when: +- You suspect evodb corruption but want to diagnose before taking action +- Verifying integrity after hardware issues or crashes +- Confirming successful repairs + +### Use `evodb repair` when: +- `evodb verify` reports verification errors +- You experience masternode list inconsistencies +- After unclean shutdown or disk errors +- As an alternative to full reindex when snapshots are intact + +### Full reindex required when: +- Repair reports errors prefixed with "CRITICAL:" +- Snapshots are missing or corrupted (cannot be repaired by this tool) +- Block data is unavailable (pruned nodes in repair range) + +--- + +## Technical Details + +### Verification Process +- Processes snapshot pairs sequentially (snapshots stored every 576 blocks) +- Applies all stored diffs between snapshots +- Verifies result matches target snapshot using `CDeterministicMNList::IsEqual` +- Reports all errors without stopping early + +### Repair Process +- Reads actual blockchain blocks from disk +- Processes special transactions to rebuild masternode lists +- Uses dummy coins view (avoids UTXO lookups for historical blocks) +- Calculates correct diffs and verifies before committing +- Fails fast on critical errors (missing blocks, missing snapshots) + +### Performance Considerations +- Repair is I/O intensive (reads blockchain blocks, writes database) +- Progress logged every 100 snapshot pairs +- Database writes batched in 16MB chunks for efficiency +- Caches cleared after repair to ensure consistency + +### Implementation Notes +- Both commands require `::cs_main` lock +- Special handling for initial DIP0003 snapshot (may not exist in older databases) +- Only diffs can be repaired; missing snapshots require full reindex +- Repair verification must pass before diffs are committed to database diff --git a/doc/man/dash-cli.1 b/doc/man/dash-cli.1 index d74ed986f458..1a63739ccfd9 100644 --- a/doc/man/dash-cli.1 +++ b/doc/man/dash-cli.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-CLI "1" "October 2025" "dash-cli v23.0.0" "User Commands" +.TH DASH-CLI "1" "December 2025" "dash-cli v23.0.1" "User Commands" .SH NAME -dash-cli \- manual page for dash-cli v23.0.0 +dash-cli \- manual page for dash-cli v23.0.1 .SH SYNOPSIS .B dash-cli [\fI\,options\/\fR] \fI\, \/\fR[\fI\,params\/\fR] \fI\,Send command to Dash Core\/\fR @@ -15,7 +15,7 @@ dash-cli \- manual page for dash-cli v23.0.0 .B dash-cli [\fI\,options\/\fR] \fI\,help Get help for a command\/\fR .SH DESCRIPTION -Dash Core RPC client version v23.0.0 +Dash Core RPC client version v23.0.1 .SH OPTIONS .HP \-? @@ -162,4 +162,4 @@ The source code is available from . This is experimental software. Distributed under the MIT software license, see the accompanying file COPYING -or +or \ No newline at end of file diff --git a/doc/man/dash-qt.1 b/doc/man/dash-qt.1 index 653b59a47b4a..a05e31737787 100644 --- a/doc/man/dash-qt.1 +++ b/doc/man/dash-qt.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-QT "1" "October 2025" "dash-qt v23.0.0" "User Commands" +.TH DASH-QT "1" "December 2025" "dash-qt v23.0.1" "User Commands" .SH NAME -dash-qt \- manual page for dash-qt v23.0.0 +dash-qt \- manual page for dash-qt v23.0.1 .SH SYNOPSIS .B dash-qt [\fI\,command-line options\/\fR] [\fI\,URI\/\fR] .SH DESCRIPTION -Dash Core version v23.0.0 +Dash Core version v23.0.1 .PP Optional URI is a Dash address in BIP21 URI format. .SH OPTIONS @@ -25,9 +25,9 @@ message) If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: -0000000000000009ba1e8f47851d036bb618a4f6565eb3c32d1f647d450ff195, +00000000000000149d207073b6959004cb457df98e111981c9bf0e4e5190c5db, testnet: -00000107d42829a38e31c1a38c660d621e1ca376a880df1520e85e38af175d3a) +000000598ffa45083b795d85a1e40f13da68b2c6c81e232cb990108f0515f88e) .HP \fB\-blockfilterindex=\fR .IP @@ -125,12 +125,12 @@ Do not keep transactions in the mempool longer than hours (default: .HP \fB\-par=\fR .IP -Set the number of script verification threads (\fB\-24\fR to 15, 0 = auto, <0 = +Set the number of script verification threads (\fB\-14\fR to 15, 0 = auto, <0 = leave that many cores free, default: 0) .HP \fB\-parbls=\fR .IP -Set the number of BLS verification threads (\fB\-24\fR to 33, 0 = auto, <0 = +Set the number of BLS verification threads (\fB\-14\fR to 33, 0 = auto, <0 = leave that many cores free, default: 0) .HP \fB\-persistmempool\fR @@ -870,7 +870,7 @@ is optional). If is not supplied or if = 1, output all debug and trace logging. can be: addrman, bench, blockstorage, chainlocks, cmpctblock, coindb, coinjoin, creditpool, ehf, estimatefee, gobject, http, i2p, instantsend, -ipc, leveldb, libevent, llmq, llmq\-dkg, llmq\-sigs, lock, mempool, +ipc, leveldb, libevent, llmq, llmq\-dkg, llmq\-sigs, mempool, mempoolrej, mnpayments, mnsync, net, netconn, proxy, prune, qt, rand, reindex, rpc, selectcoins, spork, tor, txreconciliation, validation, walletdb, zmq. This option can be specified multiple @@ -1157,7 +1157,7 @@ Set the font weight for bold texts. Possible range 0 to 8 (default: 4) .HP \fB\-font\-weight\-normal\fR .IP -Set the font weight for normal texts. Possible range 0 to 8 (default: 2) +Set the font weight for normal texts. Possible range 0 to 8 (default: 1) .HP \fB\-lang=\fR .IP @@ -1188,4 +1188,4 @@ The source code is available from . This is experimental software. Distributed under the MIT software license, see the accompanying file COPYING -or +or \ No newline at end of file diff --git a/doc/man/dash-tx.1 b/doc/man/dash-tx.1 index 48c8c6622977..cf099e2d19dc 100644 --- a/doc/man/dash-tx.1 +++ b/doc/man/dash-tx.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-TX "1" "October 2025" "dash-tx v23.0.0" "User Commands" +.TH DASH-TX "1" "December 2025" "dash-tx v23.0.1" "User Commands" .SH NAME -dash-tx \- manual page for dash-tx v23.0.0 +dash-tx \- manual page for dash-tx v23.0.1 .SH SYNOPSIS .B dash-tx [\fI\,options\/\fR] \fI\, \/\fR[\fI\,commands\/\fR] \fI\,Update hex-encoded dash transaction\/\fR @@ -9,7 +9,7 @@ dash-tx \- manual page for dash-tx v23.0.0 .B dash-tx [\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] \fI\,Create hex-encoded dash transaction\/\fR .SH DESCRIPTION -Dash Core dash\-tx utility version v23.0.0 +Dash Core dash\-tx utility version v23.0.1 .SH OPTIONS .HP \-? @@ -119,4 +119,4 @@ The source code is available from . This is experimental software. Distributed under the MIT software license, see the accompanying file COPYING -or +or \ No newline at end of file diff --git a/doc/man/dash-wallet.1 b/doc/man/dash-wallet.1 index 8f6403314a0f..e91d618e718b 100644 --- a/doc/man/dash-wallet.1 +++ b/doc/man/dash-wallet.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-WALLET "1" "October 2025" "dash-wallet v23.0.0" "User Commands" +.TH DASH-WALLET "1" "December 2025" "dash-wallet v23.0.1" "User Commands" .SH NAME -dash-wallet \- manual page for dash-wallet v23.0.0 +dash-wallet \- manual page for dash-wallet v23.0.1 .SH DESCRIPTION -Dash Core dash\-wallet version v23.0.0 +Dash Core dash\-wallet version v23.0.1 .PP dash\-wallet is an offline tool for creating and interacting with Dash Core wallet files. By default dash\-wallet will act on wallets in the default mainnet wallet directory in the datadir. @@ -109,4 +109,4 @@ The source code is available from . This is experimental software. Distributed under the MIT software license, see the accompanying file COPYING -or +or \ No newline at end of file diff --git a/doc/man/dashd.1 b/doc/man/dashd.1 index 437f7b0c0017..88b9e1f0a0b1 100644 --- a/doc/man/dashd.1 +++ b/doc/man/dashd.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASHD "1" "October 2025" "dashd v23.0.0" "User Commands" +.TH DASHD "1" "December 2025" "dashd v23.0.1" "User Commands" .SH NAME -dashd \- manual page for dashd v23.0.0 +dashd \- manual page for dashd v23.0.1 .SH SYNOPSIS .B dashd [\fI\,options\/\fR] \fI\,Start Dash Core\/\fR .SH DESCRIPTION -Dash Core version v23.0.0 +Dash Core version v23.0.1 .SH OPTIONS .HP \-? @@ -23,9 +23,9 @@ message) If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: -0000000000000009ba1e8f47851d036bb618a4f6565eb3c32d1f647d450ff195, +00000000000000149d207073b6959004cb457df98e111981c9bf0e4e5190c5db, testnet: -00000107d42829a38e31c1a38c660d621e1ca376a880df1520e85e38af175d3a) +000000598ffa45083b795d85a1e40f13da68b2c6c81e232cb990108f0515f88e) .HP \fB\-blockfilterindex=\fR .IP @@ -123,12 +123,12 @@ Do not keep transactions in the mempool longer than hours (default: .HP \fB\-par=\fR .IP -Set the number of script verification threads (\fB\-24\fR to 15, 0 = auto, <0 = +Set the number of script verification threads (\fB\-14\fR to 15, 0 = auto, <0 = leave that many cores free, default: 0) .HP \fB\-parbls=\fR .IP -Set the number of BLS verification threads (\fB\-24\fR to 33, 0 = auto, <0 = +Set the number of BLS verification threads (\fB\-14\fR to 33, 0 = auto, <0 = leave that many cores free, default: 0) .HP \fB\-persistmempool\fR @@ -868,7 +868,7 @@ is optional). If is not supplied or if = 1, output all debug and trace logging. can be: addrman, bench, blockstorage, chainlocks, cmpctblock, coindb, coinjoin, creditpool, ehf, estimatefee, gobject, http, i2p, instantsend, -ipc, leveldb, libevent, llmq, llmq\-dkg, llmq\-sigs, lock, mempool, +ipc, leveldb, libevent, llmq, llmq\-dkg, llmq\-sigs, mempool, mempoolrej, mnpayments, mnsync, net, netconn, proxy, prune, qt, rand, reindex, rpc, selectcoins, spork, tor, txreconciliation, validation, walletdb, zmq. This option can be specified multiple @@ -1137,4 +1137,4 @@ The source code is available from . This is experimental software. Distributed under the MIT software license, see the accompanying file COPYING -or +or \ No newline at end of file diff --git a/doc/release-notes.md b/doc/release-notes.md index 52c97926a9ec..cdb08b21b89f 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,8 +1,7 @@ -# Dash Core version v23.0.0 +# Dash Core version v23.0.1 -This is a new major version release, bringing new features, various bugfixes and other improvements. -This release is **mandatory** for all masternodes. -This release is optional but recommended for all other nodes. +This is a new patch version release, bringing various bugfixes, performance improvements, and new tooling for database maintenance. +This release is **optional** for all nodes, although recommended. Please report bugs using the issue tracker at GitHub: @@ -22,225 +21,32 @@ dashd/dash-qt (on Linux). ### Downgrade to a version < v23.0.0 Downgrading to a version older than v23.0.0 is not supported, and will -require a reindex. + require a reindex. -# Notable changes +# Release Notes -## EvoDB migration +## New Features -This release introduces a new internal format for masternode state data to support extended addresses and make future updates of this data seamless. Nodes will automatically migrate to the new format. Downgrading to an earlier version would require a full reindex. #6813 +- Added `evodb verify` and `evodb repair` RPC commands for diagnosing and repairing corrupted deterministic masternode list diffs in evodb. See `doc/evodb-verify-repair.md` for full documentation (dash#6969). +- Added automatic verification and repair of evodb diffs at node startup. This helps detect and fix database corruption without manual intervention. Use `-forceevodbrepair` to force re-verification (dash#6999). -## Breaking Change: Block Filter Index Format Update +## Bug Fixes -The compact block filter index format has been updated to include Dash special transaction data, providing feature parity with bloom filters for SPV client support. This change is incompatible with existing blockfilter indexes. Existing blockfilter indexes will automatically be re-created with the new version. +- Fixed HD chain encryption check ordering issue where `LoadHDChain()` would fail if `CRYPTED_HDCHAIN` records were read before `MASTER_KEY` records during wallet loading (dash#6944). +- Corrected BLS scheme setting in `MigrateLegacyDiffs()` when `nVersion` is present. The legacy scheme was only being set when the `nVersion` field was missing instead of whenever a `pubKeyOperator` field is present (dash#6961). +- Fixed BLS benchmarks crash when ran independently without BLS_DKG benchmarks (dash#6977). +- Fixed build issue on Debian 13 by including `QDebug` directly in Qt code (dash#7009). +- Fixed `BuildTestVectors` call to adjust batch size based on output flag (dash#7013). -- The blockfilter index now includes fields from Dash special transactions: - - ProRegTx (masternode registration) - - ProUpServTx (masternode service updates) - - ProUpRegTx (masternode operator updates) - - ProUpRevTx (masternode revocation) - - AssetLockTx (platform credit outputs) -- A versioning system has been added to detect incompatible indexes on startup -- The index version is now 2 (previously unversioned) +## Performance Improvements -### Benefits +- Removed duplicated check of the same key in the InstantSend database (dash#6964). -- SPV clients can now detect and track Dash-specific transactions -- Feature parity between bloom filters and compact block filters -- Protection against serving incorrect filter data to light clients +## Dependencies -## Other notable changes +- Updated Qt from 5.15.14 to 5.15.18, which includes security patches for CVE-2025-4211, CVE-2025-5455, and CVE-2025-30348 (dash#6949). -* Performance of block validation in Dash Core has been significantly improved, index from - scratch and reindex are up to 20% faster compared to v22.x. -* To help prevent fingerprinting transactions created by the Dash Core wallet, change output - amounts are now randomized. (#6685) -* Dash Core will no longer migrate EvoDb databases generated in v19 and v20, users upgrading - from these versions are recommended to run `-reindex` to rebuild all databases and indexes. (#6579) - -## Updated REST APIs - -- The `/headers/` and `/blockfilterheaders/` endpoints have been updated to use - a query parameter instead of a path parameter to specify the result count. The - count parameter is now optional and defaults to 5 for both endpoints. The old - endpoints are still functional, and have no documented behaviour change. (#6784) - - For `/headers`, use - `GET /rest/headers/.?count=` instead of - `GET /rest/headers//.` (deprecated) - - For `/blockfilterheaders/`, use - `GET /rest/blockfilterheaders//.?count=` instead of - `GET /rest/blockfilterheaders///.` (deprecated) - -## P2P and network changes - -- `MIN_PEER_PROTO_VERSION` has been bumped to `70221`. (#6877) -- `PROTO_VERSION` has been bumped to `70238` with the introduction of the `platformban` p2p message. - This message allows evonodes to initiate PoSe masternode banning in Dash Core for evonodes which are - not providing adequate service on Dash Platform. -- `cycleHash` field in `isdlock` message will now represent a DKG cycle starting block of the signing quorum instead of a DKG cycle starting block corresponding to the current chain height. While this is fully backwards compatible with older versions of Dash Core, other implementations might not be expecting this, so the P2P protocol version was bumped to 70237. (#6608) -- UNIX domain sockets can now be used for proxy connections. Set `-onion` or `-proxy` to the local socket path with the prefix `unix:` (e.g. `-onion=unix:/home/me/torsocket`). UNIX socket paths are now accepted for `-zmqpubrawblock` and `-zmqpubrawtx` with the format `-zmqpubrawtx=unix:/path/to/file`. (#6634) - -## Updated RPCs - - -* `protx revoke` will now use the legacy scheme version for legacy masternodes instead of defaulting to the - highest `ProUpRevTx` version. (#6729) - -* The RPCs `protx register_legacy`, `protx register_fund_legacy`, `protx register_prepare_legacy` and - `protx update_registrar_legacy` have been deprecated in Dash Core v23 and may be removed in a future version. - They can be re-enabled with the runtime argument `-deprecatedrpc=legacy_mn`. - -* The argument `legacy` in `bls generate` has been deprecated in Dash Core v23 and may be ignored in a future version. - It can be re-enabled with the runtime argument `-deprecatedrpc=legacy_mn`. (#6723) - -* A new optional field `submit` has been introduced to the `protx revoke`, `protx update_registrar`, `protx update_service` RPCs. It behaves identically to `submit` in `protx register` or `protx register_fund`. (#6720) - -* The `instantsendtoaddress` RPC was deprecated in Dash Core v0.15 and is now removed. (#6686) - -* `quorum rotationinfo` will now expect the third param to be a JSON array. (#6628) - -* `getislocks` will now return the request `id` for each InstantSend Lock in results. (#6607) - -* `coinjoin status` is a new RPC that reports the status message of all running mix sessions. `coinjoin start` will no longer - report errors from mix sessions; users are recommended to query the status using `coinjoin status` instead. (#6594) - -* The RPCs `masternode current` and `masternode winner` were deprecated in Dash Core v0.17 and are now removed. The `getpoolinfo` RPC was deprecated in Dash Core v0.15 and is now removed. (#6567) - -* The `-deprecatedrpc=addresses` configuration option has been removed. RPCs `gettxout`, `getrawtransaction`, `decoderawtransaction`, `decodescript`, `gettransaction verbose=true` and REST endpoints `/rest/tx`, `/rest/getutxos`, `/rest/block` no longer return the `addresses` and `reqSigs` fields, which were previously deprecated in 21.0. (#6569) - -* The `getblock` RPC command now supports verbosity level 3, containing transaction inputs `prevout` information. The existing `/rest/block/` REST endpoint is modified to contain this information too. Every `vin` field will contain an additional `prevout` subfield. (#6577) - -* A new RPC `newkeypool` has been added, which will flush (entirely clear and refill) the keypool. (#6577) - -* The return value of the `pruneblockchain` method had an off-by-one bug and now returns the height of the last pruned block as documented. (#6784) - -* The `listdescriptors` RPC now includes an optional coinjoin field to identify CoinJoin descriptors. (#6835) - -### Extended address support - -Dash Core v23 introduces support for extended masternode addresses, replacing legacy single-endpoint fields and -enabling more flexible network setups. The following RPC changes implement this functionality: - -#### New and deprecated fields - -* The keys `platformP2PPort` and `platformHTTPPort` have been deprecated for the following RPCs, `decoderawtransaction`, - `decodepsbt`, `getblock`, `getrawtransaction`, `gettransaction`, `masternode status` (only the `dmnState` key), - `protx diff`, `protx listdiff` and has been replaced with the key `addresses`. - * The deprecated key is still available without additional runtime arguments, but is liable to be removed in future versions - of Dash Core. (#6811) -* The key `service` has been deprecated for some RPCs (`decoderawtransaction`, `decodepsbt`, `getblock`, `getrawtransaction`, - `gettransaction`, `masternode status` (only for the `dmnState` key), `protx diff`, `protx listdiff`) and has been replaced - with the key `addresses`. - * This deprecation also extends to the functionally identical key, `address` in `masternode list` (and its alias, `masternodelist`). - * The deprecated key is still available without additional runtime arguments, but is liable to be removed in future versions - of Dash Core. - * This change does not affect `masternode status` (except for the `dmnState` key) as `service` does not represent a payload - value but the external address advertised by the active masternode. (#6665) - -#### Renamed input fields - -* The input field `ipAndPort` has been renamed to `coreP2PAddrs`. In addition to accepting a string, `coreP2PAddrs` can - now accept an array of strings, subject to validation rules. -* The input field `platformP2PPort` has been renamed to `platformP2PAddrs`. In addition to numeric inputs (i.e. ports), - the field can now accept a string (i.e. an addr:port pair) and arrays of strings (i.e. multiple addr:port pairs), - subject to validation rules. -* The input field `platformHTTPPort` has been renamed to `platformHTTPSAddrs`. In addition to numeric inputs (i.e. ports), - the field can now accept a string (i.e. an addr:port pair) and arrays of strings (i.e. multiple addr:port pairs), - subject to validation rules. - -#### Reporting behavior - -* When reporting on extended address payloads, `platformP2PPort` and `platformHTTPPort` will read the port value from - `netInfo[PLATFORM_P2P][0]` and `netInfo[PLATFORM_HTTPS][0]` respectively as both fields are subsumed into `netInfo`. - * If `netInfo` is blank (which is allowed by ProRegTx), `platformP2PPort` and `platformHTTPPort` will report `-1` to indicate - that the port number cannot be determined. - * `protx listdiff` will not report `platformP2PPort` or `platformHTTPPort` if the legacy fields were not updated (i.e. - changes to `netInfo` will not translate into reporting). This is because `platformP2PPort` or `platformHTTPPort` have - dedicated diff flags and post-consolidation, all changes are now affected by `netInfo`'s diff flag. - - To avoid the perception of changes to fields that are not serialized by extended address payloads, data from `netInfo` will - not be translated for this RPC call. (#6666) -* The field `addresses` will now also report on platform P2P and platform HTTPS endpoints as `addresses['platform_p2p']` - and `addresses['platform_https']` respectively. - * On payloads before extended addresses, if a masternode update affects `platformP2PPort` and/or `platformHTTPPort` - but does not affect `netInfo`, `protx listdiff` does not contain enough information to report on the masternode's - address and will report the changed port paired with the dummy address `255.255.255.255`. - - This does not affect `protx listdiff` queries where `netInfo` was updated or diffs relating to masternodes that - have upgraded to extended addresses. - -#### Masternode rules - -* If the masternode is eligible for extended addresses, `protx register{,_evo}` and `protx register_fund{,_evo}` will: - * Continue allowing `coreP2PAddrs` to be left blank, as long as `platformP2PAddrs` and `platformHTTPSAddrs` are also left blank. - * Attempting to populate any three address fields will make populating all fields mandatory. - * Continue to allow specifying only the port number for `platformP2PAddrs` and `platformHTTPSAddrs`, pairing it with the address - from the first `coreP2PAddrs` entry. This mirrors existing behavior. - * This method of entry may not be available in future releases of Dash Core and operators are recommended to switch over to - explicitly specifying (arrays of) addr:port strings for all address fields. (#6666) - * No longer default to the core P2P port if a port is not specified in the addr:port pair. All ports must be specified explicitly. - -Masternodes ineligible for extended addresses (i.e. all nodes before fork activation or legacy BLS nodes) must follow - the legacy rules by continuing to: - -* Default to the core P2P port if provided an address without a port. -* Specify `platformP2PAddrs` and `platformHTTPSAddrs` even if they wish to keep `coreP2PAddrs` blank. - -## Updated settings - -- BIP157 compact block filters are now automatically enabled for masternodes. This improves privacy for light clients - connecting to masternodes and enables better support for pruned nodes. When a node is configured as a masternode - (via `-masternodeblsprivkey`), both `-peerblockfilters` and `-blockfilterindex=basic` are automatically enabled. - Note that this feature requires approximately 1GB+ of additional disk space for the block filter index. - Regular nodes keep the previous defaults (disabled). Masternodes can still explicitly disable these features - if needed by setting `-peerblockfilters=0` or `-blockfilterindex=0` (#6711). - -- Setting `-maxconnections=0` will now disable `-dnsseed` and `-listen` (users may still set them to override) (#6649). - -- Ports specified in `-port` and `-rpcport` options are now validated at startup. Values that previously worked and were considered valid can now result in errors. (#6634) - -## Statistics - -- IPv6 hosts are now supported by the StatsD client. -- `-statshost` now accepts URLs to allow specifying the protocol, host and port in one argument. -- Specifying invalid values will no longer result in the silent disablement of the StatsD client and will now cause errors at startup. (#6837) - -- The arguments `-statsenabled`, `-statsns`, `-statshostname` have been removed. They were previously deprecated in v22.0 and will no longer be recognized on runtime. (#6505) - -## Build System - -GCC 11.1 or later, or Clang 16.0 or later, are now required to compile Dash Core. (#6389) - -## Command-line Options - -### Changes in Existing Command-line Options - -- `-platform-user` removed in v23 (deprecated in v22 and never used by platform). (#6482) - -## Wallet - -- Wallet passphrases and mnemonic passphrases may now contain null characters. (#6780 #6792) - -- `receivedby` RPCs now include coinbase transactions. Previously, the following wallet RPCs excluded coinbase transactions: `getreceivedbyaddress`, `getreceivedbylabel`, `listreceivedbyaddress`, `listreceivedbylabel`. The previous behaviour can be restored using `-deprecatedrpc=exclude_coinbase` (may be removed in a future release). A new option `include_immature_coinbase` (default=`false`) determines whether to account for immature coinbase transactions. (#6601) - -### Mobile CoinJoin Compatibility - -- Fixed an issue where CoinJoin funds mixed in the Dash Android wallet were invisible when importing the mnemonic into Dash Core. Descriptor Wallets now include an additional default descriptor for mobile CoinJoin funds, ensuring seamless wallet migration and complete fund visibility across different Dash wallet implementations. -- This is a breaking change that increases the default number of descriptors from 2 to 3 on mainnet (internal, external, mobile CoinJoin) for newly created descriptor wallets only - existing wallets are unaffected. (#6835) - -## GUI changes - -- A mnemonic verification dialog is now shown after creating a new HD wallet, requiring users to verify they have written down their recovery phrase (#6946). -- A new menu item "Show Recovery Phrase…" has been added to the Settings menu to view the recovery phrase for existing HD wallets (#6946). -- Added governance proposal voting functionality to the Qt interface. Users with masternode voting keys can now vote on governance proposals directly from the governance tab via right-click context menu. Added masternode count display to governance tab, showing how many masternodes the wallet can vote with. (#6690) -- Added a menu item to restore a wallet from a backup file. (#6648) - -## RPC Wallet - -- `unloadwallet` now fails if a rescan is in progress. (#6759) -- `gettransaction`, `listtransactions`, `listsinceblock` now return the `abandoned` field for all transactions. (#6780) +# v23.0.1 Change log See detailed [set of changes][set-of-changes]. @@ -250,9 +56,7 @@ Thanks to everyone who directly contributed to this release: - Kittywhiskers Van Gogh - Konstantin Akimov -- Odysseas Gabrielides - PastaPastaPasta -- thephez - UdjinM6 As well as everyone that submitted issues, reviewed pull requests and helped @@ -262,6 +66,7 @@ debug the release candidates. These releases are considered obsolete. Old release notes can be found here: +- [v23.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.0.0.md) released Nov/10/2025 - [v22.1.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.3.md) released Jul/15/2025 - [v22.1.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.2.md) released Apr/15/2025 - [v22.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.1.md) released Feb/17/2025 @@ -320,4 +125,4 @@ These releases are considered obsolete. Old release notes can be found here: - [v0.10.x](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.10.0.md) released Sep/25/2014 - [v0.9.x](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.9.0.md) released Mar/13/2014 -[set-of-changes]: https://github.com/dashpay/dash/compare/v22.1.3...dashpay:v23.0.0 \ No newline at end of file +[set-of-changes]: https://github.com/dashpay/dash/compare/v23.0.0...dashpay:v23.0.1 diff --git a/doc/release-notes/dash/release-notes-23.0.0.md b/doc/release-notes/dash/release-notes-23.0.0.md new file mode 100644 index 000000000000..52c97926a9ec --- /dev/null +++ b/doc/release-notes/dash/release-notes-23.0.0.md @@ -0,0 +1,323 @@ +# Dash Core version v23.0.0 + +This is a new major version release, bringing new features, various bugfixes and other improvements. +This release is **mandatory** for all masternodes. +This release is optional but recommended for all other nodes. + +Please report bugs using the issue tracker at GitHub: + + + +# Upgrading and downgrading + +## How to Upgrade + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over /Applications/Dash-Qt (on Mac) or +dashd/dash-qt (on Linux). + +## Downgrade warning + +### Downgrade to a version < v23.0.0 + +Downgrading to a version older than v23.0.0 is not supported, and will +require a reindex. + +# Notable changes + +## EvoDB migration + +This release introduces a new internal format for masternode state data to support extended addresses and make future updates of this data seamless. Nodes will automatically migrate to the new format. Downgrading to an earlier version would require a full reindex. #6813 + +## Breaking Change: Block Filter Index Format Update + +The compact block filter index format has been updated to include Dash special transaction data, providing feature parity with bloom filters for SPV client support. This change is incompatible with existing blockfilter indexes. Existing blockfilter indexes will automatically be re-created with the new version. + +- The blockfilter index now includes fields from Dash special transactions: + - ProRegTx (masternode registration) + - ProUpServTx (masternode service updates) + - ProUpRegTx (masternode operator updates) + - ProUpRevTx (masternode revocation) + - AssetLockTx (platform credit outputs) +- A versioning system has been added to detect incompatible indexes on startup +- The index version is now 2 (previously unversioned) + +### Benefits + +- SPV clients can now detect and track Dash-specific transactions +- Feature parity between bloom filters and compact block filters +- Protection against serving incorrect filter data to light clients + +## Other notable changes + +* Performance of block validation in Dash Core has been significantly improved, index from + scratch and reindex are up to 20% faster compared to v22.x. +* To help prevent fingerprinting transactions created by the Dash Core wallet, change output + amounts are now randomized. (#6685) +* Dash Core will no longer migrate EvoDb databases generated in v19 and v20, users upgrading + from these versions are recommended to run `-reindex` to rebuild all databases and indexes. (#6579) + +## Updated REST APIs + +- The `/headers/` and `/blockfilterheaders/` endpoints have been updated to use + a query parameter instead of a path parameter to specify the result count. The + count parameter is now optional and defaults to 5 for both endpoints. The old + endpoints are still functional, and have no documented behaviour change. (#6784) + + For `/headers`, use + `GET /rest/headers/.?count=` instead of + `GET /rest/headers//.` (deprecated) + + For `/blockfilterheaders/`, use + `GET /rest/blockfilterheaders//.?count=` instead of + `GET /rest/blockfilterheaders///.` (deprecated) + +## P2P and network changes + +- `MIN_PEER_PROTO_VERSION` has been bumped to `70221`. (#6877) +- `PROTO_VERSION` has been bumped to `70238` with the introduction of the `platformban` p2p message. + This message allows evonodes to initiate PoSe masternode banning in Dash Core for evonodes which are + not providing adequate service on Dash Platform. +- `cycleHash` field in `isdlock` message will now represent a DKG cycle starting block of the signing quorum instead of a DKG cycle starting block corresponding to the current chain height. While this is fully backwards compatible with older versions of Dash Core, other implementations might not be expecting this, so the P2P protocol version was bumped to 70237. (#6608) +- UNIX domain sockets can now be used for proxy connections. Set `-onion` or `-proxy` to the local socket path with the prefix `unix:` (e.g. `-onion=unix:/home/me/torsocket`). UNIX socket paths are now accepted for `-zmqpubrawblock` and `-zmqpubrawtx` with the format `-zmqpubrawtx=unix:/path/to/file`. (#6634) + +## Updated RPCs + + +* `protx revoke` will now use the legacy scheme version for legacy masternodes instead of defaulting to the + highest `ProUpRevTx` version. (#6729) + +* The RPCs `protx register_legacy`, `protx register_fund_legacy`, `protx register_prepare_legacy` and + `protx update_registrar_legacy` have been deprecated in Dash Core v23 and may be removed in a future version. + They can be re-enabled with the runtime argument `-deprecatedrpc=legacy_mn`. + +* The argument `legacy` in `bls generate` has been deprecated in Dash Core v23 and may be ignored in a future version. + It can be re-enabled with the runtime argument `-deprecatedrpc=legacy_mn`. (#6723) + +* A new optional field `submit` has been introduced to the `protx revoke`, `protx update_registrar`, `protx update_service` RPCs. It behaves identically to `submit` in `protx register` or `protx register_fund`. (#6720) + +* The `instantsendtoaddress` RPC was deprecated in Dash Core v0.15 and is now removed. (#6686) + +* `quorum rotationinfo` will now expect the third param to be a JSON array. (#6628) + +* `getislocks` will now return the request `id` for each InstantSend Lock in results. (#6607) + +* `coinjoin status` is a new RPC that reports the status message of all running mix sessions. `coinjoin start` will no longer + report errors from mix sessions; users are recommended to query the status using `coinjoin status` instead. (#6594) + +* The RPCs `masternode current` and `masternode winner` were deprecated in Dash Core v0.17 and are now removed. The `getpoolinfo` RPC was deprecated in Dash Core v0.15 and is now removed. (#6567) + +* The `-deprecatedrpc=addresses` configuration option has been removed. RPCs `gettxout`, `getrawtransaction`, `decoderawtransaction`, `decodescript`, `gettransaction verbose=true` and REST endpoints `/rest/tx`, `/rest/getutxos`, `/rest/block` no longer return the `addresses` and `reqSigs` fields, which were previously deprecated in 21.0. (#6569) + +* The `getblock` RPC command now supports verbosity level 3, containing transaction inputs `prevout` information. The existing `/rest/block/` REST endpoint is modified to contain this information too. Every `vin` field will contain an additional `prevout` subfield. (#6577) + +* A new RPC `newkeypool` has been added, which will flush (entirely clear and refill) the keypool. (#6577) + +* The return value of the `pruneblockchain` method had an off-by-one bug and now returns the height of the last pruned block as documented. (#6784) + +* The `listdescriptors` RPC now includes an optional coinjoin field to identify CoinJoin descriptors. (#6835) + +### Extended address support + +Dash Core v23 introduces support for extended masternode addresses, replacing legacy single-endpoint fields and +enabling more flexible network setups. The following RPC changes implement this functionality: + +#### New and deprecated fields + +* The keys `platformP2PPort` and `platformHTTPPort` have been deprecated for the following RPCs, `decoderawtransaction`, + `decodepsbt`, `getblock`, `getrawtransaction`, `gettransaction`, `masternode status` (only the `dmnState` key), + `protx diff`, `protx listdiff` and has been replaced with the key `addresses`. + * The deprecated key is still available without additional runtime arguments, but is liable to be removed in future versions + of Dash Core. (#6811) +* The key `service` has been deprecated for some RPCs (`decoderawtransaction`, `decodepsbt`, `getblock`, `getrawtransaction`, + `gettransaction`, `masternode status` (only for the `dmnState` key), `protx diff`, `protx listdiff`) and has been replaced + with the key `addresses`. + * This deprecation also extends to the functionally identical key, `address` in `masternode list` (and its alias, `masternodelist`). + * The deprecated key is still available without additional runtime arguments, but is liable to be removed in future versions + of Dash Core. + * This change does not affect `masternode status` (except for the `dmnState` key) as `service` does not represent a payload + value but the external address advertised by the active masternode. (#6665) + +#### Renamed input fields + +* The input field `ipAndPort` has been renamed to `coreP2PAddrs`. In addition to accepting a string, `coreP2PAddrs` can + now accept an array of strings, subject to validation rules. +* The input field `platformP2PPort` has been renamed to `platformP2PAddrs`. In addition to numeric inputs (i.e. ports), + the field can now accept a string (i.e. an addr:port pair) and arrays of strings (i.e. multiple addr:port pairs), + subject to validation rules. +* The input field `platformHTTPPort` has been renamed to `platformHTTPSAddrs`. In addition to numeric inputs (i.e. ports), + the field can now accept a string (i.e. an addr:port pair) and arrays of strings (i.e. multiple addr:port pairs), + subject to validation rules. + +#### Reporting behavior + +* When reporting on extended address payloads, `platformP2PPort` and `platformHTTPPort` will read the port value from + `netInfo[PLATFORM_P2P][0]` and `netInfo[PLATFORM_HTTPS][0]` respectively as both fields are subsumed into `netInfo`. + * If `netInfo` is blank (which is allowed by ProRegTx), `platformP2PPort` and `platformHTTPPort` will report `-1` to indicate + that the port number cannot be determined. + * `protx listdiff` will not report `platformP2PPort` or `platformHTTPPort` if the legacy fields were not updated (i.e. + changes to `netInfo` will not translate into reporting). This is because `platformP2PPort` or `platformHTTPPort` have + dedicated diff flags and post-consolidation, all changes are now affected by `netInfo`'s diff flag. + + To avoid the perception of changes to fields that are not serialized by extended address payloads, data from `netInfo` will + not be translated for this RPC call. (#6666) +* The field `addresses` will now also report on platform P2P and platform HTTPS endpoints as `addresses['platform_p2p']` + and `addresses['platform_https']` respectively. + * On payloads before extended addresses, if a masternode update affects `platformP2PPort` and/or `platformHTTPPort` + but does not affect `netInfo`, `protx listdiff` does not contain enough information to report on the masternode's + address and will report the changed port paired with the dummy address `255.255.255.255`. + + This does not affect `protx listdiff` queries where `netInfo` was updated or diffs relating to masternodes that + have upgraded to extended addresses. + +#### Masternode rules + +* If the masternode is eligible for extended addresses, `protx register{,_evo}` and `protx register_fund{,_evo}` will: + * Continue allowing `coreP2PAddrs` to be left blank, as long as `platformP2PAddrs` and `platformHTTPSAddrs` are also left blank. + * Attempting to populate any three address fields will make populating all fields mandatory. + * Continue to allow specifying only the port number for `platformP2PAddrs` and `platformHTTPSAddrs`, pairing it with the address + from the first `coreP2PAddrs` entry. This mirrors existing behavior. + * This method of entry may not be available in future releases of Dash Core and operators are recommended to switch over to + explicitly specifying (arrays of) addr:port strings for all address fields. (#6666) + * No longer default to the core P2P port if a port is not specified in the addr:port pair. All ports must be specified explicitly. + +Masternodes ineligible for extended addresses (i.e. all nodes before fork activation or legacy BLS nodes) must follow + the legacy rules by continuing to: + +* Default to the core P2P port if provided an address without a port. +* Specify `platformP2PAddrs` and `platformHTTPSAddrs` even if they wish to keep `coreP2PAddrs` blank. + +## Updated settings + +- BIP157 compact block filters are now automatically enabled for masternodes. This improves privacy for light clients + connecting to masternodes and enables better support for pruned nodes. When a node is configured as a masternode + (via `-masternodeblsprivkey`), both `-peerblockfilters` and `-blockfilterindex=basic` are automatically enabled. + Note that this feature requires approximately 1GB+ of additional disk space for the block filter index. + Regular nodes keep the previous defaults (disabled). Masternodes can still explicitly disable these features + if needed by setting `-peerblockfilters=0` or `-blockfilterindex=0` (#6711). + +- Setting `-maxconnections=0` will now disable `-dnsseed` and `-listen` (users may still set them to override) (#6649). + +- Ports specified in `-port` and `-rpcport` options are now validated at startup. Values that previously worked and were considered valid can now result in errors. (#6634) + +## Statistics + +- IPv6 hosts are now supported by the StatsD client. +- `-statshost` now accepts URLs to allow specifying the protocol, host and port in one argument. +- Specifying invalid values will no longer result in the silent disablement of the StatsD client and will now cause errors at startup. (#6837) + +- The arguments `-statsenabled`, `-statsns`, `-statshostname` have been removed. They were previously deprecated in v22.0 and will no longer be recognized on runtime. (#6505) + +## Build System + +GCC 11.1 or later, or Clang 16.0 or later, are now required to compile Dash Core. (#6389) + +## Command-line Options + +### Changes in Existing Command-line Options + +- `-platform-user` removed in v23 (deprecated in v22 and never used by platform). (#6482) + +## Wallet + +- Wallet passphrases and mnemonic passphrases may now contain null characters. (#6780 #6792) + +- `receivedby` RPCs now include coinbase transactions. Previously, the following wallet RPCs excluded coinbase transactions: `getreceivedbyaddress`, `getreceivedbylabel`, `listreceivedbyaddress`, `listreceivedbylabel`. The previous behaviour can be restored using `-deprecatedrpc=exclude_coinbase` (may be removed in a future release). A new option `include_immature_coinbase` (default=`false`) determines whether to account for immature coinbase transactions. (#6601) + +### Mobile CoinJoin Compatibility + +- Fixed an issue where CoinJoin funds mixed in the Dash Android wallet were invisible when importing the mnemonic into Dash Core. Descriptor Wallets now include an additional default descriptor for mobile CoinJoin funds, ensuring seamless wallet migration and complete fund visibility across different Dash wallet implementations. +- This is a breaking change that increases the default number of descriptors from 2 to 3 on mainnet (internal, external, mobile CoinJoin) for newly created descriptor wallets only - existing wallets are unaffected. (#6835) + +## GUI changes + +- A mnemonic verification dialog is now shown after creating a new HD wallet, requiring users to verify they have written down their recovery phrase (#6946). +- A new menu item "Show Recovery Phrase…" has been added to the Settings menu to view the recovery phrase for existing HD wallets (#6946). +- Added governance proposal voting functionality to the Qt interface. Users with masternode voting keys can now vote on governance proposals directly from the governance tab via right-click context menu. Added masternode count display to governance tab, showing how many masternodes the wallet can vote with. (#6690) +- Added a menu item to restore a wallet from a backup file. (#6648) + +## RPC Wallet + +- `unloadwallet` now fails if a rescan is in progress. (#6759) +- `gettransaction`, `listtransactions`, `listsinceblock` now return the `abandoned` field for all transactions. (#6780) + +See detailed [set of changes][set-of-changes]. + +# Credits + +Thanks to everyone who directly contributed to this release: + +- Kittywhiskers Van Gogh +- Konstantin Akimov +- Odysseas Gabrielides +- PastaPastaPasta +- thephez +- UdjinM6 + +As well as everyone that submitted issues, reviewed pull requests and helped +debug the release candidates. + +# Older releases + +These releases are considered obsolete. Old release notes can be found here: + +- [v22.1.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.3.md) released Jul/15/2025 +- [v22.1.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.2.md) released Apr/15/2025 +- [v22.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.1.md) released Feb/17/2025 +- [v22.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.0.md) released Feb/10/2025 +- [v22.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.0.0.md) released Dec/12/2024 +- [v21.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.1.1.md) released Oct/22/2024 +- [v21.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.1.0.md) released Aug/8/2024 +- [v21.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.2.md) released Aug/1/2024 +- [v21.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.0.md) released Jul/25/2024 +- [v20.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.1.1.md) released April/3/2024 +- [v20.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.1.0.md) released March/5/2024 +- [v20.0.4](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.0.4.md) released Jan/13/2024 +- [v20.0.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.0.3.md) released December/26/2023 +- [v20.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.0.2.md) released December/06/2023 +- [v20.0.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.0.1.md) released November/18/2023 +- [v20.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.0.0.md) released November/15/2023 +- [v19.3.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-19.3.0.md) released July/31/2023 +- [v19.2.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-19.2.0.md) released June/19/2023 +- [v19.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-19.1.0.md) released May/22/2023 +- [v19.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-19.0.0.md) released Apr/14/2023 +- [v18.2.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-18.2.2.md) released Mar/21/2023 +- [v18.2.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-18.2.1.md) released Jan/17/2023 +- [v18.2.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-18.2.0.md) released Jan/01/2023 +- [v18.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-18.1.1.md) released January/08/2023 +- [v18.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-18.1.0.md) released October/09/2022 +- [v18.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-18.0.2.md) released October/09/2022 +- [v18.0.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-18.0.1.md) released August/17/2022 +- [v0.17.0.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.17.0.3.md) released June/07/2021 +- [v0.17.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.17.0.2.md) released May/19/2021 +- [v0.16.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.16.1.1.md) released November/17/2020 +- [v0.16.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.16.1.0.md) released November/14/2020 +- [v0.16.0.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.16.0.1.md) released September/30/2020 +- [v0.15.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.15.0.0.md) released Febrary/18/2020 +- [v0.14.0.5](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.5.md) released December/08/2019 +- [v0.14.0.4](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.4.md) released November/22/2019 +- [v0.14.0.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.3.md) released August/15/2019 +- [v0.14.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.2.md) released July/4/2019 +- [v0.14.0.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.1.md) released May/31/2019 +- [v0.14.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.14.0.md) released May/22/2019 +- [v0.13.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.13.3.md) released Apr/04/2019 +- [v0.13.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.13.2.md) released Mar/15/2019 +- [v0.13.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.13.1.md) released Feb/9/2019 +- [v0.13.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.13.0.md) released Jan/14/2019 +- [v0.12.3.4](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.3.4.md) released Dec/14/2018 +- [v0.12.3.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.3.3.md) released Sep/19/2018 +- [v0.12.3.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.3.2.md) released Jul/09/2018 +- [v0.12.3.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.3.1.md) released Jul/03/2018 +- [v0.12.2.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.2.3.md) released Jan/12/2018 +- [v0.12.2.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.2.2.md) released Dec/17/2017 +- [v0.12.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.2.md) released Nov/08/2017 +- [v0.12.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.1.md) released Feb/06/2017 +- [v0.12.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.12.0.md) released Aug/15/2015 +- [v0.11.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.11.2.md) released Mar/04/2015 +- [v0.11.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.11.1.md) released Feb/10/2015 +- [v0.11.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.11.0.md) released Jan/15/2015 +- [v0.10.x](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.10.0.md) released Sep/25/2014 +- [v0.9.x](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-0.9.0.md) released Mar/13/2014 + +[set-of-changes]: https://github.com/dashpay/dash/compare/v22.1.3...dashpay:v23.0.0 \ No newline at end of file diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index c5fe3fbdfddb..4db5412e16d6 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -64,6 +65,8 @@ int main(int argc, char** argv) SetupBenchArgs(argsman); SapphireAutoDetect(); SHA256AutoDetect(); + BLSInit(); + bls::bls_legacy_scheme.store(false); std::string error; if (!argsman.ParseParameters(argc, argv, error)) { tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); diff --git a/src/bench/bls.cpp b/src/bench/bls.cpp index 9199f1f33360..e6206d241d9f 100644 --- a/src/bench/bls.cpp +++ b/src/bench/bls.cpp @@ -255,7 +255,7 @@ static void BLS_Verify_Batched(benchmark::Bench& bench) std::vector sigs; std::vector msgHashes; std::vector invalid; - BuildTestVectors(bench.output() ? 1000 : 1, 10, pubKeys, secKeys, sigs, msgHashes, invalid); + BuildTestVectors(bench.output() ? 1000 : 1, bench.output() ? 10 : 1, pubKeys, secKeys, sigs, msgHashes, invalid); // Benchmark. size_t i = 0; @@ -311,7 +311,7 @@ static void BLS_Verify_BatchedParallel(benchmark::Bench& bench) std::vector sigs; std::vector msgHashes; std::vector invalid; - BuildTestVectors(bench.output() ? 1000 : 1, 10, pubKeys, secKeys, sigs, msgHashes, invalid); + BuildTestVectors(bench.output() ? 1000 : 1, bench.output() ? 10 : 1, pubKeys, secKeys, sigs, msgHashes, invalid); std::list>> futures; diff --git a/src/chainparams.cpp b/src/chainparams.cpp index be5b6110fa2f..59050c95155d 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -219,10 +219,10 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_V24].useEHF = true; // The best chain should have at least this much work. - consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000ae8d578b28f390b9c630"); // 2361500 + consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000b0c59b8fc6afc98570c6"); // 2381436 // By default assume that the signatures in ancestors of this block are valid. - consensus.defaultAssumeValid = uint256S("0x0000000000000009ba1e8f47851d036bb618a4f6565eb3c32d1f647d450ff195"); // 2361500 + consensus.defaultAssumeValid = uint256S("0x00000000000000149d207073b6959004cb457df98e111981c9bf0e4e5190c5db"); // 2381436 /** * The message start string is designed to be unlikely to occur in normal data. @@ -418,10 +418,10 @@ class CTestNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_V24].useEHF = true; // The best chain should have at least this much work. - consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000036073cd80626b9c"); // 1295700 + consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000036bc6a08fcad9d5"); // 1374139 // By default assume that the signatures in ancestors of this block are valid. - consensus.defaultAssumeValid = uint256S("0x00000107d42829a38e31c1a38c660d621e1ca376a880df1520e85e38af175d3a"); // 1295700 + consensus.defaultAssumeValid = uint256S("0x000000598ffa45083b795d85a1e40f13da68b2c6c81e232cb990108f0515f88e"); // 1374139 pchMessageStart[0] = 0xce; pchMessageStart[1] = 0xe2; diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index edd30d189b28..fc4c51b43823 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -25,12 +25,14 @@ #include #include +#include #include #include static const std::string DB_LIST_SNAPSHOT = "dmn_S3"; static const std::string DB_LIST_DIFF = "dmn_D4"; // Bumped for nVersion-first format static const std::string DB_LIST_DIFF_LEGACY = "dmn_D3"; // Legacy format key +static const std::string DB_LIST_REPAIRED = "dmn_R1"; uint64_t CDeterministicMN::GetInternalId() const { @@ -1488,9 +1490,9 @@ bool CDeterministicMNManager::MigrateLegacyDiffs(const CBlockIndex* const tip_in stateDiff.fields |= CDeterministicMNStateDiff::Field_nVersion; stateDiff.state.nVersion = dmn->pdmnState->nVersion; } - if (stateDiff.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) { - stateDiff.state.pubKeyOperator.SetLegacy(stateDiff.state.nVersion == ProTxVersion::LegacyBLS); - } + } + if (stateDiff.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) { + stateDiff.state.pubKeyOperator.SetLegacy(stateDiff.state.nVersion == ProTxVersion::LegacyBLS); } convertedDiff.updatedMNs.emplace(internalId, stateDiff); } @@ -1570,3 +1572,342 @@ bool CDeterministicMNManager::MigrateLegacyDiffs(const CBlockIndex* const tip_in return true; } + +CDeterministicMNManager::RecalcDiffsResult CDeterministicMNManager::RecalculateAndRepairDiffs( + const CBlockIndex* start_index, const CBlockIndex* stop_index, ChainstateManager& chainman, + BuildListFromBlockFunc build_list_func, bool repair) +{ + RecalcDiffsResult result; + result.start_height = start_index->nHeight; + result.stop_height = stop_index->nHeight; + + const auto& consensus_params = Params().GetConsensus(); + + // Clamp start height to DIP0003 activation (no snapshots/diffs exist before this) + if (start_index->nHeight < consensus_params.DIP0003Height) { + start_index = stop_index->GetAncestor(consensus_params.DIP0003Height); + if (!start_index) { + result.verification_errors.push_back(strprintf("Stop height %d is below DIP0003 activation height %d", + stop_index->nHeight, consensus_params.DIP0003Height)); + return result; + } + LogPrintf("CDeterministicMNManager::%s -- Clamped start height from %d to DIP0003 activation height %d\n", + __func__, result.start_height, consensus_params.DIP0003Height); + // Update result to reflect the clamped start height + result.start_height = start_index->nHeight; + } + + // Collect all snapshot blocks in the range + std::vector snapshot_blocks = CollectSnapshotBlocks(start_index, stop_index, consensus_params); + + if (snapshot_blocks.empty()) { + result.verification_errors.push_back("Could not find starting snapshot"); + return result; + } + + if (snapshot_blocks.size() < 2) { + result.verification_errors.push_back(strprintf("Need at least 2 snapshots, found %d", snapshot_blocks.size())); + return result; + } + + LogPrintf("CDeterministicMNManager::%s -- Processing %d snapshot pairs between heights %d and %d\n", __func__, + snapshot_blocks.size() - 1, result.start_height, result.stop_height); + + // Storage for recalculated diffs if we plan to repair + std::vector> recalculated_diffs; + + // Process each pair of consecutive snapshots + for (size_t i = 0; i < snapshot_blocks.size() - 1; ++i) { + const CBlockIndex* from_index = snapshot_blocks[i]; + const CBlockIndex* to_index = snapshot_blocks[i + 1]; + + // Load the snapshots from disk + CDeterministicMNList from_snapshot; + CDeterministicMNList to_snapshot; + + bool has_from_snapshot = m_evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, from_index->GetBlockHash()), from_snapshot); + bool has_to_snapshot = m_evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, to_index->GetBlockHash()), to_snapshot); + + // Handle missing snapshots + if (!has_from_snapshot) { + // The initial snapshot at DIP0003 activation might not exist in the database on nodes + // that synced before the fix to explicitly write it. This is the only acceptable case. + if (from_index->nHeight == consensus_params.DIP0003Height) { + // Create an empty initial snapshot (matching what GetListForBlockInternal does) + from_snapshot = CDeterministicMNList(from_index->GetBlockHash(), from_index->nHeight, 0); + LogPrintf("CDeterministicMNManager::%s -- Using empty initial snapshot at DIP0003 height %d\n", + __func__, from_index->nHeight); + } else { + // Any other missing snapshot is critical corruption beyond our repair capability + result.verification_errors.push_back(strprintf("CRITICAL: Snapshot missing at height %d. " + "This cannot be repaired by this tool - full reindex required.", from_index->nHeight)); + return result; + } + } + + if (!has_to_snapshot) { + // Missing target snapshot is always critical - we cannot repair snapshots, only diffs + result.verification_errors.push_back(strprintf("CRITICAL: Snapshot missing at height %d. " + "This cannot be repaired by this tool - full reindex required.", to_index->nHeight)); + return result; + } + + // Log progress periodically (every 100 snapshot pairs) to avoid spam + if (i % 100 == 0) { + LogPrintf("CDeterministicMNManager::%s -- Progress: verifying snapshot pair %d/%d (heights %d-%d)\n", + __func__, i + 1, snapshot_blocks.size() - 1, from_index->nHeight, to_index->nHeight); + } + + // Verify this snapshot pair + bool is_snapshot_pair_valid = VerifySnapshotPair(from_index, to_index, from_snapshot, to_snapshot, result); + + // If repair mode is enabled and verification failed, recalculate diffs from blockchain + if (repair && !is_snapshot_pair_valid) { + auto temp_diffs = RepairSnapshotPair(from_index, to_index, from_snapshot, to_snapshot, build_list_func, result); + if (temp_diffs.empty()) { + // RepairSnapshotPair failed - this is a critical error, cannot continue + return result; + } + // Only commit diffs if recalculation verification passed + recalculated_diffs.insert(recalculated_diffs.end(), temp_diffs.begin(), temp_diffs.end()); + result.diffs_recalculated += temp_diffs.size(); + } + } + + // Write repaired diffs to database + if (repair) { + WriteRepairedDiffs(recalculated_diffs, result); + } + + return result; +} + +bool CDeterministicMNManager::IsRepaired() const { return m_evoDb.Exists(DB_LIST_REPAIRED); } + +void CDeterministicMNManager::CompleteRepair() +{ + auto dbTx = m_evoDb.BeginTransaction(); + m_evoDb.Write(DB_LIST_REPAIRED, 1); + dbTx->Commit(); + // flush it to disk + if (!m_evoDb.CommitRootTransaction()) { + LogPrintf("CDeterministicMNManager::%s -- Failed to commit to evoDB\n", __func__); + assert(false); + } +} + +std::vector CDeterministicMNManager::CollectSnapshotBlocks( + const CBlockIndex* start_index, const CBlockIndex* stop_index, const Consensus::Params& consensus_params) +{ + std::vector snapshot_blocks; + + // Add the starting snapshot (find the snapshot at or before start) + // Walk backwards to find a snapshot block (divisible by DISK_SNAPSHOT_PERIOD) + // or the initial snapshot at DIP0003 activation height + const CBlockIndex* snapshot_start_index = start_index; + while (snapshot_start_index && snapshot_start_index->nHeight > consensus_params.DIP0003Height && + (snapshot_start_index->nHeight % DISK_SNAPSHOT_PERIOD) != 0) { + snapshot_start_index = snapshot_start_index->pprev; + } + + if (!snapshot_start_index) { + return snapshot_blocks; // Empty vector indicates error + } + + // Collect all snapshot blocks up to and including the stop block + snapshot_blocks.push_back(snapshot_start_index); + + // Find all subsequent snapshot heights + int current_snapshot_height = snapshot_start_index->nHeight; + while (true) { + // Calculate next snapshot height + int next_snapshot_height; + if (current_snapshot_height == consensus_params.DIP0003Height) { + // If we're at DIP0003 activation (initial snapshot), next is at first regular interval + next_snapshot_height = ((consensus_params.DIP0003Height / DISK_SNAPSHOT_PERIOD) + 1) * DISK_SNAPSHOT_PERIOD; + } else { + // Otherwise, add DISK_SNAPSHOT_PERIOD + next_snapshot_height = current_snapshot_height + DISK_SNAPSHOT_PERIOD; + } + + if (next_snapshot_height > stop_index->nHeight) { + break; + } + + const CBlockIndex* next_snapshot_index = stop_index->GetAncestor(next_snapshot_height); + if (!next_snapshot_index) { + break; + } + + snapshot_blocks.push_back(next_snapshot_index); + current_snapshot_height = next_snapshot_height; + } + + return snapshot_blocks; +} + +bool CDeterministicMNManager::VerifySnapshotPair( + const CBlockIndex* from_index, const CBlockIndex* to_index, const CDeterministicMNList& from_snapshot, + const CDeterministicMNList& to_snapshot, RecalcDiffsResult& result) +{ + // Verify this snapshot pair by applying all stored diffs sequentially + CDeterministicMNList test_list = from_snapshot; + + try { + for (int nHeight = from_index->nHeight + 1; nHeight <= to_index->nHeight; ++nHeight) { + const CBlockIndex* pIndex = to_index->GetAncestor(nHeight); + if (!pIndex) { + result.verification_errors.push_back(strprintf("Failed to get ancestor at height %d", nHeight)); + return false; + } + + CDeterministicMNListDiff diff; + if (!m_evoDb.Read(std::make_pair(DB_LIST_DIFF, pIndex->GetBlockHash()), diff)) { + result.verification_errors.push_back(strprintf("Failed to read diff at height %d", nHeight)); + return false; + } + + diff.nHeight = nHeight; + test_list.ApplyDiff(pIndex, diff); + } + } catch (const std::exception& e) { + result.verification_errors.push_back(strprintf("Exception during verification: %s", e.what())); + return false; + } + + // Verify that applying all diffs results in the target snapshot + bool is_snapshot_pair_valid = test_list.IsEqual(to_snapshot); + + if (is_snapshot_pair_valid) { + result.snapshots_verified++; + } else { + result.verification_errors.push_back( + strprintf("Verification failed between snapshots at heights %d and %d: " + "Applied diffs do not match target snapshot", + from_index->nHeight, to_index->nHeight)); + } + + return is_snapshot_pair_valid; +} + +std::vector> CDeterministicMNManager::RepairSnapshotPair( + const CBlockIndex* from_index, const CBlockIndex* to_index, const CDeterministicMNList& from_snapshot, + const CDeterministicMNList& to_snapshot, BuildListFromBlockFunc build_list_func, RecalcDiffsResult& result) +{ + CDeterministicMNList current_list = from_snapshot; + // Temporary storage for recalculated diffs (one per block in this snapshot interval) + std::vector> temp_diffs; + temp_diffs.reserve(to_index->nHeight - from_index->nHeight); + + LogPrintf("CDeterministicMNManager::%s -- Repairing: recalculating diffs between snapshots at heights %d and %d\n", + __func__, from_index->nHeight, to_index->nHeight); + + try { + for (int nHeight = from_index->nHeight + 1; nHeight <= to_index->nHeight; ++nHeight) { + const CBlockIndex* pIndex = to_index->GetAncestor(nHeight); + + // Read the actual block from disk + CBlock block; + if (!node::ReadBlockFromDisk(block, pIndex, Params().GetConsensus())) { + result.repair_errors.push_back(strprintf("CRITICAL: Failed to read block at height %d. " + "Cannot repair - full reindex required.", nHeight)); + return {}; // Critical error - cannot continue repair + } + + // Use a dummy coins view to avoid UTXO lookups. At chain tip, coins from + // historical blocks may already be spent. Since these blocks were fully + // validated when originally connected, we don't need to re-verify coin + // availability - we only need to extract special transactions. + CCoinsView view_dummy; + CCoinsViewCache view(&view_dummy); + + // Build the new list by processing this block's special transactions + // Starting from current_list (our trusted state), not from corrupted diffs + CDeterministicMNList next_list; + BlockValidationState state; + if (!build_list_func(block, pIndex->pprev, current_list, view, false, state, next_list)) { + result.repair_errors.push_back( + strprintf("CRITICAL: Failed to build list for block at height %d: %s. " + "Cannot repair - full reindex required.", nHeight, state.ToString())); + return {}; // Critical error - cannot continue repair + } + + // Set the correct block hash + next_list.SetBlockHash(pIndex->GetBlockHash()); + + // Calculate the diff between current and next + CDeterministicMNListDiff recalc_diff = current_list.BuildDiff(next_list); + recalc_diff.nHeight = nHeight; + // Store in temporary vector for this snapshot pair + temp_diffs.emplace_back(pIndex->GetBlockHash(), recalc_diff); + + // Move forward + current_list = std::move(next_list); + } + + // Verify that applying all diffs results in the target snapshot + if (current_list.IsEqual(to_snapshot)) { + LogPrintf("CDeterministicMNManager::%s -- Successfully recalculated %d diffs between heights %d and %d\n", + __func__, temp_diffs.size(), from_index->nHeight, to_index->nHeight); + return temp_diffs; // Success - return recalculated diffs + } else { + result.repair_errors.push_back( + strprintf("CRITICAL: Recalculation failed between snapshots at heights %d and %d: " + "Applied diffs do not match target snapshot. Cannot repair - full reindex required.", + from_index->nHeight, to_index->nHeight)); + return {}; // Failed verification - return empty vector + } + } catch (const std::exception& e) { + result.repair_errors.push_back(strprintf("CRITICAL: Exception during recalculation: %s. " + "Cannot repair - full reindex required.", e.what())); + return {}; // Exception - return empty vector + } +} + +void CDeterministicMNManager::WriteRepairedDiffs( + const std::vector>& recalculated_diffs, RecalcDiffsResult& result) +{ + AssertLockNotHeld(cs); + + if (recalculated_diffs.empty()) { + return; + } + + CDBBatch batch(m_evoDb.GetRawDB()); + const size_t BATCH_SIZE_THRESHOLD = 1 << 24; // 16MB + size_t diffs_written = 0; + + LogPrintf("CDeterministicMNManager::%s -- Writing %d repaired diffs to database...\n", + __func__, recalculated_diffs.size()); + + for (const auto& [block_hash, diff] : recalculated_diffs) { + batch.Write(std::make_pair(DB_LIST_DIFF, block_hash), diff); + diffs_written++; + + // Write batch when it gets too large + if (batch.SizeEstimate() >= BATCH_SIZE_THRESHOLD) { + LogPrintf("CDeterministicMNManager::%s -- Flushing batch (%d diffs written so far)...\n", + __func__, diffs_written); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + } + } + + // Write any remaining diffs in the batch + if (batch.SizeEstimate() > 0) { + LogPrintf("CDeterministicMNManager::%s -- Writing final batch...\n", __func__); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + } + + // Clear caches for repaired diffs so next read gets fresh data from disk + // Must clear both diff cache and list cache since lists were built from old diffs + LOCK(cs); + for (const auto& [block_hash, diff] : recalculated_diffs) { + mnListDiffsCache.erase(block_hash); + mnListsCache.erase(block_hash); + } + + LogPrintf("CDeterministicMNManager::%s -- Successfully repaired %d diffs (caches cleared)\n", __func__, + recalculated_diffs.size()); +} diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index e195577a5e3e..fdeacc5a49f5 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -34,6 +34,8 @@ class CEvoDB; class CSimplifiedMNList; class CSimplifiedMNListEntry; class CMasternodeMetaMan; +class ChainstateManager; +class CSpecialTxProcessor; class TxValidationState; struct RPCResult; @@ -436,6 +438,46 @@ class CDeterministicMNList return GetMN(p->first); } + // Compare two masternode lists for equality, ignoring non-deterministic members. + // Non-deterministic members (nTotalRegisteredCount, internalId) can differ between + // nodes due to different sync histories, but don't affect consensus validity. + bool IsEqual(const CDeterministicMNList& rhs) const + { + // Compare deterministic metadata + if (blockHash != rhs.blockHash || + nHeight != rhs.nHeight || + mnUniquePropertyMap != rhs.mnUniquePropertyMap) { + return false; + } + + // Compare map sizes (actual entries compared below) + // Note: Not comparing nTotalRegisteredCount (non-deterministic) + if (mnMap.size() != rhs.mnMap.size() || + mnInternalIdMap.size() != rhs.mnInternalIdMap.size()) { + return false; + } + + // Compare each masternode entry + for (const auto& [proTxHash, dmn] : mnMap) { + auto dmn_rhs = rhs.mnMap.find(proTxHash); + if (dmn_rhs == nullptr) { + return false; + } + + // Compare deterministic masternode fields + // Note: Not comparing internalId (non-deterministic) + if (dmn->proTxHash != dmn_rhs->get()->proTxHash || + dmn->collateralOutpoint != dmn_rhs->get()->collateralOutpoint || + dmn->nOperatorReward != dmn_rhs->get()->nOperatorReward || + dmn->nType != dmn_rhs->get()->nType || + // Use SerializeHash for pdmnState to avoid enumerating all state fields + SerializeHash(*dmn->pdmnState) != SerializeHash(*dmn_rhs->get()->pdmnState)) { + return false; + } + } + return true; + } + private: template [[nodiscard]] uint256 GetUniquePropertyHash(const T& v) const @@ -679,6 +721,33 @@ class CDeterministicMNManager void DoMaintenance() EXCLUSIVE_LOCKS_REQUIRED(!cs, !cs_cleanup); + // Recalculate and optionally repair diffs between snapshots + struct RecalcDiffsResult { + int start_height{0}; + int stop_height{0}; + int diffs_recalculated{0}; + int snapshots_verified{0}; + std::vector verification_errors; + std::vector repair_errors; + }; + + // Callback type for building a new MN list from a block + using BuildListFromBlockFunc = std::function pindexPrev, + const CDeterministicMNList& prevList, + const CCoinsViewCache& view, + bool debugLogs, + BlockValidationState& state, + CDeterministicMNList& mnListRet)>; + + [[nodiscard]] RecalcDiffsResult RecalculateAndRepairDiffs(const CBlockIndex* start_index, + const CBlockIndex* stop_index, ChainstateManager& chainman, + BuildListFromBlockFunc build_list_func, bool repair) + EXCLUSIVE_LOCKS_REQUIRED(!cs); + [[nodiscard]] bool IsRepaired() const; + void CompleteRepair(); + // Migration support for nVersion-first CDeterministicMNStateDiff format [[nodiscard]] bool IsMigrationRequired() const EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); [[nodiscard]] bool MigrateLegacyDiffs(const CBlockIndex* const tip_index) EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); @@ -686,6 +755,18 @@ class CDeterministicMNManager private: void CleanupCache(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs); CDeterministicMNList GetListForBlockInternal(gsl::not_null pindex) EXCLUSIVE_LOCKS_REQUIRED(cs); + + // Helper methods for RecalculateAndRepairDiffs + std::vector CollectSnapshotBlocks(const CBlockIndex* start_index, const CBlockIndex* stop_index, + const Consensus::Params& consensus_params); + bool VerifySnapshotPair(const CBlockIndex* from_index, const CBlockIndex* to_index, + const CDeterministicMNList& from_snapshot, const CDeterministicMNList& to_snapshot, + RecalcDiffsResult& result); + std::vector> RepairSnapshotPair( + const CBlockIndex* from_index, const CBlockIndex* to_index, const CDeterministicMNList& from_snapshot, + const CDeterministicMNList& to_snapshot, BuildListFromBlockFunc build_list_func, RecalcDiffsResult& result); + void WriteRepairedDiffs(const std::vector>& recalculated_diffs, + RecalcDiffsResult& result) EXCLUSIVE_LOCKS_REQUIRED(!cs); }; bool CheckProRegTx(CDeterministicMNManager& dmnman, const CTransaction& tx, gsl::not_null pindexPrev, TxValidationState& state, const CCoinsViewCache& view, bool check_sigs); diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 45d022b7acda..3316f51256a1 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -175,10 +175,22 @@ bool CSpecialTxProcessor::BuildNewListFromBlock(const CBlock& block, gsl::not_nu BlockValidationState& state, CDeterministicMNList& mnListRet) { AssertLockHeld(cs_main); + CDeterministicMNList oldList = m_dmnman.GetListForBlock(pindexPrev); + return RebuildListFromBlock(block, pindexPrev, oldList, view, debugLogs, state, mnListRet); +} + +bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_null pindexPrev, + const CDeterministicMNList& prevList, const CCoinsViewCache& view, + bool debugLogs, BlockValidationState& state, + CDeterministicMNList& mnListRet) +{ + // Verify that prevList either represents an empty/initial state (default-constructed), + // or it matches the previous block's hash. + assert(prevList == CDeterministicMNList() || prevList.GetBlockHash() == pindexPrev->GetBlockHash()); int nHeight = pindexPrev->nHeight + 1; - CDeterministicMNList oldList = m_dmnman.GetListForBlock(pindexPrev); + CDeterministicMNList oldList = prevList; CDeterministicMNList newList = oldList; newList.SetBlockHash(uint256()); // we can't know the final block hash, so better not return a (invalid) block hash newList.SetHeight(nHeight); @@ -234,13 +246,18 @@ bool CSpecialTxProcessor::BuildNewListFromBlock(const CBlock& block, gsl::not_nu dmn->collateralOutpoint = proTx.collateralOutpoint; } - Coin coin; - CAmount expectedCollateral = GetMnType(proTx.nType).collat_amount; - if (!proTx.collateralOutpoint.hash.IsNull() && (!view.GetCoin(dmn->collateralOutpoint, coin) || - coin.IsSpent() || coin.out.nValue != expectedCollateral)) { - // should actually never get to this point as CheckProRegTx should have handled this case. - // We do this additional check nevertheless to be 100% sure - return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-collateral"); + // Complain about spent collaterals only when we process the tip. + // This is safe because blocks below the tip were verified + // when they were connected initially. + if (!view.GetBestBlock().IsNull()) { + Coin coin; + CAmount expectedCollateral = GetMnType(proTx.nType).collat_amount; + if (!proTx.collateralOutpoint.hash.IsNull() && (!view.GetCoin(dmn->collateralOutpoint, coin) || + coin.IsSpent() || coin.out.nValue != expectedCollateral)) { + // should actually never get to this point as CheckProRegTx should have handled this case. + // We do this additional check nevertheless to be 100% sure + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-collateral"); + } } auto replacedDmn = newList.GetMNByCollateral(dmn->collateralOutpoint); diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index 84f74cd06e85..de293d0dfaba 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -79,6 +79,12 @@ class CSpecialTxProcessor const CCoinsViewCache& view, bool debugLogs, BlockValidationState& state, CDeterministicMNList& mnListRet) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + // Variant that takes an explicit starting list instead of loading from GetListForBlock + // Used for rebuilding diffs from trusted snapshots + bool RebuildListFromBlock(const CBlock& block, gsl::not_null pindexPrev, + const CDeterministicMNList& prevList, const CCoinsViewCache& view, bool debugLogs, + BlockValidationState& state, CDeterministicMNList& mnListRet); + private: bool CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const CCbTx& cbTx, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/init.cpp b/src/init.cpp index e8028aa0a354..12ebb1154bb4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -81,9 +81,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -511,6 +513,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-assumevalid=", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocksdir=", "Specify directory to hold blocks subdirectory for *.dat files (default: )", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-fastprune", "Use smaller block files and lower minimum prune height for testing purposes", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-tinyblk", "Use smaller block files for testing purposes", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); #if HAVE_SYSTEM argsman.AddArg("-blocknotify=", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif @@ -719,6 +722,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-checkmempool=", strprintf("Run mempool consistency checks every transactions. Use 0 to disable. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-checkpoints", strprintf("Enable rejection of any forks from the known historical chain until block %s (default: %u)", defaultChainParams->Checkpoints().GetHeight(), DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-deprecatedrpc=", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-forceevodbrepair", "Force evodb masternode list diff verification and repair on startup, even if already repaired (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitancestorcount=", strprintf("Do not accept transactions if number of in-mempool ancestors is or more (default: %u)", DEFAULT_ANCESTOR_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitancestorsize=", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitdescendantcount=", strprintf("Do not accept transactions if any ancestor would have or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); @@ -2348,6 +2352,53 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LogPrintf("Filling coin cache with masternode UTXOs: done in %dms\n", Ticks(SteadyClock::now() - start)); } + if (fReindex || fReindexChainState) { + LogPrintf("Skipping evodb repair during reindex\n"); + node.dmnman->CompleteRepair(); // Mark as repaired since we're rebuilding fresh + } else if (node.dmnman->IsRepaired() && !args.GetBoolArg("-forceevodbrepair", false)) { + LogPrintf("Masternode list diffs are already repaired\n"); + } else { + const CBlockIndex* start_index; + const CBlockIndex* stop_index; + { + LOCK(cs_main); + const auto& consensus_params = Params().GetConsensus(); + start_index = chainman.ActiveChain()[consensus_params.DIP0003Height]; + stop_index = chainman.ActiveChain().Tip(); + } + + if (start_index && stop_index && start_index->nHeight < stop_index->nHeight) { + LogPrintf("Verifying and repairing masternode list diffs...\n"); + const auto start{SteadyClock::now()}; + // Create a callback that wraps CSpecialTxProcessor::BuildNewListFromBlock + auto build_list_func = [&node](const CBlock& block, gsl::not_null pindexPrev, + const CDeterministicMNList& prevList, const CCoinsViewCache& view, + bool debugLogs, BlockValidationState& state, + CDeterministicMNList& mnListRet) -> bool { + return node.chain_helper->special_tx->RebuildListFromBlock(block, pindexPrev, prevList, view, debugLogs, state, mnListRet); + }; + auto result = node.dmnman->RecalculateAndRepairDiffs(start_index, stop_index, chainman, build_list_func, true); + + if (!result.verification_errors.empty()) { + LogPrintf("WARNING: Verification errors:\n%s\n", Join(result.verification_errors, "\n")); + } + + if (!result.repair_errors.empty()) { + // Critical errors occurred - reindex required + LogPrintf("Failed to repair masternode list diffs. Database corruption detected. " /* Continued */ + "Please restart with -reindex to rebuild the database.\n" + "Errors:\n%s\n", + Join(result.repair_errors, "\n")); + StartShutdown(); + return; + } + node.dmnman->CompleteRepair(); + LogPrintf("Successfully repaired %d masternode list diffs, verified %d snapshots in %ds\n", + result.diffs_recalculated, result.snapshots_verified, + Ticks(SteadyClock::now() - start)); + } + } + if (node.mn_activeman != nullptr) { node.mn_activeman->Init(chainman.ActiveTip()); } diff --git a/src/instantsend/db.cpp b/src/instantsend/db.cpp index 695e297403bf..a22349d508ed 100644 --- a/src/instantsend/db.cpp +++ b/src/instantsend/db.cpp @@ -280,11 +280,7 @@ InstantSendLockPtr CInstantSendDb::GetInstantSendLockByHashInternal(const uint25 ret = std::make_shared(); bool exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret); if (!exists || (::SerializeHash(*ret) != hash)) { - ret = std::make_shared(); - exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret); - if (!exists || (::SerializeHash(*ret) != hash)) { - ret = nullptr; - } + ret = nullptr; } islockCache.insert(hash, ret); return ret; diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 0a5ca022fa73..bc27517f967f 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -589,12 +589,14 @@ void UnlinkPrunedFiles(const std::set& setFilesToPrune) static FlatFileSeq BlockFileSeq() { - return FlatFileSeq(gArgs.GetBlocksDirPath(), "blk", gArgs.GetBoolArg("-fastprune", false) ? 0x4000 /* 16kb */ : BLOCKFILE_CHUNK_SIZE); + return FlatFileSeq(gArgs.GetBlocksDirPath(), "blk", + gArgs.GetBoolArg("-fastprune", false) ? 0x4000 /* 16kb */ : + (gArgs.GetBoolArg("-tinyblk", false) ? 0x10000 /* 64kb */ : BLOCKFILE_CHUNK_SIZE)); } static FlatFileSeq UndoFileSeq() { - return FlatFileSeq(gArgs.GetBlocksDirPath(), "rev", UNDOFILE_CHUNK_SIZE); + return FlatFileSeq(gArgs.GetBlocksDirPath(), "rev", gArgs.GetBoolArg("-tinyblk", false) ? 0x10000 /* 64kb */ : UNDOFILE_CHUNK_SIZE); } FILE* OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 394d1d97165c..e78adbb79884 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include #include diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index 81bf1ecd7814..be2383047a38 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -1710,6 +1710,175 @@ static RPCHelpMan protx_listdiff() }; } +// Helper function for evodb verify/repair commands +static UniValue evodb_verify_or_repair_impl(const JSONRPCRequest& request, bool repair) +{ + const NodeContext& node = EnsureAnyNodeContext(request.context); + ChainstateManager& chainman = EnsureChainman(node); + CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman); + CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper); + + const CBlockIndex* start_index; + const CBlockIndex* stop_index; + + { + LOCK(::cs_main); + // Default to DIP0003 activation height if startBlock not specified + if (request.params[0].isNull()) { + const auto& consensus_params = Params().GetConsensus(); + start_index = chainman.ActiveChain()[consensus_params.DIP0003Height]; + if (!start_index) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot find DIP0003 activation block"); + } + } else { + uint256 start_block_hash = ParseBlock(request.params[0], chainman, "startBlock"); + start_index = chainman.m_blockman.LookupBlockIndex(start_block_hash); + if (!start_index) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Start block not found"); + } + } + + // Default to chain tip if stopBlock not specified + if (request.params[1].isNull()) { + stop_index = chainman.ActiveChain().Tip(); + if (!stop_index) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot find chain tip"); + } + } else { + uint256 stop_block_hash = ParseBlock(request.params[1], chainman, "stopBlock"); + stop_index = chainman.m_blockman.LookupBlockIndex(stop_block_hash); + if (!stop_index) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Stop block not found"); + } + } + } + + int start_height = start_index->nHeight; + int stop_height = stop_index->nHeight; + + // Validation + if (stop_height < start_height) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "stopBlock must be >= startBlock"); + } + + // Create a callback that wraps CSpecialTxProcessor::RebuildListFromBlock + auto build_list_func = [&chain_helper](const CBlock& block, gsl::not_null pindexPrev, + const CDeterministicMNList& prevList, const CCoinsViewCache& view, + bool debugLogs, BlockValidationState& state, + CDeterministicMNList& mnListRet) -> bool { + return chain_helper.special_tx->RebuildListFromBlock(block, pindexPrev, prevList, view, debugLogs, state, mnListRet); + }; + + // Call the dmnman method to do the work + auto recalc_result = dmnman.RecalculateAndRepairDiffs(start_index, stop_index, chainman, build_list_func, repair); + + // Convert result to UniValue + UniValue result(UniValue::VOBJ); + UniValue verification_errors(UniValue::VARR); + + for (const auto& error : recalc_result.verification_errors) { + verification_errors.push_back(error); + } + + result.pushKV("startHeight", recalc_result.start_height); + result.pushKV("stopHeight", recalc_result.stop_height); + result.pushKV("diffsRecalculated", recalc_result.diffs_recalculated); + result.pushKV("snapshotsVerified", recalc_result.snapshots_verified); + result.pushKV("verificationErrors", verification_errors); + + // Only include repair errors if we're in repair mode + if (repair) { + UniValue repair_errors(UniValue::VARR); + for (const auto& error : recalc_result.repair_errors) { + repair_errors.push_back(error); + } + result.pushKV("repairErrors", repair_errors); + } + + return result; +} + +static RPCHelpMan evodb_verify() +{ + return RPCHelpMan{"evodb verify", + "\nVerifies evodb diff records between specified block heights.\n" + "Checks that all diffs applied between snapshots in the range match the saved snapshots in evodb.\n" + "This is a read-only operation that does not modify the database.\n" + "If no heights are specified, defaults to the full range from DIP0003 activation to chain tip.\n", + { + {"startBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The starting block height (defaults to DIP0003 activation height)."}, + {"stopBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The ending block height (defaults to current chain tip)."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::NUM, "startHeight", "Actual starting block height (may differ from input if clamped to DIP0003 activation)"}, + {RPCResult::Type::NUM, "stopHeight", "Ending block height"}, + {RPCResult::Type::NUM, "diffsRecalculated", "Number of diffs recalculated (always 0 for verify-only mode)"}, + {RPCResult::Type::NUM, "snapshotsVerified", "Number of snapshot pairs that passed verification"}, + {RPCResult::Type::ARR, "verificationErrors", "List of verification errors (empty if verification passed)", + { + {RPCResult::Type::STR, "", "Error message"}, + } + }, + } + }, + RPCExamples{ + HelpExampleCli("evodb verify", "") + + HelpExampleCli("evodb verify", "1000 2000") + + HelpExampleRpc("evodb", "\"verify\"") + + HelpExampleRpc("evodb", "\"verify\", 1000, 2000") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + return evodb_verify_or_repair_impl(request, false); +}, + }; +} + +static RPCHelpMan evodb_repair() +{ + return RPCHelpMan{"evodb repair", + "\nRepairs corrupted evodb diff records between specified block heights.\n" + "First verifies all diffs applied between snapshots in the range.\n" + "If verification fails, recalculates diffs from blockchain data and replaces corrupted records.\n" + "If no heights are specified, defaults to the full range from DIP0003 activation to chain tip.\n", + { + {"startBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The starting block height (defaults to DIP0003 activation height)."}, + {"stopBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The ending block height (defaults to current chain tip)."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::NUM, "startHeight", "Actual starting block height (may differ from input if clamped to DIP0003 activation)"}, + {RPCResult::Type::NUM, "stopHeight", "Ending block height"}, + {RPCResult::Type::NUM, "diffsRecalculated", "Number of diffs successfully recalculated and written to database"}, + {RPCResult::Type::NUM, "snapshotsVerified", "Number of snapshot pairs that passed verification"}, + {RPCResult::Type::ARR, "verificationErrors", "Errors encountered during verification phase (empty if verification passed)", + { + {RPCResult::Type::STR, "", "Error message"}, + } + }, + {RPCResult::Type::ARR, "repairErrors", "Critical errors encountered during repair phase (non-empty means full reindex required)", + { + {RPCResult::Type::STR, "", "Error message"}, + } + }, + } + }, + RPCExamples{ + HelpExampleCli("evodb repair", "") + + HelpExampleCli("evodb repair", "1000 2000") + + HelpExampleRpc("evodb", "\"repair\"") + + HelpExampleRpc("evodb", "\"repair\", 1000, 2000") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + return evodb_verify_or_repair_impl(request, true); +}, + }; +} + static RPCHelpMan protx_help() { return RPCHelpMan{ @@ -1877,6 +2046,8 @@ void RegisterEvoRPCCommands(CRPCTable& tableRPC) {"evo", &protx_help}, {"evo", &protx_diff}, {"evo", &protx_listdiff}, + {"hidden", &evodb_verify}, + {"hidden", &evodb_repair}, }; static const CRPCCommand commands_wallet[]{ {"evo", &protx_list}, diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index db18ad9e6999..483895479ccb 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -427,11 +427,11 @@ void LegacyScriptPubKeyMan::GenerateNewHDChain(const SecureString& secureMnemoni } } -bool LegacyScriptPubKeyMan::LoadHDChain(const CHDChain& chain) +bool LegacyScriptPubKeyMan::LoadHDChain(const CHDChain& chain, bool skip_encryption_check) { LOCK(cs_KeyStore); - if (m_storage.HasEncryptionKeys() != chain.IsCrypted()) return false; + if (!skip_encryption_check && m_storage.HasEncryptionKeys() != chain.IsCrypted()) return false; m_hd_chain = chain; return true; diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 7f4ee75c413b..ba3c6efbf7bb 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -404,7 +404,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv /* Set the HD chain model (chain child index counters) and writes it to the database */ bool AddHDChain(WalletBatch &batch, const CHDChain& chain); //! Load a HD chain model (used by LoadWallet) - bool LoadHDChain(const CHDChain& chain); + bool LoadHDChain(const CHDChain& chain, bool skip_encryption_check = false); /** * Set the HD chain model (chain child index counters) using temporary wallet db object * which causes db flush every time these methods are used diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index e2e4452d64da..df835e254726 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -572,9 +572,10 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CHDChain chain; ssValue >> chain; assert ((strType == DBKeys::CRYPTED_HDCHAIN) == chain.IsCrypted()); - if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain)) - { - strErr = "Error reading wallet database: SetHDChain failed"; + // Skip encryption check during loading as MASTER_KEY records may not be loaded yet. + // Consistency will be validated after all records are loaded. + if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain, /*skip_encryption_check=*/true)) { + strErr = "Error reading wallet database: LoadHDChain failed"; return false; } } else if (strType == DBKeys::HDPUBKEY) { @@ -875,6 +876,23 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) } m_batch->CloseCursor(); + // Validate HD chain encryption consistency now that all data is loaded + if (auto spk_man = pwallet->GetLegacyScriptPubKeyMan()) { + CHDChain hdChain; + if (spk_man->GetHDChain(hdChain)) { + // If HD chain exists, validate encryption consistency + bool fHasMasterKeys = pwallet->HasEncryptionKeys(); + bool fChainCrypted = hdChain.IsCrypted(); + + if (fHasMasterKeys != fChainCrypted) { + pwallet->WalletLogPrintf("Error: HD chain encryption state (%s) inconsistent with wallet encryption state (%s)\n", + fChainCrypted ? "encrypted" : "not encrypted", + fHasMasterKeys ? "encrypted" : "not encrypted"); + return DBErrors::CORRUPT; + } + } + } + // Set the active ScriptPubKeyMans for (auto spk_man : wss.m_active_external_spks) { pwallet->LoadActiveScriptPubKeyMan(spk_man.second, /*internal=*/false); diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index d9ad16b25459..1f784b0962d0 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -48,7 +48,6 @@ check_json_precision, copy_datadir, force_finish_mnsync, - get_chain_conf_names, get_datadir_path, initialize_datadir, p2p_port, @@ -56,7 +55,8 @@ satoshi_round, softfork_active, wait_until_helper, - get_chain_folder, rpc_port, + get_chain_folder, + write_config, ) @@ -607,7 +607,7 @@ def add_dynamically_node(self, extra_args=None, *, rpchost=None, binary=None): return t_node # TODO: move it to DashTestFramework where it belongs - def dynamically_initialize_datadir(self, node_p2p_port, node_rpc_port): + def dynamically_initialize_datadir(self, mnidx): source_data_dir = get_datadir_path(self.options.tmpdir, 0) # use node0 as a source new_data_dir = get_datadir_path(self.options.tmpdir, len(self.nodes)) @@ -624,27 +624,13 @@ def dynamically_initialize_datadir(self, node_p2p_port, node_rpc_port): if entry not in ['chainstate', 'blocks', 'indexes', 'evodb']: os.remove(os.path.join(new_data_dir, self.chain, entry)) - (chain_name_conf_arg, chain_name_conf_arg_value, chain_name_conf_section) = get_chain_conf_names(self.chain) - - with open(os.path.join(new_data_dir, "dash.conf"), 'w', encoding='utf8') as f: - f.write("{}={}\n".format(chain_name_conf_arg, chain_name_conf_arg_value)) - f.write("[{}]\n".format(chain_name_conf_section)) - f.write("port=" + str(node_p2p_port) + "\n") - f.write("rpcport=" + str(node_rpc_port) + "\n") - f.write("server=1\n") - f.write("debug=1\n") - f.write("keypool=1\n") - f.write("discover=0\n") - f.write("listenonion=0\n") - f.write("printtoconsole=0\n") - f.write("upnp=0\n") - f.write("natpmp=0\n") - f.write("shrinkdebugfile=0\n") - f.write("dip3params=2:2\n") - f.write(f"testactivationheight=v20@{self.v20_height}\n") - f.write(f"testactivationheight=mn_rr@{self.mn_rr_height}\n") - os.makedirs(os.path.join(new_data_dir, 'stderr'), exist_ok=True) - os.makedirs(os.path.join(new_data_dir, 'stdout'), exist_ok=True) + write_config(os.path.join(new_data_dir, "dash.conf"), + n=mnidx, chain=self.chain, disable_autoconnect=self.disable_autoconnect) + self.append_dip3_config(new_data_dir) + + os.makedirs(os.path.join(new_data_dir, 'stderr'), exist_ok=True) + os.makedirs(os.path.join(new_data_dir, 'stdout'), exist_ok=True) + return new_data_dir def start_node(self, i, *args, **kwargs): """Start a dashd""" @@ -1476,11 +1462,7 @@ def add_nodes(self, num_nodes: int, extra_args=None, *, rpchost=None, binary=Non old_num_nodes = len(self.nodes) super().add_nodes(num_nodes, extra_args, rpchost=rpchost, binary=binary, binary_cli=binary_cli, versions=versions) for i in range(old_num_nodes, old_num_nodes + num_nodes): - append_config(self.nodes[i].datadir, [ - "dip3params=2:2", - f"testactivationheight=v20@{self.v20_height}", - f"testactivationheight=mn_rr@{self.mn_rr_height}", - ]) + self.append_dip3_config(self.nodes[i].datadir) if old_num_nodes == 0: # controller node is the only node that has an extra option allowing it to submit sporks append_config(self.nodes[0].datadir, ["sporkkey=cP4EKFyJsHT39LDqgdcB43Y3YXjNyjb5Fuas1GQSeAtjnZWmZEQK"]) @@ -1494,6 +1476,13 @@ def connect_nodes(self, a, b, *, peer_advertises_v2=None): if mn2.nodeIdx is not None: mn2.get_node(self).setmnthreadactive(True) + def append_dip3_config(self, datadir): + append_config(datadir, [ + "dip3params=2:2", + f"testactivationheight=v20@{self.v20_height}", + f"testactivationheight=mn_rr@{self.mn_rr_height}", + ]) + def set_dash_test_params(self, num_nodes, masterodes_count, extra_args=None, evo_count=0): self.mn_count = masterodes_count self.evo_count = evo_count @@ -1593,12 +1582,9 @@ def create_simple_node(self, extra_args=None): def dynamically_add_masternode(self, evo=False, rnd=None, should_be_rejected=False) -> Optional[MasternodeInfo]: mn_idx = len(self.nodes) - node_p2p_port = p2p_port(mn_idx) - node_rpc_port = rpc_port(mn_idx) - protx_success = False try: - created_mn_info = self.dynamically_prepare_masternode(mn_idx, node_p2p_port, evo, rnd) + created_mn_info = self.dynamically_prepare_masternode(mn_idx, evo, rnd) protx_success = True except: self.log.info("dynamically_prepare_masternode failed") @@ -1609,7 +1595,7 @@ def dynamically_add_masternode(self, evo=False, rnd=None, should_be_rejected=Fal # nothing to do return None - self.dynamically_initialize_datadir(node_p2p_port, node_rpc_port) + self.dynamically_initialize_datadir(mn_idx) node_info = self.add_dynamically_node(self.extra_args[0]) args = ['-masternodeblsprivkey=%s' % created_mn_info.keyOperator] + node_info.extra_args @@ -1628,10 +1614,11 @@ def dynamically_add_masternode(self, evo=False, rnd=None, should_be_rejected=Fal self.log.info("Successfully started and synced proTx:"+str(created_mn_info.proTxHash)) return created_mn_info - def dynamically_prepare_masternode(self, idx, node_p2p_port, evo=False, rnd=None) -> MasternodeInfo: + def dynamically_prepare_masternode(self, idx, evo=False, rnd=None) -> MasternodeInfo: mn = MasternodeInfo(evo=evo, legacy=(not softfork_active(self.nodes[0], 'v19'))) mn.generate_addresses(self.nodes[0]) + node_p2p_port = p2p_port(idx) platform_node_id = hash160(b'%d' % rnd).hex() if rnd is not None else hash160(b'%d' % node_p2p_port).hex() addrs_platform_p2p = node_p2p_port + 101 addrs_platform_https = node_p2p_port + 102 diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 107bd72cc6c3..4fb7d537159d 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -406,6 +406,8 @@ def write_config(config_path, *, n, chain, extra_config="", disable_autoconnect= f.write("upnp=0\n") f.write("natpmp=0\n") f.write("shrinkdebugfile=0\n") + # To reduce IO and consumed disk storage use tiny size for allocated blk and rev files + f.write("tinyblk=1\n") # To improve SQLite wallet performance so that the tests don't timeout, use -unsafesqlitesync f.write("unsafesqlitesync=1\n") if disable_autoconnect: