From 94edb5759dd2da2f848fc922c99ea23292cffa3e Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Fri, 6 Mar 2026 18:38:54 +0700 Subject: [PATCH] fix(export): gate pre-quorum on verified signature count hasQuorum() and getExportsWithQuorum() were using raw signerMap.size() which includes unverified signatures. TxQ could inject a ttEXPORT pseudo-tx that then fails the stricter verified-signature check in Change::applyExport(). Use verifiedSignatureCount() instead so TxQ only injects when cryptographically verified quorum is actually met. Also add cmake plumbing for enhanced logging: link date::date-tz when available and enable BEAST_ENHANCED_LOGGING for Debug builds. --- cmake/RippledCore.cmake | 7 +++++ cmake/RippledInterface.cmake | 3 ++ src/xrpld/app/misc/ExportSignatureCollector.h | 8 +++++ .../misc/detail/ExportSignatureCollector.cpp | 29 +++++++++++++++---- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/cmake/RippledCore.cmake b/cmake/RippledCore.cmake index c96c42b6c..efe2c1797 100644 --- a/cmake/RippledCore.cmake +++ b/cmake/RippledCore.cmake @@ -68,6 +68,13 @@ target_link_libraries(xrpl.imports.main $<$:antithesis-sdk-cpp> ) +# Enhanced logging can use date::make_zoned/current_zone when location-rich log +# formatting is compiled in. Link date-tz when available so Debug builds and +# explicit -DBEAST_ENHANCED_LOGGING=ON builds resolve those symbols. +if(TARGET date::date-tz) + target_link_libraries(xrpl.imports.main INTERFACE date::date-tz) +endif() + include(add_module) include(target_link_modules) diff --git a/cmake/RippledInterface.cmake b/cmake/RippledInterface.cmake index 93a973ac7..db8407089 100644 --- a/cmake/RippledInterface.cmake +++ b/cmake/RippledInterface.cmake @@ -22,6 +22,9 @@ target_compile_definitions (opts $<$:BEAST_NO_UNIT_TEST_INLINE=1> $<$:BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES=1> $<$:RIPPLE_SINGLE_IO_SERVICE_THREAD=1> + # Enhanced logging is enabled for Debug builds, or explicitly via + # -DBEAST_ENHANCED_LOGGING=ON for other build types. + $<$,$>:BEAST_ENHANCED_LOGGING=1> $<$:ENABLE_VOIDSTAR>) target_compile_options (opts INTERFACE diff --git a/src/xrpld/app/misc/ExportSignatureCollector.h b/src/xrpld/app/misc/ExportSignatureCollector.h index 3e9d7346f..2d15d5ba6 100644 --- a/src/xrpld/app/misc/ExportSignatureCollector.h +++ b/src/xrpld/app/misc/ExportSignatureCollector.h @@ -124,6 +124,14 @@ public: std::size_t signatureCount(uint256 const& txnHash) const; + /** Get the number of cryptographically verified signatures. + + @param txnHash The hash of the exported transaction + @return Number of verified validator signatures + */ + std::size_t + verifiedSignatureCount(uint256 const& txnHash) const; + /** Check if an export has reached quorum. Quorum is 80% of the UNL (rounded up). diff --git a/src/xrpld/app/misc/detail/ExportSignatureCollector.cpp b/src/xrpld/app/misc/detail/ExportSignatureCollector.cpp index 8be84d447..af669eb50 100644 --- a/src/xrpld/app/misc/detail/ExportSignatureCollector.cpp +++ b/src/xrpld/app/misc/detail/ExportSignatureCollector.cpp @@ -258,6 +258,17 @@ ExportSignatureCollector::signatureCount(uint256 const& txnHash) const return 0; } +std::size_t +ExportSignatureCollector::verifiedSignatureCount(uint256 const& txnHash) const +{ + std::lock_guard lock(mutex_); + + auto it = verified_.find(txnHash); + if (it != verified_.end()) + return it->second.size(); + return 0; +} + std::size_t ExportSignatureCollector::getUNLSize(ReadView const& view, Application& app) const @@ -271,16 +282,18 @@ ExportSignatureCollector::hasQuorum( ReadView const& view, Application& app) const { - auto const sigCount = signatureCount(txnHash); + auto const verifiedCount = verifiedSignatureCount(txnHash); + auto const totalCount = signatureCount(txnHash); auto const unlSize = getUNLSize(view, app); auto const threshold = calculateQuorumThreshold(unlSize); JLOG(j_.trace()) << "Export: hasQuorum check for " << txnHash - << " sigCount=" << sigCount << " unlSize=" << unlSize + << " verified=" << verifiedCount + << " total=" << totalCount << " unlSize=" << unlSize << " threshold=" << threshold; - return sigCount >= threshold; + return verifiedCount >= threshold; } std::vector @@ -296,12 +309,16 @@ ExportSignatureCollector::getExportsWithQuorum( for (auto const& [txnHash, signerMap] : signatures_) { - if (signerMap.size() >= threshold) + auto verIt = verified_.find(txnHash); + auto const verifiedCount = + verIt != verified_.end() ? verIt->second.size() : 0u; + if (verifiedCount >= threshold) { ready.push_back(txnHash); JLOG(j_.info()) - << "Export: quorum reached for " << txnHash << " (" - << signerMap.size() << "/" << unlSize << " signatures)"; + << "Export: quorum reached for " << txnHash << " (verified=" + << verifiedCount << " total=" << signerMap.size() << "/" + << unlSize << ")"; } }