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.
This commit is contained in:
Nicholas Dudfield
2026-03-06 18:38:54 +07:00
parent ce57b6a3a0
commit 94edb5759d
4 changed files with 41 additions and 6 deletions

View File

@@ -68,6 +68,13 @@ target_link_libraries(xrpl.imports.main
$<$<BOOL:${voidstar}>: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)

View File

@@ -22,6 +22,9 @@ target_compile_definitions (opts
$<$<BOOL:${beast_no_unit_test_inline}>:BEAST_NO_UNIT_TEST_INLINE=1>
$<$<BOOL:${beast_disable_autolink}>:BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES=1>
$<$<BOOL:${single_io_service_thread}>:RIPPLE_SINGLE_IO_SERVICE_THREAD=1>
# Enhanced logging is enabled for Debug builds, or explicitly via
# -DBEAST_ENHANCED_LOGGING=ON for other build types.
$<$<OR:$<CONFIG:Debug>,$<BOOL:${BEAST_ENHANCED_LOGGING}>>:BEAST_ENHANCED_LOGGING=1>
$<$<BOOL:${voidstar}>:ENABLE_VOIDSTAR>)
target_compile_options (opts
INTERFACE

View File

@@ -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).

View File

@@ -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<uint256>
@@ -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 << ")";
}
}